4-3 摄像头的初始化流程及v4l2子设备驱动
这个问题弄清楚了以后下面就来看获得Camera信息以后如何做后续的处理:
在fimc_init_global调用结束之后我们获得了OV9650的信息,之后在probe函数里面就会继续调用一个函数:fimc_configure_subdev().
这个函数的实现如下:
/*
* Assign v4l2 device and subdev to fimc
* it is called per every fimc ctrl registering
*/
static int fimc_configure_subdev(struct platform_device *pdev, int id)
{
struct s3c_platform_fimc *pdata;
struct s3c_platform_camera *cam;
struct i2c_adapter *i2c_adap;
struct i2c_board_info *i2c_info;
struct v4l2_subdev *sd;
struct fimc_control *ctrl;
unsigned short addr;
char *name;
int cfg;
ctrl = get_fimc_ctrl(id);
pdata = to_fimc_plat(&pdev->dev);
cam = pdata->camera[id];
/* Subdev registration */
if (cam) {
i2c_adap = i2c_get_adapter(cam->i2c_busnum);
// 省略结果判断语句
i2c_info = cam->info;
// 省略结果判断语句
name = i2c_info->type;
// 省略结果判断语句
addr = i2c_info->addr;
// 省略结果判断语句
/*
* NOTE: first time subdev being registered,
* s_config is called and try to initialize subdev device
* but in this point, we are not giving MCLK and power to subdev
* so nothing happens but pass platform data through
*/
sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,name, i2c_info, &addr);
if (!sd) {
fimc_err("%s: v4l2 subdev board registering failed\n",
__func__);
}
/* Assign camera device to fimc */
fimc_dev->camera[cam->id] = cam;
/* Assign subdev to proper camera device pointer */
fimc_dev->camera[cam->id]->sd = sd;
return 0;
}
这里面涉及到一些内核中I2C总线的操作,因为OV9650本身从总线的角度来讲可以看做是一个I2C的设备。实际上刚才分析平台数据的时候忽略了一个重要的信息,就是camera_c里面有一个成员是info,类型为struct i2c_board_info,熟悉Linux内核I2C驱动架构就应该知道i2c_board_info是用来描述一个I2C设备。平台代码里是这样定义的:
/* Add by ys for ov9650 sensor */
static struct ov9650_platform_data ov9650 = {
.default_width = 640,
.default_height = 480,
/* ITU-R BT 601: 16 */
.pixelformat = V4L2_PIX_FMT_YUYV, // 16 YUV 4:2:2
.freq = 22000000, // 24MHz
.is_mipi = 0, // Don't use MIPI-CSI-2
};
static struct i2c_board_info camera_info[] = {
{
I2C_BOARD_INFO("S5K4BA", 0x2d),
.platform_data = &s5k4ba,
},
{
I2C_BOARD_INFO("S5K6AA", 0x3c),
.platform_data = &s5k6aa,
},
/* add by ys for ov9650 sensor */
{
I2C_BOARD_INFO("OV9650", 0x60 >> 1),
.platform_data = &ov9650,
},
};
数组的第三个元素就是描述OV9650的,0x60就是OV9650作为从设备的设备地址,之所以这里要右移一位是因为控制器驱动还会把这个地址左移一位。
我们再回到fimc_configure_subdev这个函数里面,它首先从平台数据那里取得camera,如果不为空,就从上面的i2c_board_info里面取得i2c相应的信息,比如适配器(adapter),i2c_info, name, addr等信息,这里就不详细讲解i2c驱动的框架层了,这部分单独去学习难度并不大。总之获得所有需要的信息之后,就会调用v4l2_i2c_new_subdev_board()这个函数,这函数是整个Camera驱动的关键。简单来说,这个函数会做两件事情,第一件事情是根据传递过来的i2c设备的相关信息向I2C总线添加一个i2c设备,然后向v4l2子系统注册一个v4l2子设备(sub-dev)。也就是说OV9650既是一个I2C设备,也是一个V4L的子设备,这样就把i2c和v4l2联系起来了。
在具体分析v4l2_i2c_new_subdev_board()这个函数里具体都作了哪些工作之前,我们注意在这个函数调用之前有一段注释很有用,这段注释如下:
912 /*
913 * NOTE: first time subdev being registered,
914 * s_config is called and try to initialize subdev device
915 * but in this point, we are not giving MCLK and power to subdev
916 * so nothing happens but pass platform data through
917 */
918 sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
919 name, i2c_info, &addr);
意思就是说当v4l2的subdev(子设备)第一次被注册时,s_config这个函数会被调用并且尝试去初始化这个subdev。但是在这个时候,我们不会给这个subdev供给时钟(MCLK)和电源。所以这时除了把平台数据传递过去,其它什么事情都没有做。这里的关键是s_config函数,这个函数其实是v4l2-subdev对应的一个回调函数,从面向对象的角度来看,也就是内核中每一个抽象成v4l2-subdev的对象所对应的一个方法。但是在fimc的驱动看不到有什么地方去实现这个方法,这是因为这里摄像头控制器的驱动和具体某一种摄像头的驱动是分离的,毕竟摄像头可以有很多种,但是控制器就一个,这样可以分层实现,结构非常清晰而且易于扩展。