4-2: FIMC驱动的Camera接口分析
接口的关键还是在于fimc_dev.c里的probe函数。probe里面会调用一个函数叫fimc_init_global(),这里面会完成摄像头的分配以及时钟的获取。这个函数的原型如下:
static int fimc_init_global( struct platform_device *pdev )
这个platform_device是内核从平台代码那里传递过来的,里面包含的就是和具体平台相关的信息,其中就应该包含摄像头信息。
函数的实现:
static int fimc_init_global(struct platform_device *pdev)
{
struct fimc_control *ctrl;
struct s3c_platform_fimc *pdata;
//这个结构体就是用来描述一个摄像头的,先不管它里面的内容
//等会儿在分析平台代码的时候可以看到它是如何被填充的
struct s3c_platform_camera *cam;
struct clk *srclk;
int id, i;
//获得平台信息
pdata = to_fimc_plat(&pdev->dev);
id = pdev->id; //id号可能是0,1,2
ctrl = get_fimc_ctrl(id); //获得id号对应的fimc_control结构体指针
/* Registering external camera modules. re-arrange order to be sure */
for (i = 0; i < FIMC_MAXCAMS; i++) {
cam = pdata->camera[i]; //从平台数据取得camera的信息
if (!cam)
continue; // change break to continue by ys
/* WriteBack doesn't need clock setting */
if(cam->id == CAMERA_WB) {
fimc_dev->camera[cam->id] = cam;
break;
}
// 获得时钟源信息
srclk = clk_get(&pdev->dev, cam->srclk_name);
if (IS_ERR(srclk)) {
fimc_err("%s: failed to get mclk source\n", __func__);
return -EINVAL;
}
// 获得camera的时钟信息
/* mclk */
cam->clk = clk_get(&pdev->dev, cam->clk_name);
if (IS_ERR(cam->clk)) {
fimc_err("%s: failed to get mclk source\n", __func__);
return -EINVAL;
}
if (cam->clk->set_parent) {
cam->clk->parent = srclk;
cam->clk->set_parent(cam->clk, srclk);
}
/* Assign camera device to fimc */
fimc_dev->camera[cam->id] = cam; // 将从平台获得的camera分配给全局数据结构
// fimc_dev
}
fimc_dev->initialized = 1;
return 0;
}
可以看到这个函数实际上就是把camera的信息从平台数据那里取过来,然后分配给fimc_dev. fimc_dev定义在fimc.h里面。类型为struct fimc_global,原型如下:
/* global */
struct fimc_global {
struct fimc_control ctrl[FIMC_DEVICES];
struct s3c_platform_camera *camera[FIMC_MAXCAMS];
int initialized;
};
现在我们需要看一下平台代码那里如何描述一个摄像头以及如何把抽象数据结构传递到平台数据里面。
S5PC100 SOC对应的平台代码位于:
arch/arm/mach-s5pc100/mach-smdkc100.c
我们是这样来描述一个camera的:
#ifdef CONFIG_VIDEO_OV9650
/* add by ys for ov9650 */
static struct s3c_platform_camera camera_c = {
.id = CAMERA_PAR_A, /* FIXME */
.type = CAM_TYPE_ITU, /* 2.0M ITU */
.fmt = ITU_601_YCBCR422_8BIT,
.order422 = CAM_ORDER422_8BIT_YCBYCR,
.i2c_busnum = 1,
.info = &camera_info[2],
.pixelformat = V4L2_PIX_FMT_YUYV,
.srclk_name = "dout_mpll",
.clk_name = "sclk_cam",
.clk_rate = 16000000, /* 16MHz */
.line_length = 640, /* 640*480 */
/* default resol for preview kind of thing */
.width = 640,
.height = 480,
.window = {
.left = 0,
.top = 0,
.width = 640,
.height = 480,
},
/* Polarity */
.inv_pclk = 1,
.inv_vsync = 0,
.inv_href = 0,
.inv_hsync = 0,
.initialized = 0,
};
#endif
这里面的信息描述了OV9650相关的所有信息。type代表摄像头是ITU的接口,fmt代表摄像头输出的格式是ITU_601_YCBCR422_8BIT,order422代表YUV三个分量的顺序是YcbCr。这些都和前面的描述相符。另外里面还有时钟源的信息,时钟的大小以及捕捉图像的解析度,这里设置的是640x480(VGA模式),因为经过调试发现OV9650工作在VGA的模式下比较流畅清晰。Polarity代表信号的极性,具体的设置要和摄像头本身的设置一致。
i2c_busnum是I2C总线的总线编号,因为S5PC100一共有两条I2C总线(0和1),我们连在SDA1上,所以i2c_busnum是1。
camera_c是fimc_plat结构体的一个成员:
/* Interface setting */
static struct s3c_platform_fimc fimc_plat = {
.default_cam = CAMERA_PAR_A,
.camera[ 2 ] = &camera_c,
.hw_ver = 0x40,
};
这里会把camera_c赋值给fimc_plat里的camera数组的第三个元素,之所以是第三个是因为Android的原因。这在分析Android的摄像头硬件抽象层时会有解释。
struct s3c_platform_fimc这个结构体其实就是fimc对应的平台数据结构。在平台代码里,会由以下三个函数负责注册:
s3c_fimc0_set_platdata(&fimc_plat);
s3c_fimc1_set_platdata(&fimc_plat);
s3c_fimc2_set_platdata(&fimc_plat);
至于这几个函数如何实现,这里就不分析了,有兴趣可以自己看代码。
也就是说只要平台代码这边我们填充了一个struct s3c_platform_camera类型的结构体,然后把它添加到fimc_plat里面,fimc的驱动就能获得对应的Camera的信息。