观察linux内核中的 /drivers/video/
查找包含fb的文件;会发现framebuffer的实现需要三个关键文件:
1 /linux/include/linux/fb.h
2/linux/drivers/video/fbmem.c
3/linux/drivers/video/****fb.c;比如linux内核中给我们提供的s3c2410fb.c/h;
再比如: atmel_lcdfb.c;
即根据不同的硬件要有不同的 framebuffer的实现;
这里具体分析的是 s3c2410板子的实现,以它为参考,方便我们做其他硬件设备的驱动;
一、总体概述:
用户自定义一个fb的驱动,向系统注册(系统的fbmem.c中的register_framebuffer);
然后,我们就把剩下的工作交给系统内核处理;即整个过程为:
our driver——frame buffer driver——linux kernel
(1)前提知识1:
三星的s3c2410用户手册上:LCD Controller有:
LCDCON1/2/3/4/5;LCDSADDR1/2/3(SNT/TFT分别为fb起始地址1/2,和虚拟屏幕地址);
R/G/BLUT(STN:R,G,B的查看表);DITHMODE(抖动模式);TPAL(TFT:临时调色般);
LCDINTPND/SRCPND/INTMSK(分别为 终端保留/源;编码);LPCSEL(LPC3600 控制器)
(2)前提知识2:
linux 内核的驱动管理机制(四个字,两个层面:平台+驱动,platform+driver)
定义设备资源 ,如:static struct s3c2410_uartcfg smdk2410_uartcfgs[] __initdata ={};
描述资源,定义platform(注意,这里的platform有多种,可以说有多少硬件平台就有多少platform格式);
P+D中在fb的体现就是:
static struct resource s3c_fb_resource,struct platform_device s3c_device_fb;
s3c_fb_driver,struct platform_device;
这里给处的是:/linux-2.6.*/drivers/video/platform_lcd.c中的定义
platform_device结构:
static struct platform_driver platform_lcd_driver = {
.driver = {
.name = "platform-lcd",
.owner = THIS_MODULE,
},
.probe = platform_lcd_probe,
.remove = __devexit_p(platform_lcd_remove),
.suspend = platform_lcd_suspend,
.resume = platform_lcd_resume,
};
在/linux-2.6.*/drivers/video/s3c-fb.c中的初始化例子:
static struct platform_driver s3c_fb_driver = {
.probe = s3c_fb_probe,
.remove = __devexit_p(s3c_fb_remove),
.suspend = s3c_fb_suspend,
.resume = s3c_fb_resume,
.driver = {
.name = "s3c-fb",//注意,驱动名和设备名要对应相同。即s3c_fb_driver和s3c_device_fb的name字段都为s3c-fb
.owner = THIS_MODULE,
},
};
//设备定义
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
//fb资源
//linux/arch/arm/plat-s3c/dev-fb.c
static struct resource s3c_fb_resource[] = {
[0] = {
.start = S3C_PA_FB,
.end = S3C_PA_FB + SZ_16K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD_VSYNC,
.end = IRQ_LCD_VSYNC,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_LCD_FIFO,
.end = IRQ_LCD_FIFO,
.flags = IORESOURCE_IRQ,
},
[3] = {
.start = IRQ_LCD_SYSTEM,
.end = IRQ_LCD_SYSTEM,
.flags = IORESOURCE_IRQ,
},
};
//fb设备
struct platform_device s3c_device_fb = {
.name = "s3c-fb",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_fb_resource),
.resource = s3c_fb_resource,
.dev.dma_mask = &s3c_device_fb.dev.coherent_dma_mask,
.dev.coherent_dma_mask = 0xffffffffUL,
};
内核处理过程:用户定义platform_device并添加到系统、注册(platform_device_register),对应在linux/arch/arm/plat-s3c/init.c中的
arch_initcall;
在来看 linux/arch/arm/mach-s3c2410/mach-smdk2410.c,这里是对整个s3c2410来说的,其中的SMDK是三星微控制器开发包的简称,即:
Samsung MCU Development Kit,其中在初始化 smdk2410_init中添加平台
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
用户定义platform_driver添加到系统、并注册();然后控制权交给内核;
二、这里详细分解:
前提说明:所需观察文件有:
linux/drivers/video/s3c-fb.c
linux/drivers/video/s3c2410fb.c
linux/drivers/video/s3c2410fb.h
参考文件夹:
/linux/arch/arm/mach-s3c2410
/linux/arch/arm/plat-s3c
来看用户自定义部分:
在/linux/drivers/video/s3c2410fb.h中,
P+D的模式,我们先寻找driver的添加和注册部分:
(1)模块初始化
module_init(s3c2410fb_init);
说明:在 linux*/include/linux/init.h中的宏为:
#ifndef MODULE
#define module_init(x) __initcall(x);
#else
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
而:#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) module_init(fn)
即,module_init是真正的驱动模块的入口,那么内核是如何区分不同的驱动呢?它是如何管理通过module_init添加的驱动呢?
来看:
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
即,我们要注册一个MODULE,必然要指定三个参数:level,fn,id;其中level和id由内核管理生成,我们只要注册一个module_init的函数入口即可。
具体信息应查看: /linux*/include/linux/compiler*.h/module.h/kobject.h/kobj_type/kset等;
(1.1)来看s3c2410fb驱动的加载,很简单,只需要
int __init s3c2410fb_init(void)
{
int ret = platform_driver_register(&s3c2410fb_driver);
if (ret == 0)
ret = platform_driver_register(&s3c2412fb_driver);
return ret;
}
(2)完成加载之后呢?我们从驱动结构入手:
static struct platform_driver s3c_fb_driver = {
.probe = s3c_fb_probe,
.remove = __devexit_p(s3c_fb_remove),
.suspend = s3c_fb_suspend,
.resume = s3c_fb_resume,
.driver = {
.name = "s3c-fb",
.owner = THIS_MODULE,
},
};
//一般在注册之后会有一个match匹配的过程,用于判断driver和platform的name是否一致;
在内核驱动匹配成功会,将调用probe函数,一般 probe用于具体的驱动实现。
static int __init s3c2410fb_probe(struct platform_device *pdev)
{
return s3c24xxfb_probe(pdev, DRV_S3C2410);
}
//具体的实现为(具体含义 read the fucking code)
static char driver_name[] = "s3c2410fb";
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;//这里是操作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);//(1) Initialise all LCD-related registers
s3c2410fb_check_var(&fbinfo->var, fbinfo);//(2)Get the video params out of 'var'
ret = s3c2410fb_cpufreq_register(info);
//(3)设置s3c2410fb_cpufreq_transition等,
//s3c2410fb_activate_var!!!!激活控制设备
//s3c2410fb_set_lcdaddr等对控制器和其他LCD设备设置
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:elease_resource(info->mem);kfree(info->mem);dealloc_fb:
platform_set_drvdata(pdev, NULL);
framebuffer_release(fbinfo);return ret;}//(3s3c2410fb_remove,s3c2410fb_suspend,s3c2410fb_resume;