基於Linux 3.0.8 Samsung FIMC(S5PV210) 的攝像頭驅動框架解讀(一)

作者:咕唧咕唧liukun321

來自:http://blog.csdn.net/liukun321


FIMC這個名字應該是從S5PC1x0開始出現的,在s5pv210裏面的定義是攝像頭接口,但是它同樣具有圖像數據顏色空間轉換的作用。而exynos4412對它的定義看起來更清晰些,攝像頭接口被定義爲FIMC-LITE 。顏色空間轉換的硬件結構被定義爲FIMC-IS。不多說了,我們先來看看Linux3.0.8 三星的BSP包中與fimc驅動相關的文件。



上面的源碼文件組成了整個fimc的驅動框架。通過.c文件的命名也大致可以猜測到FIMC的幾個用途:

 

1、Capture ,Camera Interface 用於控制Camera,及m2m操作

2、Output,這個用途可以簡單看成:只使用了FIMC的m2m功能,這裏fimc實際上就成了一個帶有顏色空間轉換功能的高速DMA。

3、Overlay,比如Android 的Overlay就依賴了FIMC的這個功能,可以簡單把它看作是個m2fb,當然實質上還是m2m。

 

清楚FIMC的大致用途了。再來說說,每個C文件在FIMC驅動框架中扮演了何種角色:

csis.c文件,用於MIPI 接口的攝像頭設備,這裏不多說什麼了。

 

fimc_dev.c 是驅動中對FIMC硬件設備最高層的抽象,這在後面會詳細介紹。

 

fimc_v4l2.c  linux驅動中 ,將fimc 設備的功能操作接口(Capture,output,Overlay),用v4l2框架封裝。在應用層用過攝像頭設備,或在應用層使用FMIC設備完成過m2m操作的朋友應該都清楚,fimc經層層封裝後最終暴露給用戶空間的是v4l2 標準接口的設備文件 videoX。 這裏面也引出了一個我們應該關注的問題:Fimc設備在軟件層上是如何同攝像頭設備關聯的。

 

fimc_capture.c  實現對camera Interface 的控制操作,它實現的基礎依賴硬件相關的攝像頭驅動(eg.ov965X.c  / ov5642.c 等)。 並且提供以下函數接口,由fimc_v4l2.c文件進一步封裝

int fimc_g_parm(struct file *file, void*fh, struct v4l2_streamparm *a)

int fimc_s_parm(struct file *file, void*fh, struct v4l2_streamparm *a)

intfimc_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *qc)

intfimc_querymenu(struct file *file, void *fh, struct v4l2_querymenu *qm)

intfimc_enum_input(struct file *file, void *fh, struct v4l2_input *inp)

intfimc_g_input(struct file *file, void *fh, unsigned int *i)

intfimc_release_subdev(struct fimc_control *ctrl)

intfimc_s_input(struct file *file, void *fh, unsigned int i)

intfimc_enum_fmt_vid_capture(struct file *file, void *fh,struct v4l2_fmtdesc *f)

intfimc_g_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f)

intfimc_s_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f)

intfimc_try_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f)

intfimc_reqbufs_capture(void *fh, struct v4l2_requestbuffers *b)

intfimc_querybuf_capture(void *fh, struct v4l2_buffer *b)

intfimc_g_ctrl_capture(void *fh, struct v4l2_control *c)

intfimc_s_ctrl_capture(void *fh, struct v4l2_control *c)

intfimc_s_ext_ctrls_capture(void *fh, struct v4l2_ext_controls *c)

intfimc_cropcap_capture(void *fh, struct v4l2_cropcap *a)

intfimc_g_crop_capture(void *fh, struct v4l2_crop *a)

intfimc_s_crop_capture(void *fh, struct v4l2_crop *a)

intfimc_start_capture(struct fimc_control *ctrl)

intfimc_stop_capture(struct fimc_control *ctrl)

intfimc_streamon_capture(void *fh)

intfimc_streamoff_capture(void *fh)

intfimc_qbuf_capture(void *fh, struct v4l2_buffer *b)

intfimc_dqbuf_capture(void *fh, struct v4l2_buffer *b)

 

 

