data/attach/1907/q2doql57xy4q6pflsnxvgd84urvnol0u.jpgdata/attach/1907/5kd6ee7gc5kn4w3quypdbk8xg9trxfj9.jpgdata/attach/1907/5i2ufyt4l6ixzlmo1n950y11g4paa6uv.jpgdata/attach/1907/jho3sk6lomerql5aomhx2wzadfmpjhvv.jpg
对于IPU在内核驱动中的执行过程,需要通过应用程序的函数调用来一步一步追踪,下面就根据mxc_v4l2_capture.c这个应用程序来分析。经过此轮分析,应该对IPU内部那些函数都有一个大致的认识。
1.
应用程序中的参数
g_in_width= 352, g_in_height = 288, g_out_width = 352, g_out_height = 288,g_rotate = 0, g_capture_count = 50, g_camera_framerate
= 30,
这些信息能够打印出来:
in_width= 352, in_height = 288
out_width= 352, out_height = 288
top= 0, left = 0
2. open函数
fd_v4l= open(g_v4l_device, O_RDWR, 0)
打开设备/dev/video0
应用程序中调用open函数最终就会调用到mxc_v4l2_capture.c中的mxc_v4l_open函数,在这个mxc_v4l_open函数中,最重要的是从cam->sensor中获取信息,但是一直不理解cam->sensor这个参数在哪初始化设置的,以及mxc_v4l_open函数中设置了crop的值,同时这些值也在init_camera_struct函数中进行了设置,到底哪个设置起作用?最后终于缕清这个思路了。
关于这个流程,参见《master和slave的匹配过程》这个文档。
在这个mxc_v4l_open函数中,通过调用vidioc_int_g_ifparm(cam->sensor,&ifparm)和vidioc_int_g_fmt_cap(cam->sensor,&cam_fmt)函数来获取slave设备的一些信息来分别设置csi_param和cam->crop_xxx的值。这个cam->sensor所对应的就是ov5640这个设备,即ov5640_int_device结构体。这两个函数分别会调用到ov5640.c中的ioctl_g_ifparm函数和ioctl_g_fmt_cap函数,在ioctl_g_ifparm函数中会对传进来的&ifparm参数进行填充,在ioctl_g_fmt_cap函数中会对传进来的&cam_fmt参数进行填充。然后根据&ifparm和&cam_fmt来填充cam_data结构体。
另外需要注意的是,在mxc_v4l_open函数中,会调用到prp_enc_select(cam)这个函数,这个函数在ipu_prp_enc.c中定义,它指定了cam_data结构体中几个函数指针所指向的函数,这几个函数在streamon的时候需要用到。这个函数是从mxc_v4l2_capture.c跳转到ipu_prp_enc.c文件中的入口函数。
在mxc_v4l2_open函数中,调用了ipu_csi_set_window_size,ipu_csi_set_window_pos和ipu_csi_init_interface三个函数:
2.1 ipu_csi_set_window_size函数
ipu_csi_set_window_size函数设置了CSI_ACT_FRM_SIZE寄存器,
ipu_csi_set_window_size(cam->ipu,cam->crop_current.width,cam->crop_current.height,cam->csi);
void ipu_csi_set_window_size(struct ipu_soc *ipu, uint32_t width, uint32_t height, uint32_t csi)
{
_ipu_get(ipu);
mutex_lock(&ipu->mutex_lock);
ipu_csi_write(ipu, csi, (width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE);
mutex_unlock(&ipu->mutex_lock);
_ipu_put(ipu);
}
在这个ipu_csi_set_window_size函数中,cam->crop_current.width=
640, cam->crop_current.height = 480, 所以将(width– 1) = 639设置成CSI_ACT_FRM_SIZE的低16位,(height-
1) =479设置成CSI_ACT_FRM_SIZE的高16位,所以设置后CSI_ACT_FRM_SIZE的值为0x01DF027F。
2.2 ipu_csi_set_window_pos函数
ipu_csi_set_window_pos函数设置了CSI_OUT_FRM_CTRL寄存器,
ipu_csi_set_window_pos(cam->ipu, cam->crop_current.left, cam->crop_current.top, cam->csi);
void ipu_csi_set_window_pos(struct ipu_soc *ipu, uint32_t left, uint32_t top, uint32_t csi)
{
uint32_t temp;
_ipu_get(ipu);
mutex_lock(&ipu->mutex_lock);
temp = ipu_csi_read(ipu, csi, CSI_OUT_FRM_CTRL);
temp &= ~(CSI_HSC_MASK | CSI_VSC_MASK);
temp |= ((top << CSI_VSC_SHIFT) | (left << CSI_HSC_SHIFT));
ipu_csi_write(ipu, csi, temp, CSI_OUT_FRM_CTRL);
mutex_unlock(&ipu->mutex_lock);
_ipu_put(ipu);
}
在这个函数中,cam->crop_current.top= cam->crop_current.left
= 0;看这个ipu_set_window_pos函数,它先读取出CSI_OUT_FRM_CTRL寄存器的值保存为temp,然后通过temp&=
~(CSI_HSC_MASK | CSI_VSC_MASK); 将这个temp的CSI_HSC_MASK和CSI_VSC_MASK位清零。CSI_HSC_MASK为0x1FFF0000,
CSI_VSC_MASK为0x00000FFF,对比下图可以看出来,即对应图中的CSI0_HSC和CSI0_VSC位。之后通过temp|=
((top << CSI_VSC_SHIFT) | (left << CSI_HSC_SHIFT));将top和left分别设置到对应的CSI_VSC_MASK和CSI_HSC_MASK位。可以猜测,CSI_VSC_SHIFT就是CSI0_VSC位的偏移值,从图中可以看出来为0,CSI_HSC_SHIFT为CSI0_HSC位的偏移值,从图中可以看出来为16.
由于这两个值都为0,所以最终的打印信息,都是0x00000000。
2.3 ipu_csi_init_interface函数
ipu_csi_init_interface(cam->ipu, cam->crop_bounds.width, cam->crop_bounds.height,
cam_fmt.fmt.pix.pixelformat, csi_param);
int32_t ipu_csi_init_interface(struct ipu_soc *ipu, uint16_t width, uint16_t height,
uint32_t pixel_fmt, ipu_csi_signal_cfg_t cfg_param)
2.3.1
在ipu_csi_init_interface函数中首先设置CSI_SENS_CONF寄存器,
CSI_SENS_CONF寄存器的值在设置之前是:0x02008900,设置后是0x00000900。这个CSI_SENS_CONF寄存器对应的是mxc_v4l_open函数中设置的csi_param参数的值,可以从打印出来的值反推出来:csi_param.data_fmt=
1L,即:
在ipu_csi_init_interface函数原型中会根据传入的pixel_fmt来执行IPU_PIX_FMT_YUYV这个case;
pixel_fmt为IPU_PIX_FMT_YUYV;
cfg_param.data_fmt= CSI_SENS_CONF_DATA_FMT_YUV422_YUYV;
之后会将cfg_param.data_fmt设置到CSI_SENS_CONF寄存器中。
那么这个pixel_fmt是在哪设置的??在ipu_csi_init_interface函数原型中的pixel_fmt所对应的实参是cam_fmt.fmt.pix.pixelformat,而这个cam_fmt.fmt.pix.pixelformat是通过
vidioc_int_g_fmt_cap(cam->sensor,&cam_fmt);函数获取到的,那么继续追踪到ov5640.c中的ioctl_g_fmt_cap函数,里面只有简单的几句话:
static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f)
{
struct sensor_data *sensor = s->priv;
f->fmt.pix = sensor->pix;
return 0;
}
所以最终是从s->priv中获取到的pix参数,s->priv参数在ov5640.c中就是ov5640_int_device->priv,那么这个参数是在哪设置的呢?在ov5640_probe函数中,首先设置ov5640_data参数,然后将ov5640_int_device.priv指向这个设置好的&ov5640_data参数。从这里面可以看出来ov5640_data.pix.pixelformat=
V4L2_PIX_FMT_YUYV;它对应的fourcc码是('Y','U',
'Y','V'),这个fourcc码与IPU_PIX_FMT_YUYV所对应的fourcc码相同。它们只是在不同的驱动层次里面封装的名字不太相同。
同样可以反推出来csi_param.data_width=
1,即mxc_v4l_open函数中:
csi_param.data_width= IPU_CSI_DATA_WIDTH_8;
由于这几个参数是在mxc_v4l_open函数中通过vidioc_int_g_ifparm函数来获取到的。在ov5640.c的ioctl_g_ifparm函数中指定了p->u.bt656.mode=
V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT;在mxc_v4l_open函数中通过
if(ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT)
csi_param.data_width= IPU_CSI_DATA_WIDTH_8;
与反推出来的结果一致。
2.3.2
然后是设置CSI_SENS_FRM_SIZE寄存器,
ipu_csi_write(ipu,csi, (width - 1) | (height - 1) << 16, CSI_SENS_FRM_SIZE);
其中cam->crop_bounds.width= 640, cam->crop_bounds.height
= 480,
所以这个CSI_SENS_FRM_SIZE寄存器的值为:0x01DF027F。
在mxc_v4l_open函数中设置了csi_param.clk_mode=
0;所以下面判断cfg_param.clk_mode的语句都没有执行,所以CSI_CCIR_CODE_1,CSI_CCIR_CODE_2,CSI_CCIR_CODE_3的值都为0.
3. VIDIOC_DBG_G_CHIP_IDENT ioctl调用
ioctl(fd_v4l,VIDIOC_DBG_G_CHIP_IDENT, &chip)
通过调用VIDIOC_DBG_G_CHIP_IDENT宏来获取sensor的信息,保存在chip这个结构体中,打印出:sensorchip
is ov5640_camera
用户空间调用VIDIOC_DBG_G_CHIP_IDENT这个ioctl调用,会调用到mxc_v4l2_capture.c中的mxc_v4l_ioctl函数,继续调用mxc_v4l_do_ioctl函数,找到VIDIOC_DBG_G_CHIP_IDENT这个case,在这个case中会调用vidioc_int_g_chip_ident这个函数,最终会调用到ov5640.c文件中的ioctl_g_chip_ident函数。
chip是structv4l2_dbg_chip_ident类型的结构体,如下所示:
struct v4l2_dbg_chip_ident {
struct v4l2_dbg_match match;
__u32 ident; /* chip identifier as specified in */
__u32 revision; /* chip revision, chip specific */
} __attribute__ ((packed));
struct v4l2_dbg_match {
__u32 type; /* Match type */
union { /* Match this chip, meaning determined by type */
__u32 addr;
char name[32];
};
} __attribute__ ((packed));
在caseVIDIOC_DBG_G_CHIP_IDENT中将ident设置为V4L2_IDENT_NONE,revision设置为0;在ioctl_g_chip_ident函数中将match.type设置为V4L2_CHIP_MATCH_I2C_DRIVER,match.name设置为"ov5640_camera"。
然后应用程序中通过:printf("sensorchip is %s
",
chip.match.name);打印出上面的信息。
4.VIDIOC_ENUM_FRAMESIZES ioctl调用
ioctl(fd_v4l,VIDIOC_ENUM_FRAMESIZES, &fsize)
通过调用VIDIOC_ENUM_FRAMESIZES宏来枚举framesize的信息,会调用到mxc_v4l2_capture.c中mxc_v4l_do_ioctl函数的VIDIOC_ENUM_FRAMESIZEScase,在里面会调用到vidioc_int_enum_framesizes函数,它最终会调用到ov5640.c文件中的ioctl_enum_framesizes函数。
structv4l2_frmsizeenum *fsize
struct v4l2_frmsizeenum {
__u32 index; /* Frame size number */
__u32 pixel_format; /* Pixel format */
__u32 type; /* Frame size type the device supports. */
union { /* Frame size */
struct v4l2_frmsize_discrete discrete;
struct v4l2_frmsize_stepwise stepwise;
};
__u32 reserved[2]; /* Reserved space for future use */
};
这个函数根据ov5640_data结构体和ov5640_mode_info_data这个二维数组来设置&fsize结构体,在应用程序中,这个ioctl外面是一个while循环,它会将所有的framesize都打印出来。在应用程序的输出信息中已经将这个fsize打印出来了:
sensorsupported frame size:
640x480
320x240
720x480
720x576
1280x720
1920x1080
2592x1944
176x144
1024x768
这个ov5640_mode_info_data二维数组是ov5640.c中维护的一个静态数组,它表示ov5640这个设备支持什么的格式等信息。
5.VIDIOC_ENUM_FMT ioctl调用
ioctl(fd_v4l,VIDIOC_ENUM_FMT, &ffmt)
通过这个ioctl调用,将所有的支持的format格式列举出来,会调用到mxc_v4l2_capture.c中mxc_v4l_do_ioctl函数的VIDIOC_ENUM_FMTcase,它最终会调用到ov5640.c文件中的ioctl_enum_fmt_cap函数,fmt->pixelformat=
ov5640_data.pix.pixelformat;
输出信息如下:
sensorframe format: YUYV
sensorframe format: YUYV
sensorframe format: YUYV
sensorframe format: YUYV
sensorframe format: YUYV
sensorframe format: YUYV
sensorframe format: YUYV
sensorframe format: YUYV
sensorframe format: YUYV
6.VIDIOC_S_PARM ioctl调用
ioctl(fd_v4l,VIDIOC_S_PARM, &parm)
通过这个ioctl调用来设置一些param参数,在应用程序中进行了如下