NXP

5.1 mxc_v4l2_capture.c应用程序追踪分析

2019-07-12 13:42发布

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函数中进行了设置,到底哪个设置起作用?最后终于缕清这个思路了。 关于这个流程,参见《masterslave的匹配过程》这个文档。
在这个mxc_v4l_open函数中,通过调用vidioc_int_g_ifparm(cam->sensor,&ifparm)vidioc_int_g_fmt_cap(cam->sensor,&cam_fmt)函数来获取slave设备的一些信息来分别设置csi_paramcam->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_sizeipu_csi_set_window_posipu_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); 将这个tempCSI_HSC_MASKCSI_VSC_MASK位清零。CSI_HSC_MASK0x1FFF0000, CSI_VSC_MASK0x00000FFF,对比下图可以看出来,即对应图中的CSI0_HSCCSI0_VSC位。之后通过temp|= ((top << CSI_VSC_SHIFT) | (left << CSI_HSC_SHIFT));topleft分别设置到对应的CSI_VSC_MASKCSI_HSC_MASK位。可以猜测,CSI_VSC_SHIFT就是CSI0_VSC位的偏移值,从图中可以看出来为0,CSI_HSC_SHIFTCSI0_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_fmtIPU_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.cioctl_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_1CSI_CCIR_CODE_2CSI_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函数。
chipstructv4l2_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_NONErevision设置为0;在ioctl_g_chip_ident函数中将match.type设置为V4L2_CHIP_MATCH_I2C_DRIVERmatch.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.cmxc_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.cmxc_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参数,在应用程序中进行了如下