fimc_output.c  實現fimc m2m操作,需要用FIMC實現硬件顏色空間轉換的時候,這個文件裏的函數就派上作用了,另外在fimc 用於Capture 和 overlay 過程本質上也包含m2m操作。因此除了提供功能函數接口,由fimc_v4l2.c文件進一步封裝。另外還提供了一些功能函數供fimc_dev.c調用,比如用於設置一個m2m過程的srcAddr(源地址) 和 dstAddr(目的地址)。這部分接口太多就不貼出來了。

 

fimc_overlay.c  實現fimc overlay操作。同樣提供函數接口,由fimc_v4l2.c文件進一步封裝。

 

fimc_regs.c  Fimc硬件相關操作,基本寄存器配置等。這個文件提供函數接口供fimc_capture.cfimc_output.cfimc_overlay.c調用。

 

通過剛纔的分析,可以總結出下面的源碼結構圖:


好了,框架有了,再來看源碼就輕鬆多了

接下來,先來看看FIMC設備的註冊過程。以FIMC-0爲例,說說/dev/video0 這個設備文件是怎麼出來的。

先看幾個關鍵結構:

首先是 s3c_platform_fimcfimc_plat_lsi;也就是抽象fimc模塊的數據結構,fimc_plat_lsi還包含了一個.camera成員。該結構初始化如下

static struct s3c_platform_fimc  fimc_plat_lsi = {
	.srclk_name	= "mout_mpll",
	.clk_name	= "sclk_fimc",
	.lclk_name	= "fimc",
	.clk_rate	= 166750000,
#if defined(CONFIG_VIDEO_S5K4EA)
	.default_cam	= CAMERA_CSI_C,
#else
#ifdef CAM_ITU_CH_A
	.default_cam	= CAMERA_PAR_A,
#else
	.default_cam	= CAMERA_PAR_B,
#endif
#endif
	.camera		= {
#ifdef CONFIG_VIDEO_S5K4ECGX
			&s5k4ecgx,
#endif
#ifdef CONFIG_VIDEO_S5KA3DFX
			&s5ka3dfx,
#endif
#ifdef CONFIG_VIDEO_S5K4BA
			&s5k4ba,
#endif
#ifdef CONFIG_VIDEO_S5K4EA
			&s5k4ea,
#endif
#ifdef CONFIG_VIDEO_TVP5150
			&tvp5150,
#endif
#ifdef CONFIG_VIDEO_OV9650
			&ov9650,
#endif
	},
	.hw_ver		= 0x43,
};

可以看到在s3c_platform_fimc中有一個camera成員。這裏重點看一下ov9650.展開ov9650


static struct s3c_platform_camera ov9650 = {
	#ifdef CAM_ITU_CH_A
	.id		= CAMERA_PAR_A,
	#else
	.id		= CAMERA_PAR_B,
	#endif
	.type		= CAM_TYPE_ITU,
	.fmt		= ITU_601_YCBCR422_8BIT,
	.order422	= CAM_ORDER422_8BIT_YCBYCR,
	.i2c_busnum	= 0,
	.info		= &ov9650_i2c_info,
	.pixelformat	= V4L2_PIX_FMT_YUYV,
	.srclk_name	= "mout_mpll",
	/* .srclk_name	= "xusbxti", */
	.clk_name	= "sclk_cam1",
	.clk_rate	= 40000000,
	.line_length	= 1920,
	.width		= 1280,
	.height		= 1024,
	.window		= {
		.left	= 0,
		.top	= 0,
		.width	= 1280,
		.height	= 1024,
	},

	/* Polarity */
	.inv_pclk	= 1,
	.inv_vsync	= 1,
	.inv_href	= 0,
	.inv_hsync	= 0,

	.initialized	= 0,
	.cam_power	= ov9650_power_en,
};

這個結構體,實現了對ov9650攝像頭硬件結構的抽象。定義了攝像頭的關鍵參數和基本特性。

