NXP

3.4 mxc_v4l_open函数分析

2019-07-12 13:51发布

下面就来分析这个mxc_v4l_open函数: static int mxc_v4l_open(struct file *file) { struct v4l2_ifparm ifparm; struct v4l2_format cam_fmt; ipu_csi_signal_cfg_t csi_param; struct video_device *dev = video_devdata(file); //获取video_device结构体 cam_data *cam = video_get_drvdata(dev); int err = 0; struct sensor_data *sensor; pr_debug(" In MVC: mxc_v4l_open "); pr_debug(" device name is %s ", dev->name); if (!cam) { //如果没有获取到cam,就返回-EBADF。 pr_err("ERROR: v4l2 capture: Internal error, " "cam_data not found! "); return -EBADF; } if (cam->sensor == NULL || cam->sensor->type != v4l2_int_type_slave) { pr_err("ERROR: v4l2 capture: slave not found! "); return -EAGAIN; } //如果cam->sensor不存在或者类型不是v4l2_int_type_slave的话就返回-EAGAIN //这个cam->sensor是在mxc_v4l2_master_attach函数中通过cam->sensor= slave设置的。 //所以cam->sensor就代表当前使用的slave设备。 sensor = cam->sensor->priv; if (!sensor) { pr_err("%s: Internal error, sensor_data is not found! ", __func__); return -EBADF; } //这个cam->sensor->priv是在slave设备的probe函数中设置的,比如对于ov5640.c函数中,通过 //ov5640_int_device.priv= &ov5640_data;来设置的。 down(&cam->busy_lock); err = 0; if (signal_pending(current)) goto oops; /*signal_pending(current)检查当前进程是否有信号处理,返回不为0表示有信号需要处理。返回-ERESTARTSYS表示信号函数处理完毕后重新执行信号函数前的某个系统调用。也就是说,如果信号函数前有发生系统调用,在调度信号处理函数之前,内核会检查系统调用的返回值,看看是不是因为这个信号而中断了系统调用.如果返回值-ERESTARTSYS,并且当前调度的信号具备-ERESTARTSYS属性,系统就会在用户信号函数返回之后再执行该系统调用。*/ if (cam->open_count++ == 0) { wait_event_interruptible(cam->power_queue, cam->low_power == false); /*这个power_queue等待队列是在init_camera_struct函数中初始化的,等待上电以后继续往下运行,在mxc_v4l2_resume函数中,会将low_power标志位置为false,然后唤醒这个队列。*/ if (strcmp(mxc_capture_inputs[cam->current_input].name, "CSI MEM") == 0) { #if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) err = csi_enc_select(cam); #endif } else if (strcmp(mxc_capture_inputs[cam->current_input].name, "CSI IC MEM") == 0) { #if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) err = prp_enc_select(cam); #endif } /*cam->current_inputinit_camera_struct中被初始化为0,在这根据mxc_capture_inputs[]这个数组中第cam->current_input项的名字来选择执行哪个函数。在这个文件中,mxc_capture_inputs[0]的名字是“CSIIC MEM”,同时CONFIG_MXC_IPU_PRP_ENC这个宏定义了,所以执行prp_enc_select函数。这两个函数的目的就是根据不同的情况来选择为cam_data结构体里面那几个函数指针初始化为不同的值。这几个函数指针很重要,在后面使用到的时候再具体分析。*/ cam->enc_counter = 0; INIT_LIST_HEAD(&cam->ready_q); INIT_LIST_HEAD(&cam->working_q); INIT_LIST_HEAD(&cam->done_q); /*初始化3个队列头,这3个队列是在使用buffer过程中需要使用的。*/ vidioc_int_g_ifparm(cam->sensor, &ifparm);
/*在《vidioc_int_*类函数的调用过程》中分析了这类函数,它最终会调用到slave设备中的ioctl_g_ifparm函数。它的作用就是填充ifparm这个结构体,以ov5640.c为例如下所示: static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) { if (s == NULL) { pr_err(" ERROR!! no slave device set! "); return -1; } memset(p, 0, sizeof(*p)); p->u.bt656.clock_curr = ov5640_data.mclk; pr_debug(" clock_curr=mclk=%d ", ov5640_data.mclk); p->if_type = V4L2_IF_TYPE_BT656; p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; p->u.bt656.clock_min = OV5640_XCLK_MIN; p->u.bt656.clock_max = OV5640_XCLK_MAX; p->u.bt656.bt_sync_correct = 1; /* Indicate external vsync */ return 0; } 但是这个函数就是根据ov5640.c里面的一些值来设置ifparm这个结构体,跟cam->sensor没啥关系,从这个ov5640slave设备里面获取的这些值,用来进行下面的设置,这个函数可以看成是从底层的slave设备里面获取参数的函数。 */ csi_param.sens_clksrc = 0; csi_param.clk_mode = 0; csi_param.data_pol = 0; csi_param.ext_vsync = 0; csi_param.pack_tight = 0; csi_param.force_eof = 0; csi_param.data_en_pol = 0; csi_param.mclk = ifparm.u.bt656.clock_curr; csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; if (ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) csi_param.data_width = IPU_CSI_DATA_WIDTH_8; else if (ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) csi_param.data_width = IPU_CSI_DATA_WIDTH_10; else csi_param.data_width = IPU_CSI_DATA_WIDTH_8; csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; csi_param.csi = cam->csi; /*这个csi_paramipu_csi_signal_cfg_t类型的,根据上面vidioc_int_g_ifparm函数从底层slave设备获取的参数来对csi_param结构体进行初始化。*/ cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); /*这个vidioc_int_g_fmt_cap函数也是《vidioc_int_*类函数的调用过程》中分析的,它最终会调用到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; } 最终会根据cam->sensor->pix里面的值来填充cam_fmt->fmt.pix的值。看这个ioctl_g_fmt_cap函数,s就是cam->sensors->priv就是ov5640probe函数中指定的ov5640_data结构体。所以最终,f->fmt.pix里面存放的就是ov5640_data->pix里面的值。 */ /* Reset the sizes. Needed to prevent carryover of last * operation.*/ cam->crop_bounds.top = cam->crop_bounds.left = 0; cam->crop_bounds.width = cam_fmt.fmt.pix.width; cam->crop_bounds.height = cam_fmt.fmt.pix.height; /* This also is the max crop size for this device. */ cam->crop_defrect.top = cam->crop_defrect.left = 0; cam->crop_defrect.width = cam_fmt.fmt.pix.width; cam->crop_defrect.height = cam_fmt.fmt.pix.height; /* At this point, this is also the current image size. */ cam->crop_current.top = cam->crop_current.left = 0; cam->crop_current.width = cam_fmt.fmt.pix.width; cam->crop_current.height = cam_fmt.fmt.pix.height; /*根据上面获取到的cam_fmt参数来设置camcrop_boundscrop_defrectcrop_current的几个参数。*/ pr_debug("End of %s: v2f pix widthxheight %d x %d ", __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); pr_debug("End of %s: crop_bounds widthxheight %d x %d ", __func__, cam->crop_bounds.width, cam->crop_bounds.height); pr_debug("End of %s: crop_defrect widthxheight %d x %d ", __func__, cam->crop_defrect.width, cam->crop_defrect.height); pr_debug("End of %s: crop_current widthxheight %d x %d ", __func__, cam->crop_current.width, cam->crop_current.height); csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; pr_debug("On Open: Input to ipu size is %d x %d ", cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); ipu_csi_set_window_size(cam->ipu, cam->crop_current.width, cam->crop_current.height, cam->csi); ipu_csi_set_window_pos(cam->ipu, cam->crop_current.left, cam->crop_current.top, cam->csi); /*刚才在init_camera_struct函数中进行过这两个设置,设置窗口的大小和位置。在init_camera_struct函数中,直接将窗口大小和位置设置为左上角从(00)开始,640* 480 现在根据从底层slave设备中获取的参数重新设置窗口的大小和位置。*/ ipu_csi_init_interface(cam->ipu, cam->crop_bounds.width, cam->crop_bounds.height, cam_fmt.fmt.pix.pixelformat, csi_param); /*这个函数根据这几个参数,设置底层ipu寄存器的值,它大致设置了CSI_SENS_CONFCSI_SENS_FRM_SIZECSI_CCIR_CODE_1CSI_CCIR_CODE_2CSI_CCIR_CODE_3的值。我们在后面分析具体应用程序的执行流程的时候再具体分析它。*/ clk_prepare_enable(sensor->sensor_clk); /*这个函数在clk.h中定义,如下所示: /* clk_prepare_enable helps cases using clk_enable in non-atomic context. */ static inline int clk_prepare_enable(struct clk *clk) { int ret; ret = clk_prepare(clk); if (ret) return ret; ret = clk_enable(clk); if (ret) clk_unprepare(clk); return ret; } 看它的注释就能理解大概意思,这个clk_prepare_enable函数是为了帮助clk_enable函数应用在非原子上下文的情况下。它的核心就是这个clk_enable函数。 */ vidioc_int_s_power(cam->sensor, 1); /*这个函数同样是《vidioc_int_*类函数的调用过程》中所分析的,它最终会调用到ov5640.c中的ioctl_s_power函数: static int ioctl_s_power(struct v4l2_int_device *s, int on) { struct sensor_data *sensor = s->priv; if (on && !sensor->on) { if (io_regulator) if (regulator_enable(io_regulator) != 0) return -EIO; if (core_regulator) if (regulator_enable(core_regulator) != 0) return -EIO; if (gpo_regulator) if (regulator_enable(gpo_regulator) != 0) return -EIO; if (analog_regulator) if (regulator_enable(analog_regulator) != 0) return -EIO; /* Make sure power on */ ov5640_standby(0); } else if (!on && sensor->on) { if (analog_regulator) regulator_disable(analog_regulator); if (core_regulator) regulator_disable(core_regulator); if (io_regulator) regulator_disable(io_regulator); if (gpo_regulator) regulator_disable(gpo_regulator); ov5640_standby(1); } sensor->on = on; return 0; } 如果on=1并且sensor->on==0的话,这表示什么意思呢?sensor->on==0表明这个slave设备本身是没有上电的,然后调用vidioc_int_s_power(cam->sensor,1)函数为它上电,如果设备本身是上电状态,想要把它关闭就就调用调用vidioc_int_s_power(cam->sensor,0)即可。内部调用的是regulator_enable函数,它在/include/linux/regulator/consumer.h中定义,跳转查看可以发现这个函数和regulator_disable函数都是空函数,直接返回0.所以有用的函数就是ov5640_standby一个了,大致意思是根据pwn_gpio的值来判断是否真正的上电,断电。 */ vidioc_int_init(cam->sensor); /*会调用到ioctl_init函数,但是这个函数为空函数,直接返回0.*/ vidioc_int_dev_init(cam->sensor); /*会调用到ioctl_dev_init函数,这个函数就是初始化ov5640设备,这个函数中主要是初始化设置ov5640摄像头本身的一些寄存器的值等。在open函数中,不只是设置ipu的一些寄存器的值,还需要设置对应的摄像头本身的数据等。这个函数在分析摄像头文件的时候再具体分析。*/ } file->private_data = dev; oops: up(&cam->busy_lock); return err; }
至此就分析完这个mxc_v4l_open函数了。
上面标红 {MOD}的两个函数没有分析,在这分析一下: csi_enc_select函数在ipu_csi_enc.c中定义: int csi_enc_select(void *private) { cam_data *cam = (cam_data *) private; int err = 0; if (cam) { cam->enc_update_eba = csi_enc_eba_update; cam->enc_enable = csi_enc_enabling_tasks; cam->enc_disable = csi_enc_disabling_tasks; cam->enc_enable_csi = csi_enc_enable_csi; cam->enc_disable_csi = csi_enc_disable_csi; } else { err = -EIO; } return err; } EXPORT_SYMBOL(csi_enc_select); 它就是为cam_data结构体里面的几个函数指针赋值,这几个函数涉及到更新物理缓冲区地址,使能译码任务,使能csi等等操作,他们最终都会调用更底层的ipu操作函数来通过操作寄存器来实现这些功能。 prp_enc_select函数在ipu_prp_enc.c中定义: int prp_enc_select(void *private) { cam_data *cam = (cam_data *) private; int err = 0; if (cam) { cam->enc_update_eba = prp_enc_eba_update; cam->enc_enable = prp_enc_enabling_tasks; cam->enc_disable = prp_enc_disabling_tasks; cam->enc_enable_csi = prp_enc_enable_csi; cam->enc_disable_csi = prp_enc_disable_csi; } else { err = -EIO; } return err; } EXPORT_SYMBOL(prp_enc_select);
它同样是为cam_data中的几个函数指针赋值,这几个函数同样会调用更底层的ipu操作来实现功能。