一、总述
本驱动是基于三星的s5pv210处理器分析,对于三星平台所有的framebuffer驱动基本都是一样。对应于s5pv210中的内部外设Display Controller (FIMD)模块。
framefuffer驱动是基于字符设备驱动,在使用platform总线封装编写。
二、驱动源码的分布
1、驱动代码的源文件分布:
(1):drivers/video/samsung/s3cfb.c, 驱动主体框架。
(2):drivers/video/samsung/s3cfb_fimd6x.c,里面主要是提供LCD硬件操作的函数。
(3):arch/arm/mach-s5pv210/mach-x210.c,负责提供platform_device,这个文件里面提供了很多的基于platform总线编写的驱动需要的platform_device,mach文件是每一个移植好的内核都会提供这个文件的,例如这里的mach-x210.c文件是九鼎从三星提供的mach文件移植而来的。
(4):arch/arm/plat-s5p/devs.c,为platform_device提供一些硬件描述信息。
2、如何找到入口函数:
(1):依靠经验,在了解了一些驱动之后,发现驱动编写其实是一个模式化的东西。比如通常要先找到module_init,subsyscall,之类的入口。进而进入其相对应的函数分析。
(2):网络查找或论坛提问等。
三、驱动程序分析
前面的led驱动我们从0开始写过一个platform驱动,知道platform驱动主要分为两部分。
一部分是platform_device部分,另一部分是platform_driver部分。
1、首先我们分析platform_device部分
static u64 fb_dma_mask = 0xffffffffUL;
struct platform_device s3c_device_fb = {
.name = "s3cfb",
.id = -1,
.num_resources = ARRAY_SIZE(s3cfb_resource),
.resource = s3cfb_resource,
.dev = {
.dma_mask = &fb_dma_mask,
.coherent_dma_mask = 0xffffffffUL
}
};
首先是定义了一个platform_device的结构体变量,填充了name(用来匹配driver),id(区分相同名称的不同platform_device,-1表示由系统自动分配),resource(表示用到的那些资源,比如寄存器,中断等),dev(dev里面参数很多,这里因为lcd要用dma功能,所以把dma相关的填了)
static struct resource s3cfb_resource[] = {
[0] = {
.start = S5P_PA_LCD,
.end = S5P_PA_LCD + S5P_SZ_LCD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD1,
.end = IRQ_LCD1,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_LCD0,
.end = IRQ_LCD0,
.flags = IORESOURCE_IRQ,
},
};
上面说到的资源,我们在这里主要用到了三个,一个是一段寄存器(这里用的物理地址,在驱动中用动态映射),两个中断。
platform_device这个结构体是所有使用platform框架的驱动最重要的部分。我们这里先是把它添加到一个指针数组中,后面一次添加放在这个只准数组中的所有成员,
static struct platform_device *smdkc110_devices[] __initdata = {
......
#ifdef CONFIG_FB_S3C
&s3c_device_fb,
#endif
......
};
之后再smdkc110_machine_init里面我们先把所有的smdkc110_devices里面的内容添加。
因为lcd的参数比较多,而且支持的种类变化很多,上面填充的参数只是公有的。一些有差异性的参数,三星条件编译的方式以函数方式进行了初始化。
我这里是在smdkc110_machine_init中注册公有的后,调用的。
其中关背光是很简单的,直接把相对应的io配置一下就行了。
#ifdef CONFIG_FB_S3C_EK070TN93
static void smdkv210_backlight_off(void)
{
/* backlight enable pin low level */
s3c_gpio_cfgpin(S5PV210_GPH2(4), S3C_GPIO_OUTPUT);
s3c_gpio_setpull(S5PV210_GPH2(4), S3C_GPIO_PULL_UP);
gpio_set_value(S5PV210_GPH2(4), 0);
}
#endif
而s3cfb_set_platdata函数主要是对具体平台相关的数据初始化和填充,以供后面driver使用。
s3cfb_set_platdata默认传的参数本身已经初始化了个别参数。里面再继续填充没默认初始化的部分。下面是默认初始化的部分。注意这里我们定义了有5个窗口。
static struct s3c_platform_fb ek070tn93_fb_data __initdata = {
.hw_ver = 0x62,
.nr_wins = 5,
.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
.swap = FB_SWAP_WORD | FB_SWAP_HWORD,
.lcd = &ek070tn93,
.cfg_gpio = ek070tn93_cfg_gpio,
.backlight_on = ek070tn93_backlight_on,
.backlight_onoff = ek070tn93_backlight_off,
.reset_lcd = ek070tn93_reset_lcd,
};
其中.lcd里面放置的全部是lcd初始化时序相关的参数。
static struct s3cfb_lcd ek070tn93 = {
.width = S5PV210_LCD_WIDTH,
.height = S5PV210_LCD_HEIGHT,
.bpp = 32,
.freq = 60,
.timing = {
.h_fp = 210,
.h_bp = 38,
.h_sw = 10,
.v_fp = 22,
.v_fpe = 1,
.v_bp = 18,
.v_bpe = 1,
.v_sw = 7,
},
.polarity = {
.rise_vclk = 0,
.inv_hsync = 1,
.inv_vsync = 1,
.inv_vden = 0,
},
};
而配置gpio,开关背光之类的函数就很简单了,不再一一分析。要说明的是这里只是把背光以高低电平的方式输出了,并没有以pwm可调亮度的方式来处理,pwm调光是以另一个模块来注册的,不再这里。
static void ek070tn93_cfg_gpio(struct platform_device *pdev)
{
int i;
for (i = 0; i < 8; i++) {
s3c_gpio_cfgpin(S5PV210_GPF0(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF0(i), S3C_GPIO_PULL_NONE);
}
for (i = 0; i < 8; i++) {
s3c_gpio_cfgpin(S5PV210_GPF1(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF1(i), S3C_GPIO_PULL_NONE);
}
for (i = 0; i < 8; i++) {
s3c_gpio_cfgpin(S5PV210_GPF2(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF2(i), S3C_GPIO_PULL_NONE);
}
for (i = 0; i < 4; i++) {
s3c_gpio_cfgpin(S5PV210_GPF3(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF3(i), S3C_GPIO_PULL_NONE);
}
/* mDNIe SEL: why we shall write 0x2 ? */
writel(0x2, S5P_MDNIE_SEL);
/* drive strength to max */
writel(0xffffffff, S5PV210_GPF0_BASE + 0xc);
writel(0xffffffff, S5PV210_GPF1_BASE + 0xc);
writel(0xffffffff, S5PV210_GPF2_BASE + 0xc);
writel(0x000000ff, S5PV210_GPF3_BASE + 0xc);
}
#define S5PV210_GPD_0_0_TOUT_0 (0x2)
#define S5PV210_GPD_0_1_TOUT_1 (0x2 << 4)
#define S5PV210_GPD_0_2_TOUT_2 (0x2 << 8)
#define S5PV210_GPD_0_3_TOUT_3 (0x2 << 12)
static int ek070tn93_backlight_on(struct platform_device *pdev)
{
/* backlight enable pin */
s3c_gpio_cfgpin(S5PV210_GPH2(4), S3C_GPIO_OUTPUT);
s3c_gpio_setpull(S5PV210_GPH2(4), S3C_GPIO_PULL_UP);
gpio_set_value(S5PV210_GPH2(4), 1);
return 0;
}
static int ek070tn93_backlight_off(struct platform_device *pdev, int onoff)
{
/* backlight enable pin */
s3c_gpio_cfgpin(S5PV210_GPH2(4), S3C_GPIO_OUTPUT);
s3c_gpio_setpull(S5PV210_GPH2(4), S3C_GPIO_PULL_DOWN);
gpio_set_value(S5PV210_GPH2(4), 0);
/* LCD_5V */
s3c_gpio_cfgpin(S5PV210_GPH1(6), S3C_GPIO_OUTPUT);
s3c_gpio_setpull(S5PV210_GPH1(6), S3C_GPIO_PULL_DOWN);
gpio_set_value(S5PV210_GPH1(6), 0);
/* LCD_33 */
s3c_gpio_cfgpin(S5PV210_GPH1(7), S3C_GPIO_OUTPUT);
s3c_gpio_setpull(S5PV210_GPH1(7), S3C_GPIO_PULL_UP);
gpio_set_value(S5PV210_GPH1(7), 1);
return 0;
}
static int ek070tn93_reset_lcd(struct platform_device *pdev)
{
/* LCD_5V */
s3c_gpio_cfgpin(S5PV210_GPH1(6), S3C_GPIO_OUTPUT);
s3c_gpio_setpull(S5PV210_GPH1(6), S3C_GPIO_PULL_UP);
gpio_set_value(S5PV210_GPH1(6), 1);
/* LCD_33 */
s3c_gpio_cfgpin(S5PV210_GPH1(7), S3C_GPIO_OUTPUT);
s3c_gpio_setpull(S5PV210_GPH1(7), S3C_GPIO_PULL_DOWN);
gpio_set_value(S5PV210_GPH1(7), 0);
/* wait a moment */
//mdelay(200);
msleep(300);
return 0;
}
下面就是s3cfb_set_platdata函数的原型,其实如果参数传NULL,自己本身也有一个默认的s3c_platform_fb。但实际我们这里是传了我们自己定义的。
void __init s3cfb_set_platdata(struct s3c_platform_fb *pd)
{
struct s3c_platform_fb *npd;
int i;
if (!pd)
pd = &default_fb_data;
/* 重新申请内存,并把pd内容拷贝到新申请的内存中 */
npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform data\n", __func__);
else {
for (i = 0; i < npd->nr_wins; i++)
npd->nr_buffers[i] = 1; /* 把这5个缓冲都写1表示已经使用 */
/* 设置默认窗口 */
npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS;
/* 设置时钟名称 "sclk_fimd" */
s3cfb_get_clk_name(npd->clk_name);
npd->clk_on = s3cfb_clk_on; /* 填充时钟开关函数,在driver初始化的时候会用到 */
npd->clk_off = s3cfb_clk_off;
/* 下面两个是填充显存的起始地址和大小,我在下面分析 */
/* starting physical address of memory region */
npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1);
/* size of memory region */
npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, 1);
/* 这句很重要是把这边填充的所有内容放到最前面分析的platform_device里面去,因为前面已经把s3c_device_fb的地址添加了,这里放数据,就可以了 */
s3c_device_fb.dev.platform_data = npd;
}
}
下面分析上面的两个显存相关的内容,以为这些参数是定义在一起的,所以我们分析一个就可以了。
dma_addr_t s5p_get_media_memory_bank(int dev_id, int bank)
{
struct s5p_media_device *mdev;
mdev = s5p_get_media_device(dev_id, bank);
if (!mdev) {
printk(KERN_ERR "invalid media device\n");
return 0;
}
if (!mdev->paddr) {
printk(KERN_ERR "no memory for %s\n", mdev->name);
return 0;
}
return mdev->paddr;
}
可以发现,上面这个函数其实是调用s5p_get_media_device来取相关数据结构了。追进去继续分析。
static struct s5p_media_device *s5p_get_media_device(int dev_id, int bank)
{
struct s5p_media_device *mdev = NULL;
int i = 0, found = 0;
if (dev_id < 0)
return NULL;
while (!found && (i < nr_media_devs)) {
mdev = &media_devs[i];
if (mdev->id == dev_id && mdev->bank == bank)
found = 1;
else
i++;
}
if (!found)
mdev = NULL;
return mdev;
}
最终发现,其实是通过media_devs来找的。继续搜索分析。
void s5p_reserve_bootmem(struct s5p_media_device *mdevs, int nr_mdevs)
{
struct s5p_media_device *mdev;
void *virt_mem;
int i;
media_devs = mdevs;
nr_media_devs = nr_mdevs;
for (i = 0; i < nr_media_devs; i++) {
mdev = &media_devs[i];
if (mdev->memsize <= 0)
continue;
if (mdev->paddr)
virt_mem = __alloc_bootmem(mdev->memsize, PAGE_SIZE,
mdev->paddr);
else
virt_mem = __alloc_bootmem(mdev->memsize, PAGE_SIZE,
meminfo.bank[mdev->bank].start);
if (virt_mem != NULL) {
mdev->paddr = virt_to_phys(virt_mem);
} else {
mdev->paddr = (dma_addr_t)NULL;
printk(KERN_INFO "s5p: Failed to reserve system memory\n");
}
printk(KERN_INFO "s5pv210: %lu bytes system memory reserved "
"for %s at 0x%08x\n", (unsigned long) mdev->memsize,
mdev->name, mdev->paddr);
}
}
发现其实它是在上面函数s5p_reserve_bootmem中被设置的。继续查找s5p_reserve_bootmem的调用。
static void __init smdkc110_map_io(void)
{
s5p_init_io(NULL, 0, S5P_VA_CHIPID);
s3c24xx_init_clocks(24000000);
s5pv210_gpiolib_init();
s3c24xx_init_uarts(smdkc110_uartcfgs, ARRAY_SIZE(smdkc110_uartcfgs));
s5p_reserve_bootmem(smdkc110_media_devs, ARRAY_SIZE(smdkc110_media_devs));
#ifdef CONFIG_MTD_ONENAND
s5pc110_device_onenand.name = "s5pc110-onenand";
#endif
#ifdef CONFIG_MTD_NAND
s3c_device_nand.name = "s5pv210-nand";
#endif
s5p_device_rtc.name = "smdkc110-rtc";
}
发现其最终在smdkc110_map_io中被调用,
static struct s5p_media_device smdkc110_media_devs[] = {
[0] = {
.id = S5P_MDEV_MFC,
.name = "mfc",
.bank = 0,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_MFC0,
.paddr = 0,
},
[1] = {
.id = S5P_MDEV_MFC,
.name = "mfc",
.bank = 1,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_MFC1,
.paddr = 0,
},
[2] = {
.id = S5P_MDEV_FIMC0,
.name = "fimc0",
.bank = 1,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_FIMC0,
.paddr = 0,
},
[3] = {
.id = S5P_MDEV_FIMC1,
.name = "fimc1",
.bank = 1,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_FIMC1,
.paddr = 0,
},
[4] = {
.id = S5P_MDEV_FIMC2,
.name = "fimc2",
.bank = 1,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_FIMC2,
.paddr = 0,
},
[5] = {
.id = S5P_MDEV_JPEG,
.name = "jpeg",
.bank = 0,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_JPEG,
.paddr = 0,
},
[6] = {
.id = S5P_MDEV_FIMD,
.name = "fimd",
.bank = 1,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_FIMD,
.paddr = 0,
},
[7] = {
.id = S5P_MDEV_TEXSTREAM,
.name = "texstream",
.bank = 1,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_TEXSTREAM,
.paddr = 0,
},
[8] = {
.id = S5P_MDEV_PMEM_GPU1,
.name = "pmem_gpu1",
.bank = 0, /* OneDRAM */
.memsize = S5PV210_ANDROID_PMEM_MEMSIZE_PMEM_GPU1,
.paddr = 0,
},
[9] = {
.id = S5P_MDEV_G2D,
.name = "g2d",
.bank = 0,
.memsize = S5PV210_VIDEO_SAMSUNG_MEMSIZE_G2D,
.paddr = 0,
},
};
可以看到我们的fimc相关的信息。
至此,platform_device相关的数据就填充完了。只待platform_driver和platform_device的名称匹配上后进行注册安装。
2.接下来我们分析driver部分的内容
driver部分的内容起始部分很好找,只需要找到module_init,它里面的就是入口函数。module_exit,它里面的就是卸载函数。
static int __init s3cfb_register(void)
{
platform_driver_register(&s3cfb_driver);
return 0;
}
static void __exit s3cfb_unregister(void)
{
platform_driver_unregister(&s3cfb_driver);
}
module_init(s3cfb_register);
module_exit(s3cfb_unregister);
我们先分析入口函数
入口函数很简单,利用系统提供的平台驱动模型注册三星平台的驱动。
平台驱动模型函数都是系统提供好的,我们只管使用。我们重点分析三星驱动的实现。
static struct platform_driver s3cfb_driver = {
.probe = s3cfb_probe,
.remove = __devexit_p(s3cfb_remove),
.driver = {
.name = S3CFB_NAME, /* "s3cfb" */
.owner = THIS_MODULE,
},
};
三星驱动实现主要是提供了探针函数和卸载函数。driver里面有个.name ,还记得前面platform_device的dev里面的name吗,装载driver的时候探针函数就用这个名字来做匹配。
接下来我们分析probe函数,这个函数通常是platform_driver里面最最重要的函数。
/* 该函数的入口参数是我们在platform_device那边添加的 */
static int __devinit s3cfb_probe(struct platform_device *pdev)
{
struct s3c_platform_fb *pdata;
struct s3cfb_global *fbdev;
struct resource *res;
int i, j, ret = 0;
/* 申请struct s3cfb_global, */
fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);
if (!fbdev) {
dev_err(&pdev->dev, "failed to allocate for "
"global fb structure\n");
ret = -ENOMEM;
goto err_global;
}
fbdev->dev = &pdev->dev; /* 这一句很有意思,s3cfb_global是通常所谓的driver_data是每个平台自定义的的数据,
它里面的dev是个指针,它先和platform_device里面的dev实体绑定,后面会再次和把自己绑定到
dev里面的platform_data,待卸载的时候通过platform_device就可以轻松找到其里面内容,也可
以轻松free掉上一步为其申请的内存*/
/* 下面数对刚申请的fbdev进行填充 */
fbdev->regulator = regulator_get(&pdev->dev, "pd");
if (!fbdev->regulator) {
dev_err(fbdev->dev, "failed to get regulator\n");
ret = -EINVAL;
goto err_regulator;
}
ret = regulator_enable(fbdev->regulator);
if (ret < 0) {
dev_err(fbdev->dev, "failed to enable regulator\n");
ret = -EINVAL;
goto err_regulator;
}
/* 取出dev里面的paatform_data指针,这个platform_data是我们在platform_device部分的调用的
函数s3cfb_set_platdata的最后一句赋值的 */
pdata = to_fb_plat(&pdev->dev);
if (!pdata) {
dev_err(fbdev->dev, "failed to get platform data\n");
ret = -EINVAL;
goto err_pdata;
}
/* 填充fbdev里面的lcd指针,这里的pdata其实只是为了把device那边传过来的数据传到fbdev里 */
fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd;
/* 配置led用到的gpio */
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev);
/* 设置fb的clk */
if (pdata->clk_on)
pdata->clk_on(pdev, &fbdev->clock);
/* 得到寄存器的起始物理地址和范围 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(fbdev->dev, "failed to get io memory region\n");
ret = -EINVAL;
goto err_io;
}
/* 请求寄存器 */
res = request_mem_region(res->start,
res->end - res->start + 1, pdev->name);
if (!res) {
dev_err(fbdev->dev, "failed to request io memory region\n");
ret = -EINVAL;
goto err_io;
}
/* 动态映射寄存器 */
fbdev->regs = ioremap(res->start, res->end - res->start + 1);
if (!fbdev->regs) {
dev_err(fbdev->dev, "failed to remap io region\n");
ret = -EINVAL;
goto err_mem;
}
/* 利用映射好的寄存器先设置中断 */
s3cfb_set_vsync_interrupt(fbdev, 1);
s3cfb_set_global_interrupt(fbdev, 1);
s3cfb_init_global(fbdev); /* 初始化时钟极性,6个参数,分辨率等 */
/* 申请framebuffer并初始化里面的部分数据,这个函数我们后面重点分析一下 */
if (s3cfb_alloc_framebuffer(fbdev)) {
ret = -ENOMEM;
goto err_alloc;
}
/* 注册申请好的framebuffer */
if (s3cfb_register_framebuffer(fbdev)) {
ret = -EINVAL;
goto err_register;
}
/* 设置时钟,和默认窗口 */
s3cfb_set_clock(fbdev);
s3cfb_set_window(fbdev, pdata->default_win, 1);
/* 这里的打开背光是用高低电平方式打开的 */
s3cfb_display_on(fbdev);
/* 还记得resource吗,除了寄存器,还有两个中断,这里先得到中断,然后向系统申请中断 */
fbdev->irq = platform_get_irq(pdev, 0);
if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED,
pdev->name, fbdev)) {
dev_err(fbdev->dev, "request_irq failed\n");
ret = -EINVAL;
goto err_irq;
}
#ifdef CONFIG_FB_S3C_LCD_INIT
if (pdata->backlight_on)
pdata->backlight_on(pdev);
if (!bootloaderfb && pdata->reset_lcd)
pdata->reset_lcd(pdev);
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
fbdev->early_suspend.suspend = s3cfb_early_suspend;
fbdev->early_suspend.resume = s3cfb_late_resume;
fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;
register_early_suspend(&fbdev->early_suspend);
#endif
/* 这里是创建sys文件系统里面framebuffer相关的内容,而sysfilesystem是驱动和应用的桥梁,可以看到是在设备初始化完毕后才创建的,里面的open,write就是在本文件实现的通过s3cfb_init_fbinfo绑定在 s3cfb_global里面的fb_info里面*/
ret = device_create_file(&(pdev->dev), &dev_attr_win_power);
if (ret < 0)
dev_err(fbdev->dev, "failed to add sysfs entries\n");
dev_info(fbdev->dev, "registered successfully\n");
#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
/* 打印logo信息 */
if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
printk("Start display and show logo\n");
/* Start display and show logo on boot */
fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
}
#endif
mdelay(100);
/* 开背光 */
if (pdata->backlight_on)
pdata->backlight_on(pdev);
return 0;
err_irq:
s3cfb_display_off(fbdev);
s3cfb_set_window(fbdev, pdata->default_win, 0);
for (i = pdata->default_win;
i < pdata->nr_wins + pdata->default_win; i++) {
j = i % pdata->nr_wins;
unregister_framebuffer(fbdev->fb[j]);
}
err_register:
for (i = 0; i < pdata->nr_wins; i++) {
if (i == pdata->default_win)
s3cfb_unmap_default_video_memory(fbdev->fb[i]);
framebuffer_release(fbdev->fb[i]);
}
kfree(fbdev->fb);
err_alloc:
iounmap(fbdev->regs);
err_mem:
release_mem_region(res->start,
res->end - res->start + 1);
err_io:
pdata->clk_off(pdev, &fbdev->clock);
err_pdata:
regulator_disable(fbdev->regulator);
err_regulator:
kfree(fbdev);
err_global:
return ret;
}
接下来我们看一下上面说的要重点讲的s3cfb_alloc_framebuffer
可以看到下面这个函数主要是申请了framebuffer相关信息,并放到s3cfb_global里面,同时调用s3cfb_init_fbinfo初始化了一些参数,并绑定了s3cfb_global和platform_device(在下面分析)
static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
int ret, i;
/* 先申请nr个指针,下面用来存nr个fb的指针,我们是五个 */
ctrl->fb = kmalloc(pdata->nr_wins *
sizeof(*(ctrl->fb)), GFP_KERNEL);
if (!ctrl->fb) {
dev_err(ctrl->dev, "not enough memory\n");
ret = -ENOMEM;
goto err_alloc;
}
/* 申请nr个fb_info */
for (i = 0; i < pdata->nr_wins; i++) {
ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb),
ctrl->dev);
if (!ctrl->fb[i]) {
dev_err(ctrl->dev, "not enough memory\n");
ret = -ENOMEM;
goto err_alloc_fb;
}
/* 为申请好的每个fb初始化,就是在这个函数里面绑定的s3cfb_global和platform_device */
s3cfb_init_fbinfo(ctrl, i);
if (i == pdata->default_win) {
/* 为默认的窗口申请dma内存,虽然是虚拟内存,但使用了dma所以物理上也必须是连续的 */
if (s3cfb_map_video_memory(ctrl->fb[i])) {
dev_err(ctrl->dev,
"failed to map video memory "
"for default window (%d)\n", i);
ret = -ENOMEM;
goto err_map_video_mem;
}
}
}
return 0;
err_alloc_fb:
while (--i >= 0) {
if (i == pdata->default_win)
s3cfb_unmap_default_video_memory(ctrl->fb[i]);
err_map_video_mem:
framebuffer_release(ctrl->fb[i]);
}
kfree(ctrl->fb);
err_alloc:
return ret;
}
下面就是这个利用这个宏to_platform_device,传入device,返回platform_device。
之后利用platform_set_drvdata宏把s3cfb_global放入platform_device->dev->p->driver_data里面。完成绑定。
static void s3cfb_init_fbinfo(struct s3cfb_global *ctrl, int id)
{
struct fb_info *fb = ctrl->fb[id]; /* 取出第id个fb的指针 */
struct fb_fix_screeninfo *fix = &fb->fix;
struct fb_var_screeninfo *var = &fb->var;
struct s3cfb_window *win = fb->par;
struct s3cfb_alpha *alpha = &win->alpha;
struct s3cfb_lcd *lcd = ctrl->lcd;
struct s3cfb_lcd_timing *timing = &lcd->timing;
memset(win, 0, sizeof(*win));
/* 该句很重要,是把在probe函数开始申请的struct s3cfb_global结构体放到,platform_devive里面的dev里面的p里面的driver_data里
此后,可以直接通过platform_devive直接找到driver_data,卸载该驱动也方便free掉 */
platform_set_drvdata(to_platform_device(ctrl->dev), ctrl);
strcpy(fix->id, S3CFB_NAME);
/* 后面就是填充s3cfb_global参数 */
win->id = id;
win->path = DATA_PATH_DMA;
win->dma_burst = 16;
alpha->mode = PLANE_BLENDING;
fb->fbops = &s3cfb_ops;
fb->flags = FBINFO_FLAG_DEFAULT;
fb->pseudo_palette = &win->pseudo_pal;
#if (CONFIG_FB_S3C_NR_BUFFERS != 1)
fix->xpanstep = 2;
fix->ypanstep = 1;
#else
fix->xpanstep = 0;
fix->ypanstep = 0;
#endif
fix->type = FB_TYPE_PACKED_PIXELS;
fix->accel = FB_ACCEL_NONE;
fix->visual = FB_VISUAL_TRUECOLOR;
var->xres = lcd->width;
var->yres = lcd->height;
#if defined(CONFIG_FB_S3C_VIRTUAL)
var->xres_virtual = CONFIG_FB_S3C_X_VRES;
var->yres_virtual = CONFIG_FB_S3C_Y_VRES * CONFIG_FB_S3C_NR_BUFFERS;
#else
var->xres_virtual = var->xres;
var->yres_virtual = var->yres * CONFIG_FB_S3C_NR_BUFFERS;
#endif
var->bits_per_pixel = 32;
var->xoffset = 0;
var->yoffset = 0;
var->width = lcd->p_width;
var->height = lcd->p_height;
var->transp.length = 0;
fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
fix->smem_len = fix->line_length * var->yres_virtual;
var->nonstd = 0;
var->activate = FB_ACTIVATE_NOW;
var->vmode = FB_VMODE_NONINTERLACED;
var->hsync_len = timing->h_sw;
var->vsync_len = timing->v_sw;
var->left_margin = timing->h_fp;
var->right_margin = timing->h_bp;
var->upper_margin = timing->v_fp;
var->lower_margin = timing->v_bp;
var->pixclock = lcd->freq * (var->left_margin + var->right_margin +
var->hsync_len + var->xres) *
(var->upper_margin + var->lower_margin +
var->vsync_len + var->yres);
#if defined(CONFIG_FB_S3C_LTE480WV)
/* LTE480WV LCD Device tunning.
* To avoid LTE480WV LCD flickering
*/
var->pixclock *= 2;
#endif
dev_dbg(ctrl->dev, "pixclock: %d\n", var->pixclock);
s3cfb_set_bitfield(var);
s3cfb_set_alpha_info(var, win);
}
接下来我们看一下remove函数,起始它和probe函数的分析刚好相反,即把后注册的先注销,后申请的内存先注销。
static int __devexit s3cfb_remove(struct platform_device *pdev)
{
/* 还记得s3c_platform_fb在那里和platform_device绑定的吗,platform_device里面调用 */
struct s3c_platform_fb *pdata = to_fb_plat(&pdev->dev);
/* 还记得s3cfb_global在那里申请和platform_device绑定的吗,peobe的入口申请,在
s3cfb_alloc_framebuffer里面的s3cfb_init_fbinfo*/
struct s3cfb_global *fbdev = platform_get_drvdata(pdev);
struct s3cfb_window *win;
struct resource *res;
struct fb_info *fb;
int i;
/* 先移除sys文件系统 */
device_remove_file(&(pdev->dev), &dev_attr_win_power);
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&fbdev->early_suspend);
#endif
/* 释放申请的中断 */
free_irq(fbdev->irq, fbdev);
iounmap(fbdev->regs); /* 释放申请的寄存器 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res)
release_mem_region(res->start, /* 释放申请的寄存器空间 */
res->end - res->start + 1);
pdata->clk_off(pdev, &fbdev->clock); /* 关时钟 */
for (i = 0; i < pdata->nr_wins; i++) {
fb = fbdev->fb[i];
if (fb) {
win = fb->par;
if (win->id == pdata->default_win)
s3cfb_unmap_default_video_memory(fb); /* 还记得s3cfb_alloc_framebuffer中申请的吗 */
else
s3cfb_unmap_video_memory(fb);
s3cfb_set_buffer_address(fbdev, i);
/* 三星工程师真tm懒,注册的时候用自己名字封装一下s3cfb_register_framebuffer,注销就直接用系统提供的 */
framebuffer_release(fb);
}
}
/* probe最前面开了整流器,现在关了 */
regulator_disable(fbdev->regulator);
kfree(fbdev->fb); /* 这个还记得在哪里申请的吗,s3cfb_alloc_framebuffer函数的最前面我们总共有5个fb,所以就申请了五个指针,用来存放五个fb的地址,里面存放的fb在上面的循环中已经release了 */
kfree(fbdev); /* free掉刚进probe申请的s3cfb_global */
return 0;
}
编写了一个led的和分析了一个platform总线的内容了。
下一节,总结一下platform总线的编写流程和几个重要的函数接口和宏的实现。