LINUX下的帧缓冲是LINUX视频系统的核心,其重大意义在于使得开发人员可以与平台无关的方式编写应用程序和较高内核层的程序.从而使得内核的帧缓冲接口允许应用程序与底层图形硬件的变化无关.也就是说,只要应用程序和显示器驱动程序遵循缓冲接口,应用程序可以不用改变就可以在不同类型的视频硬件上运行.其框架图如下:
下面以MINI2440为实例进行分析.
1.平台总线、平台设备、平台驱动:
LINUX中用软件总线来组织设备与驱动.每一个设备或驱动隶属某一条总线.总线是设备找到其相应的驱动的一个桥梁.只是每一条总线都挂了多个设备多个驱动,某设备找到其相应的驱动的依据条件不同,比如平台总线有平台总线的匹配依据;IIC总线有IIC总线的匹配依据;USB总线有USB总线的匹配依据等.见博文中IIC子系统分析一文.
1-1.平台设备:
MINI2440的LCD向内核注册平台设备代码如下:
arch/arm/mach-s3c2440/mach-mini2440.c:
/*
* mini2440_features string
*
* t = Touchscreen present
* b = backlight control
* c = camera [TODO]
* 0-9 LCD configuration
*
*/
static char mini2440_features_str[12] __initdata = "0tb";
此处把LCD的配置信息存放在段mini2440_features_str段数组里面,并初始化为"0tb".
static void __init mini2440_init(void)
{
... ...;
mini2440_parse_features(&features, mini2440_features_str);
... ...;
if (features.count) /* the optional features */
platform_add_devices(features.optional, features.count);
}
mini2440_init()函数会被系统启动过去调用,和LCD相关的函数有两个:mini2440_parse_features()和platform_add_devices().
static void mini2440_parse_features(struct mini2440_features_t * features,const char * features_str )
{
const char * fp = features_str;
features->count = 0;
features->done = 0;
features->lcd_index = -1;
while (*fp) {
char f = *fp++;
switch (f) {
case '0'...'9': /* tft screen */
if (features->done & FEATURE_SCREEN) {
printk(KERN_INFO "MINI2440: '%c' ignored, "
"screen type already set\n", f);
} else {
int li = f - '0';
if (li >= ARRAY_SIZE(mini2440_lcd_cfg))
printk(KERN_INFO "MINI2440: "
"'%c' out of range LCD mode\n", f);
else {
features->optional[features->count++] =
&s3c_device_lcd;
features->lcd_index = li;
}
}
features->done |= FEATURE_SCREEN;
break;
... ...;
}
}
看参数features,如下:
struct mini2440_features_t {
int count;
int done;
int lcd_index;
struct platform_device *optional[8];
};
里面的optional域就是平台设备.上述函数mini2440_parse_features()把LCD平台相关信息s3c_device_lcd保存在此features->optional域:
static struct resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_lcd_dmamask = 0xffffffffUL;
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
至此,把mini2440平台相关的LCD信息打包进feature的optional域,并通过函数platform_add_devices()注册进内核的平台设备.见上述函数mini2440_init():
if (features.count) /* the optional features */
platform_add_devices(features.optional, features.count);
因此,就这样,MINI2440的LCD的作为一个平台设备注册进内核里面去了.
1-2.平台驱动:
MINI2440关于LCD的平台驱动的源码位于drivers/video/s3c2410fb.c:
module_init(s3c2410fb_init);
展开函数s3c2410fb_init:
int __init s3c2410fb_init(void)
{
int ret = platform_driver_register(&s3c2410fb_driver);
if (ret == 0)
ret = platform_driver_register(&s3c2412fb_driver);
return ret;
}
其中平台驱动s3c2410fb_driver定义如下:
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,
},
};
通过函数platform_driver_register()把这个平台驱动注册进内核,它会detect到其所支持的设备.其detect的依据是驱动名和设备名是否一致(具体过程可参看博文的IIC子系统分析,不再累赘):
平台驱动名:
.driver = {
.name = "s3c2410-lcd",
平台设备名:
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
它们的match的成功,将会引发平台驱动probe函数的执行,即s3c2410fb_probe()函数被调用:
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,
},
};
展开函数s3c2412fb_probe():
static int __init s3c2412fb_probe(struct platform_device *pdev)
{
return s3c24xxfb_probe(pdev, DRV_S3C2412);
}
这里的参数pdev即为:
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
展开函数s3c24xxfb_probe():
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;
mach_info = pdev->dev.platform_data;
if (mach_info == NULL) {
dev_err(&pdev->dev,
"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
if (mach_info->default_display >= mach_info->num_displays) {
dev_err(&pdev->dev, "default is %d but only %d displays\n",
mach_info->default_display, mach_info->num_displays);
return -EINVAL;
}
display = mach_info->displays + mach_info->default_display;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo)
return -ENOMEM;
platform_set_drvdata(pdev, fbinfo);
info = fbinfo->par;
info->dev = &pdev->dev;
info->drv_type = drv_type;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}
size = (res->end - res->start) + 1;
info->mem = request_mem_region(res->start, size, pdev->name);
if (info->mem == NULL) {
dev_err(&pdev->dev, "failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}
info->io = ioremap(res->start, size);
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
dprintk("devinit\n");
strcpy(fbinfo->fix.id, driver_name);
/* Stop the video */
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
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;
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);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}
info->clk = clk_get(NULL, "lcd");
if (!info->clk || IS_ERR(info->clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
ret = -ENOENT;
goto release_irq;
}
clk_enable(info->clk);
dprintk("got and enabled clock\n");
msleep(1);
info->clk_rate = clk_get_rate(info->clk);
/* find maximum required memory size for display */
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres;
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 */
ret = s3c2410fb_map_video_memory(fbinfo);
if (ret) {
printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video memory\n");
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
s3c2410fb_init_registers(fbinfo);
s3c2410fb_check_var(&fbinfo->var, fbinfo);
ret = s3c2410fb_cpufreq_register(info);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register cpufreq\n");
goto free_video_memory;
}
ret = register_framebuffer(fbinfo);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n",
ret);
goto free_cpufreq;
}
/* create device files */
ret = device_create_file(&pdev->dev, &dev_attr_debug);
if (ret) {
printk(KERN_ERR "failed to add debug attribute\n");
}
printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);
return 0;
free_cpufreq:
s3c2410fb_cpufreq_deregister(info);
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;
}
对于LINUX LCD子系统,一个核心的数据结构就是struct fb_info.是和LINUX LCD核心层打交道的关键数据.也就是说,我们只需要把我们平台相关的LCD信息及操作方法打包成struct fb_info去和LINUX LCD核心层打交互即可.下面逐步分析这个结构体的相关项的重要意义.
2.struct fb_info结构体:
struct fb_info {
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
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;
/* we need the PCI or similiar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
resource_size_t aperture_base;
resource_size_t aperture_size;
};
我们知道,struct fb_info是LINUX子系统的关键数据结构,是和LCD核心层交互的核心数据.特定平台需要封装特定平台的fb_info.因此,在函数s3c24xxfb_probe()里面:1.先分配这样的一个数据结构体;2.然后再根据我们的具体平台初始化这个结构体;3.最后通过函数register_framebuffer()注册进LINUX的LCD子系统.下面根据这三步来分析:
2-1.分配fb_info结构体:
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
其中第一个参数是和具体平台相关的:s3c2410fb_info;第二个参数pdev即为LCD的平台设备:s3c_device_lcd.
2-1-1.struct platform_device s3c_device_lcd:
这个结构体记录了S3C平台的LCD设备资源信息,比如说LCD控制器寄存器的物理地址、内存位置和中断号.
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
如上述结构体的resource域记录的便是平台相关的LCD信息:
/* LCD Controller */
static struct resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}
};
如上述结构体的start域存在的便是S3C2410(S3C2440)LCD控制器组的起始物理地址:
#define S3C24XX_PA_LCD S3C2410_PA_LCD
#define S3C2410_PA_LCD (0x4D000000)
对照S3C2440的数据手册:
以后对LCD控制器的配置,直接配置此内在区域即可.此内存区域的大小为:
#define S3C24XX_SZ_LCD SZ_1M
#define SZ_1M 0x00100000
具体可结构S3C2440的数据手册参看.
2-1-2.struct s3c2410fb_info:
明显,这个结构体是记录与平台相关的信息,它被记录在struct fb_info的par域,见函数framebuffer_alloc()源码:
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
}
中有语句:
if (size)
info->par = p + fb_info_size;
在结构体struct fb_info里面有如下注释:
/* From here on everything is device dependent */
void *par;
这个指针域记录的是平台相关的信息,如这里记录的是struct s3c2410fb_info.具体平台的fb_info和LCD子系统的fb_info通过此域关联起来.
至此,需要对平台相关的xxfb_info和LCD核心层的fb_info结构体进行初始化了.
2-1-3.struct s3c2410fb_info *info的初始化:
info = fbinfo->par;
info->dev = &pdev->dev;
info->drv_type = drv_type;
info->mem = request_mem_region(res->start, size, pdev->name);
info->io = ioremap(res->start, size);
info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
info->clk = clk_get(NULL, "lcd");
clk_enable(info->clk);
info->clk_rate = clk_get_rate(info->clk);
ret = s3c2410fb_map_video_memory(fbinfo);
上述便是相关平台相关的xxfb_info的初始化.其中下面代码:
info = fbinfo->par;
这代码便是平台相关的xxfb_info和LCD核心层的fb_info的关联.
info->dev = &pdev->dev;
其中,pdev即为平台设备端的资源,这里是:s3c_device_lcd.其dev域如下:
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
下面代码:
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
info->mem = request_mem_region(res->start, size, pdev->name);
info->io = ioremap(res->start, size);
第一句是获取平台设备端(即s3c_dvice_lcd)的内存资源信息:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}
见s3c_device_lcd:
/* LCD Controller */
static struct resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}
};
然后把这段内存通过函数ioremap()映射映射成"I/O端口"映射成内存地址,以后要操作某寄存器,变转化成操作这段内在空间即可.
下面代码:
ret = s3c2410fb_map_video_memory(fbinfo);
分配frame buffer.
至此,平台相关的xxfb_info算是初始化完成.
2-1-4.struct fb_info *fbinfo的初始化:
struct fb_info *fbinfo是LCD子系统的关键数据,"打包"进LCD核心层,包括操作集及向用户空间暴露设备节点(如/dev/fbn,n = 0,1,2,...)均在此结构体里面体现.
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;
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;
/* find maximum required memory size for display */
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres;
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;
}
ret = s3c2410fb_map_video_memory(fbinfo);
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
s3c2410fb_init_registers(fbinfo);
s3c2410fb_check_var(&fbinfo->var, fbinfo);
上述便是对LCD子系统的关键数据结构fb_info的"打包(即初始化)".
下面的代码:
fbinfo->fbops = &s3c2410fb_ops;
便是与平台相关的操作集,对应用户空间的系统调用:
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_setcolreg = s3c2410fb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
接下来借助平台设备端的信息初始化fb_info:
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
下面追踪一下display的由来:
display = mach_info->displays + mach_info->default_display;
mach_info = pdev->dev.platform_data;
这里的pdev即为平台总线设备s3c_device_lcd.我们实际使用的过程中,比如屏的大小(如3.5寸、4.3寸等)是需要设置的,下面来看这些具体的屏设备如何设置.以内核自带的mini2440搭载的3.5寸屏为例,进行分析.看这些屏信息如何流窜到LCD子系统的.
arch/arm/mach-s3c2440/mach-mini2440.c:
static struct s3c2410fb_display mini2440_lcd_cfg[] __initdata = {
[0] = { /* mini2440 + 3.5" TFT + touchscreen */
_LCD_DECLARE(
7, /* The 3.5 is quite fast */
240, 21, 38, 6, /* x timing */
320, 4, 4, 2, /* y timing */
60), /* refresh rate */
.lcdcon5 = (S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_INVVDEN |
S3C2410_LCDCON5_PWREN),
},
[1] = { /* mini2440 + 7" TFT + touchscreen */
_LCD_DECLARE(
10, /* the 7" runs slower */
800, 40, 40, 48, /* x timing */
480, 29, 3, 3, /* y timing */
50), /* refresh rate */
.lcdcon5 = (S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN),
},
/* The VGA shield can outout at several resolutions. All share
* the same timings, however, anything smaller than 1024x768
* will only be displayed in the top left corner of a 1024x768
* XGA output unless you add optional dip switches to the shield.
* Therefore timings for other resolutions have been ommited here.
*/
[2] = {
_LCD_DECLARE(
10,
1024, 1, 2, 2, /* y timing */
768, 200, 16, 16, /* x timing */
24), /* refresh rate, maximum stable,
tested with the FPGA shield */
.lcdcon5 = (S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_HWSWP),
},
};
见上面的宏--_LCD_DECLARE,是具体屏的信息,如长、宽、边框等.如下:
/* LCD timing and setup */
/*
* This macro simplifies the table bellow
*/
#define _LCD_DECLARE(_clock,_xres,margin_left,margin_right,hsync, \
_yres,margin_top,margin_bottom,vsync, refresh) \
.width = _xres, \
.xres = _xres, \
.height = _yres, \
.yres = _yres, \
.left_margin = margin_left, \
.right_margin = margin_right, \
.upper_margin = margin_top, \
.lower_margin = margin_bottom, \
.hsync_len = hsync, \
.vsync_len = vsync, \
.pixclock = ((_clock*100000000000LL) / \
((refresh) * \
(hsync + margin_left + _xres + margin_right) * \
(vsync + margin_top + _yres + margin_bottom))), \
.bpp = 16,\
.type = (S3C2410_LCDCON1_TFT16BPP |\
S3C2410_LCDCON1_TFT)
具体的LCD需要根据具体的LCD硬件信息配置这个宏,如长、宽、边框、时钟、显示格式.
域lcdcon5是根据具体的LCD的硬件信息对LCD主控端的配置信息域.这些信息将会被写到S3C2440的寄存器才会实际生效.这些具体的屏的信息是如何和LCD子系统的关键数据fb_info关联起来的呢?下面继续分析.
上述的结构体mini2440_lcd_cfg又被封装在结构体mini2440_fb_info当中的display域:
static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {
.displays = &mini2440_lcd_cfg[0], /* not constant! see init */
.num_displays = 1,
.default_display = 0,
/* Enable VD[2..7], VD[10..15], VD[18..23] and VCLK, syncs, VDEN
* and disable the pull down resistors on pins we are using for LCD
* data. */
.gpcup = (0xf << 1) | (0x3f << 10),
.gpccon = (S3C2410_GPC1_VCLK | S3C2410_GPC2_VLINE |
S3C2410_GPC3_VFRAME | S3C2410_GPC4_VM |
S3C2410_GPC10_VD2 | S3C2410_GPC11_VD3 |
S3C2410_GPC12_VD4 | S3C2410_GPC13_VD5 |
S3C2410_GPC14_VD6 | S3C2410_GPC15_VD7),
.gpccon_mask = (S3C2410_GPCCON_MASK(1) | S3C2410_GPCCON_MASK(2) |
S3C2410_GPCCON_MASK(3) | S3C2410_GPCCON_MASK(4) |
S3C2410_GPCCON_MASK(10) | S3C2410_GPCCON_MASK(11) |
S3C2410_GPCCON_MASK(12) | S3C2410_GPCCON_MASK(13) |
S3C2410_GPCCON_MASK(14) | S3C2410_GPCCON_MASK(15)),
.gpdup = (0x3f << 2) | (0x3f << 10),
.gpdcon = (S3C2410_GPD2_VD10 | S3C2410_GPD3_VD11 |
S3C2410_GPD4_VD12 | S3C2410_GPD5_VD13 |
S3C2410_GPD6_VD14 | S3C2410_GPD7_VD15 |
S3C2410_GPD10_VD18 | S3C2410_GPD11_VD19 |
S3C2410_GPD12_VD20 | S3C2410_GPD13_VD21 |
S3C2410_GPD14_VD22 | S3C2410_GPD15_VD23),
.gpdcon_mask = (S3C2410_GPDCON_MASK(2) | S3C2410_GPDCON_MASK(3) |
S3C2410_GPDCON_MASK(4) | S3C2410_GPDCON_MASK(5) |
S3C2410_GPDCON_MASK(6) | S3C2410_GPDCON_MASK(7) |
S3C2410_GPDCON_MASK(10) | S3C2410_GPDCON_MASK(11)|
S3C2410_GPDCON_MASK(12) | S3C2410_GPDCON_MASK(13)|
S3C2410_GPDCON_MASK(14) | S3C2410_GPDCON_MASK(15)),
};
见上述代码:
.displays = &mini2440_lcd_cfg[0]
在函数mini2440_init()中:
static void __init mini2440_init(void)
{
mini2440_parse_features(&features, mini2440_features_str);
if (features.lcd_index != -1) {
int li;
mini2440_fb_info.displays =
&mini2440_lcd_cfg[features.lcd_index];
printk(KERN_INFO "MINI2440: LCD");
for (li = 0; li < ARRAY_SIZE(mini2440_lcd_cfg); li++)
if (li == features.lcd_index)
printk(" [%d:%dx%d]", li,
mini2440_lcd_cfg[li].width,
mini2440_lcd_cfg[li].height);
else
printk(" %d:%dx%d", li,
mini2440_lcd_cfg[li].width,
mini2440_lcd_cfg[li].height);
printk("\n");
s3c24xx_fb_set_platdata(&mini2440_fb_info);
}
}
展开函数s3c24xx_fb_set_platdata(&mini2440_fb_info):
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
struct s3c2410fb_mach_info *npd;
npd = kmalloc(sizeof(*npd), GFP_KERNEL);
if (npd) {
memcpy(npd, pd, sizeof(*npd));
s3c_device_lcd.dev.platform_data = npd;
} else {
printk(KERN_ERR "no memory for LCD platform data\n");
}
}
可见,平台设备端记录了具体LCD的配置信息:
s3c_device_lcd.dev.platform_data = npd;
参数npd即为mini2440_fb_info.
回到上面的2-1-4:
display = mach_info->displays + mach_info->default_display;
mach_info = pdev->dev.platform_data;
因此,这里mach_info实际保存的是mini2440_fb_info.mini2440_fb_info中的displays域保存的是具体某一款LCD的硬件特性配置,如3.5寸屏.见函数mini2440_init()下面代码:
mini2440_fb_info.displays =
&mini2440_lcd_cfg[features.lcd_index];
保存的是结构体数组mini2440_lcd_cfg的某一元素,其元素成员即为保证某一款屏的硬件特性配置信息.回到2-1-4:
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
LCD子系统的关键数据fbinfo这时获取了具体LCD硬件特性配置的信息,如长、宽、像素点的位数等.
回到2-1-4,见函数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;
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) */
modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);
modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
local_irq_restore(flags);
dprintk("LPCSEL = 0x%08lx\n", mach_info->lpcsel);
writel(mach_info->lpcsel, lpcsel);
dprintk("replacing TPAL %08x\n", readl(tpal));
/* ensure temporary palette disabled */
writel(0x00, tpal);
return 0;
}
这里对平台LCD控制器的初始化.比如使能LCD控制引脚的上拉电阻之类的.
2-1-4接下来的函数 s3c2410fb_check_var(&fbinfo->var, fbinfo),此函数中涉及到另外一个重要的数据结构:struct fb_var_screeninfo.此结构体保存的是用户可修改的关于FBI的信息.比如X向分辨率、Y向分辨率、像素的位数、pixclock等.如下:
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 */
};
用户空间的命令fbset抓取的信息便是对应此结构体:
#fbset
mode "240x320-106"
# D: 9.709 MHz, H: 34.674 kHz, V: 106.362 Hz
geometry 240 320 240 640 16
timings 103000 20 10 2 2 10 2
accel false
rgba 5/11,6/5,5/0,0/0
endmode
#
明显,结构体struct fb_var_screeninfo保存的信息和具体的LCD硬件参数有关.在函数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;
struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
struct s3c2410fb_display *display = NULL;
struct s3c2410fb_display *default_display = mach_info->displays +
mach_info->default_display;
int type = default_display->type;
unsigned i;
dprintk("check_var(var=%p, info=%p)\n", var, info);
/* validate x/y resolution */
/* choose default mode if possible */
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\n",
var->xres, var->yres, var->bits_per_pixel);
return -EINVAL;
}
/* it is always the size as the display */
var->xres_virtual = display->xres;
var->yres_virtual = display->yres;
var->height = display->height;
var->width = display->width;
/* copy lcd settings */
var->pixclock = display->pixclock;
var->left_margin = display->left_margin;
var->right_margin = display->right_margin;
var->upper_margin = display->upper_margin;
var->lower_margin = display->lower_margin;
var->vsync_len = display->vsync_len;
var->hsync_len = display->hsync_len;
fbi->regs.lcdcon5 = display->lcdcon5;
/* set display type */
fbi->regs.lcdcon1 = display->type;
var->transp.offset = 0;
var->transp.length = 0;
/* set r/g/b positions */
switch (var->bits_per_pixel) {
case 1:
case 2:
case 4:
var->red.offset = 0;
var->red.length = var->bits_per_pixel;
var->green = var->red;
var->blue = var->red;
break;
case 8:
if (display->type != S3C2410_LCDCON1_TFT) {
/* 8 bpp 332 */
var->red.length = 3;
var->red.offset = 5;
var->green.length = 3;
var->green.offset = 2;
var->blue.length = 2;
var->blue.offset = 0;
} else {
var->red.offset = 0;
var->red.length = 8;
var->green = var->red;
var->blue = var->red;
}
break;
case 12:
/* 12 bpp 444 */
var->red.length = 4;
var->red.offset = 8;
var->green.length = 4;
var->green.offset = 4;
var->blue.length = 4;
var->blue.offset = 0;
break;
default:
case 16:
if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) {
/* 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;
}
因此,此处便是借助具体的LCD硬件信息去"打包"结构体struct fb_var_screeninfo.
和上面的结构体struct fb_var_screeninfo对应的,有结构体struct fb_fix_screeninfo,此结构体保存的是用户无法修改的信息,比如帧缓冲在内存的起始地址和大小.回到2-1-4,关于fb_info的fix域的初始化代码如下:
strcpy(fbinfo->fix.id, driver_name);
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;
/* find maximum required memory size for display */
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres;
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;
}
static int __init s3c2410fb_map_video_memory(struct fb_info *info)
展开函数s3c2410fb_map_video_memory(fbinfo):
static int __init s3c2410fb_map_video_memory(struct fb_info *info)
{
struct s3c2410fb_info *fbi = info->par;
dma_addr_t map_dma;
unsigned map_size = PAGE_ALIGN(info->fix.smem_len);
dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);
info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,
&map_dma, GFP_KERNEL);
if (info->screen_base) {
/* prevent initial garbage on screen */
dprintk("map_video_memory: clear %p:%08x\n",
info->screen_base, map_size);
memset(info->screen_base, 0x00, map_size);
info->fix.smem_start = map_dma;
dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",
info->fix.smem_start, info->screen_base, map_size);
}
return info->screen_base ? 0 : -ENOMEM;
}
其中下面两语句:
info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,
&map_dma, GFP_KERNEL);
info->fix.smem_start = map_dma;
分配了一个DMA内存,并将其记录在fb_info->fix.smem_start中,这是帧缓冲(FB)的内存起始位置.但是,这里只是分配内存,并不指定这段内存从哪个位置开始分配,因为LCD的帧缓冲的地址是唯一的,这段内存空间是如何和LCD的帧缓冲关联起来的呢?在函数s3c24xxfb_probe()中,见下面代码:
ret = s3c2410fb_cpufreq_register(info);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register cpufreq\n");
goto free_video_memory;
}
展开函数s3c2410fb_cpufreq_register():
static inline int s3c2410fb_cpufreq_register(struct s3c2410fb_info *info)
{
info->freq_transition.notifier_call = s3c2410fb_cpufreq_transition;
return cpufreq_register_notifier(&info->freq_transition,
CPUFREQ_TRANSITION_NOTIFIER);
}
回调函数s3c2410fb_cpufreq_transition保存在info->freq_transition.notifier_call.如何实现回调在此处就不展开.下面展开函数s3c2410fb_cpufreq_transition():
static int s3c2410fb_cpufreq_transition(struct notifier_block *nb,unsigned long val, void *data)
{
s3c2410fb_activate_var(fbinfo);
}
展开函数s3c2410fb_activate_var(fbinfo):
/* 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;
struct fb_var_screeninfo *var = &info->var;
int clkdiv;
clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2);
dprintk("%s: var->xres = %d\n", __func__, var->xres);
dprintk("%s: var->yres = %d\n", __func__, var->yres);
dprintk("%s: var->bpp = %d\n", __func__, var->bits_per_pixel);
if (type == S3C2410_LCDCON1_TFT) {
s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);
--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);
/* write new registers */
dprintk("new register set:\n");
dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);
writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,
regs + S3C2410_LCDCON1);
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);
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
}
展开函数s3c2410fb_set_lcdaddr():
/* s3c2410fb_set_lcdaddr
*
* initialise lcd controller address pointers
*/
static void s3c2410fb_set_lcdaddr(struct fb_info *info)
{
unsigned long saddr1, saddr2, saddr3;
struct s3c2410fb_info *fbi = info->par;
void __iomem *regs = fbi->io;
saddr1 = info->fix.smem_start >> 1;
saddr2 = info->fix.smem_start;
saddr2 += info->fix.line_length * info->var.yres;
saddr2 >>= 1;
saddr3 = S3C2410_OFFSIZE(0) |
S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff);
dprintk("LCDSADDR1 = 0x%08lx\n", saddr1);
dprintk("LCDSADDR2 = 0x%08lx\n", saddr2);
dprintk("LCDSADDR3 = 0x%08lx\n", saddr3);
writel(saddr1, regs + S3C2410_LCDSADDR1);
writel(saddr2, regs + S3C2410_LCDSADDR2);
writel(saddr3, regs + S3C2410_LCDSADDR3);
}
下面代码实现了DMA内存和S3C2440平台LCD控制器帧缓冲的关联:
writel(saddr1, regs + S3C2410_LCDSADDR1);
writel(saddr2, regs + S3C2410_LCDSADDR2);
writel(saddr3, regs + S3C2410_LCDSADDR3);
3.向LCD核心层注册fb_info:
经过上述第2步,完成了具体平台、具体LCD信息对LCD子系统关键数据fb_info的打包,接下来通过函数register_framebuffer()把xxfb_info注册进内核:
/**
* 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)
return -ENXIO;
if (fb_check_foreignness(fb_info))
return -ENOSYS;
/* check all firmware fbs and kick off if the base addr overlaps */
for (i = 0 ; i < FB_MAX; i++) {
if (!registered_fb[i])
continue;
if (registered_fb[i]->flags & FBINFO_MISC_FIRMWARE) {
if (fb_do_apertures_overlap(registered_fb[i], fb_info)) {
printk(KERN_ERR "fb: conflicting fb hw usage "
"%s vs %s - removing generic driver\n",
fb_info->fix.id,
registered_fb[i]->fix.id);
unregister_framebuffer(registered_fb[i]);
break;
}
}
}
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
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_add_videomode(&mode, &fb_info->modelist);
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;
}
下面的代码完成了/dev/fbn(n=0,1,2,...)设备节点的创建:
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
LINUX下一个驱动要实现设备节点的创建需要下面两个内核API:
class_create(owner, name)
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
注意到上述代码:
fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
函数device_create()中有个参数fb_class是全局变量:
struct class *fb_class;
EXPORT_SYMBOL(fb_class);
其初始化代码如下:
/**
* 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))
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;
}
因此,语句 fb_class = class_create(THIS_MODULE, "graphics")和语句 fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i);共同生成了设备节点/dev/fbn(n=0,1,2,...).在上述的函数fbmem_init()中,见语句:
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
把LCD当作字符设备来看待.这部分代码是平台无关的,其中平台无关性的操作集fb_fops:
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
};
这里是平台无关的代码,同时也是直接面向用户空间系统调用的操作集.但是实际我们的操作中,我们往往要作用在具体平台搭载的LCD硬件特性上.那么,LINUX下的LCD子系统是如何实现平台无关到平台相关的过渡的呢?下面以open()函数为例分析这一思想设计过程.当用户空间进行open进,对应的fb_open函数被调用:
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];
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;
if (info->fbops->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;
}
注意到这里会根据设备号/dev/fbn(n = 0,1,2,...)提取出我们相应的平台相关的fb_info,见下面代码:
info = registered_fb[fbidx];
if (!info)
request_module("fb%d", fbidx);
info = registered_fb[fbidx];
其中,registered_fb是一个全局数组:
extern struct fb_info *registered_fb[FB_MAX];
当我们把我们具体平台相关的fb_info注册进LCD核心子系统时,我们平台相关的fb_info被记录在这个数组里面.见函数int register_framebuffer(struct fb_info *fb_info)下面代码:
for (i = 0 ; i < FB_MAX; i++) {
if (!registered_fb[i])
continue;
if (registered_fb[i]->flags & FBINFO_MISC_FIRMWARE) {
if (fb_do_apertures_overlap(registered_fb[i], fb_info)) {
printk(KERN_ERR "fb: conflicting fb hw usage "
"%s vs %s - removing generic driver\n",
fb_info->fix.id,
registered_fb[i]->fix.id);
unregister_framebuffer(registered_fb[i]);
break;
}
}
}
registered_fb[i] = fb_info;
因此,回到函数fb_open()中:
info = registered_fb[fbidx];
提取的便是相应的平台相关的fb_info.见下面的代码:
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
用户空间虽然直接面向的是fb_open函数,但是在此函数中.又重载了具体平台相关的fb_open()函数.因此,以S3C2440平台为例,在函数:
static int __init s3c24xxfb_probe(struct platform_device *pdev,enum s3c_drv_type drv_type)
见下面代码:
fbinfo->fbops = &s3c2410fb_ops;
展开操作集s3c2410fb_ops:
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_setcolreg = s3c2410fb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
只是这里没有重载open函数.
3.系统调用:
随着s3c24xxfb_probe()函数的执行完成,内核态的软件布署也宣布完成.接下来看是LCD子系统是如何跟用户空间交互的.在LINUX下的LCD子系统,由关键数据结构fb_info去承载.见函数s3c24xxfb_probe()中下面代码:
fbinfo->fbops = &s3c2410fb_ops;
fbops结构包含了底层帧缓冲驱动程序提供的所有函数指针.fbops中前几个方法是必须要实现的,剩下的是可选的(主要用于图形加速).比如当用户接口需要执行繁重的视频操作,像混合(blending)、拉伸(stretching)、移动位图、动态渐变(gradient)生成,这时候需要通过图形加速来获取可以接受的性能.比如某CPU平台支持图形加速,大体会进行下面的动作:
fb_imageblit()方法在显示器画一幅图像,此函数为驱动提供了一个能利用视频控制器所拥有的某些特别能力的机会,加速这个操作.如果硬件不支持加速功能,cfb_imageblit()就是帧缓冲核心为加速提供的通用库函数,如在启动阶段向屏幕输出logo.fb_copyarea()将屏幕的一个矩形区域复制到另一个区域.如果图形控制器没有任何可加速这个操作能力,那么cfb_copyarea()就是为这个操作提供优化的方法.fb_fillrect()方法能快速用像素行填充矩形区.cfb_fillrect()则是通用的非加速的方法手段.
[附:]DirectFB.DirectFB是一个建立在帧缓冲接口之上的库,它为硬件图形加速和可视化接口提供了一个简单的窗口管理框架和钩子.它同时也是一个虚拟接口,允许多个帧缓冲应用程序同时存在.某些关心图形性能的嵌入式设备采用DirectFB方案,在这种策略中,DirectFB与底层的加速的帧缓冲设备驱动程序与上层的支持DierctFB的渲染引擎.
4.启动logo:
如果要在机器启动过程中显示一个logo.需要在内核配置阶段启用CONFIG_LOGO选项并选择一个可用的logo.如果需要自定义logo,往目录drivers/video/logo增加即可.常用的LOGO的图片格式是CLUT224.其实现主要由底层帧缓冲驱动程序的fb_imageblit()函数完成.其他格式的logo(如16色的vga16和黑白的单色),可以借助script/目录下的脚本将标准的PPM转换成logo格式.