因爲fimc設備在linux3.0.8內核中作爲一個平臺設備加載,而上面提到的s3c_platform_fimcfimc_plat_lsi僅是fimc的抽象數據而非設備。這就需要將抽象fimc的結構體作爲fimc  platform_device 的一個私有數據。所以就有了下面的過程。s3c_platform_fimcfimc_plat_lsi 結構在板級設備初始化XXX_machine_init(void) 過程作爲s3c_fimc0_set_platdata 的實參傳入。之後fimc_plat_lsi就成爲了fimc設備的platform_data


s3c_fimc0_set_platdata(&fimc_plat_lsi);
s3c_fimc1_set_platdata(&fimc_plat_lsi);
s3c_fimc2_set_platdata(&fimc_plat_lsi);

以s3c_fimc0_set_platdata爲例展開


void __init s3c_fimc0_set_platdata(struct s3c_platform_fimc *pd)
{
	struct s3c_platform_fimc *npd;

	if (!pd)
		pd = &default_fimc0_data;

	npd = kmemdup(pd, sizeof(struct s3c_platform_fimc), GFP_KERNEL);
	if (!npd)
		printk(KERN_ERR "%s: no memory for platform data\n", __func__);
	else {
		if (!npd->cfg_gpio)
			npd->cfg_gpio = s3c_fimc0_cfg_gpio;

		if (!npd->clk_on)
			npd->clk_on = s3c_fimc_clk_on;

		if (!npd->clk_off)
			npd->clk_off = s3c_fimc_clk_off;

		npd->hw_ver = 0x45;

		/* starting physical address of memory region */
		npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMC0, 1);
		/* size of memory region */
		npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMC0, 1);

		s3c_device_fimc0.dev.platform_data = npd;
	}
}

最後一句是關鍵 s3c_device_fimc0.dev.platform_data = npd; 

看一下s3c_device_fimc0定義:


struct platform_device s3c_device_fimc0 = {
	.name		= "s3c-fimc",
	.id		= 0,
	.num_resources	= ARRAY_SIZE(s3c_fimc0_resource),
	.resource	= s3c_fimc0_resource,
};

fimc的抽象數據,則作爲它的私有數據被包含進了s3c_device_fimc0這個結構中。到這裏才完成了FIMC平臺設備的最終定義。這個平臺設備的定義s3c_device_fimc0又被添加到了整個硬件平臺的 platform_device 列表中,最終在XXX_machine_init(void) 函數中調用platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));  完成所有platform_device 的註冊:


static struct platform_device *mini210_devices[] __initdata = {
	&s3c_device_adc,
	&s3c_device_cfcon,
	&s3c_device_nand,
	。。。
	&s3c_device_fb,
	&mini210_lcd_dev,
#ifdef CONFIG_VIDEO_FIMC
	&s3c_device_fimc0,
	&s3c_device_fimc1,
	&s3c_device_fimc2,
}

platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));

platform_device 被加載後,等待與之匹配的platform_driver。若此時fimc driver 的驅動模塊被加載。這個時候,fimc_dev.c文件裏的static int __devinit fimc_probe(structplatform_device *pdev) 函數上場了。


