三星framebuffer驱动代码分析

一、总述

本驱动是基于三星的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总线的编写流程和几个重要的函数接口和宏的实现。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/81124536