static int __devinit fimc_probe(struct platform_device *pdev)
{
	struct s3c_platform_fimc *pdata;
	struct fimc_control *ctrl;
	struct clk *srclk;
	int ret;
	if (!fimc_dev) {
		fimc_dev = kzalloc(sizeof(*fimc_dev), GFP_KERNEL);
		if (!fimc_dev) {
			dev_err(&pdev->dev, "%s: not enough memory\n",
				__func__);
			return -ENOMEM;
		}
	}

	ctrl = fimc_register_controller(pdev);
	if (!ctrl) {
		printk(KERN_ERR "%s: cannot register fimc\n", __func__);
		goto err_alloc;
	}

	pdata = to_fimc_plat(&pdev->dev);
	if (pdata->cfg_gpio)
		pdata->cfg_gpio(pdev);

#ifdef REGULATOR_FIMC
	/* Get fimc power domain regulator */
	ctrl->regulator = regulator_get(&pdev->dev, "pd");
	if (IS_ERR(ctrl->regulator)) {
		fimc_err("%s: failed to get resource %s\n",
				__func__, "s3c-fimc");
		return PTR_ERR(ctrl->regulator);
	}
#endif //REGULATOR_FIMC
	/* fimc source clock */
	srclk = clk_get(&pdev->dev, pdata->srclk_name);
	if (IS_ERR(srclk)) {
		fimc_err("%s: failed to get source clock of fimc\n",
				__func__);
		goto err_v4l2;
	}

	/* fimc clock */
	ctrl->clk = clk_get(&pdev->dev, pdata->clk_name);
	if (IS_ERR(ctrl->clk)) {
		fimc_err("%s: failed to get fimc clock source\n",
			__func__);
		goto err_v4l2;
	}

	/* set parent for mclk */
	clk_set_parent(ctrl->clk, srclk);

	/* set rate for mclk */
	clk_set_rate(ctrl->clk, pdata->clk_rate);

	/* V4L2 device-subdev registration */
	ret = v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
	if (ret) {
		fimc_err("%s: v4l2 device register failed\n", __func__);
		goto err_fimc;
	}

	/* things to initialize once */
	if (!fimc_dev->initialized) {
		ret = fimc_init_global(pdev);
		if (ret)
			goto err_v4l2;
	}

	/* video device register */
	ret = video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);
	if (ret) {
		fimc_err("%s: cannot register video driver\n", __func__);
		goto err_v4l2;
	}

	video_set_drvdata(ctrl->vd, ctrl);

	ret = device_create_file(&(pdev->dev), &dev_attr_log_level);
	if (ret < 0) {
		fimc_err("failed to add sysfs entries\n");
		goto err_global;
	}
	printk(KERN_INFO "FIMC%d registered successfully\n", ctrl->id);

	return 0;

err_global:
	video_unregister_device(ctrl->vd);

err_v4l2:
	v4l2_device_unregister(&ctrl->v4l2_dev);

err_fimc:
	fimc_unregister_controller(pdev);

err_alloc:
	kfree(fimc_dev);
	return -EINVAL;

}

在fimc_probe函數中有這麼一段

if(!fimc_dev->initialized) {
                   ret = fimc_init_global(pdev);
                   if (ret)
                            goto err_v4l2;
         }


這段代碼執行過程:首先判斷fimc是否已經被初始化完成(此時FIMC是忙狀態的),如果沒有被初始化,則執行fimc_init_global(pdev);函數,它的作用是先判斷平臺數據中是否初始化了攝像頭結構(即前面提到的.camera成員),從平臺數據中獲得攝像頭的時鐘頻率並將平臺數據中內嵌的s3c_platform_camera結構數據保存到該驅動模塊全局的fimc_dev中,感興趣的朋友可以展開這個函數看一下,這裏就不再貼出來了。

 

緊接着這段代碼還執行了兩個非常關鍵的過程:

ret= v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
         if (ret) {
                   fimc_err("%s: v4l2device register failed\n", __func__);
                   goto err_fimc;
         }


這個函數裏的核心完成了對v4l2_dev->subdev鏈表頭的初始化,並將ctrl->v4l2_dev關聯到pdev->dev結構的私有數據的driver_data成員中(即完成了pdev->dev->p->driver_data= ctrl->v4l2_dev; ),也就是實現了v4l2_dev向內核結構註冊的過程。

 

ret= video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);
         if (ret) {
                   fimc_err("%s: cannotregister video driver\n", __func__);
                   goto err_v4l2;
         }
 
         video_set_drvdata(ctrl->vd, ctrl);
 
         ret = device_create_file(&(pdev->dev),&dev_attr_log_level);


上面的過程完成了對video_device 設備的註冊,並且在sys 目錄下生成了對應的屬性文件。如果系統中移植有mdev,將會生成對應設備節點/dev/videoX。

 

其實到目前爲止,只完成了fimc設備主要數據結構的初始化和註冊,幾乎沒有操作fimc或攝像頭的硬件寄存器。也沒有完成FIMC驅動和攝像頭的驅動模塊的軟件關聯。我們是如何做到僅操作fimc的設備節點/dev/videoX就能控制攝像頭設備的效果呢?下回分解吧。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章