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

i.MX6Q---IPU总结笔记 同时被 2 个专栏收录
35 篇文章 5 订阅
35 篇文章 47 订阅

对于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 <media/v4l2-chip-ident.h> */ 
	__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\n", 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参数,在应用程序中进行了如下的设置:

parm.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

parm.parm.capture.timeperframe.numerator= 1;

parm.parm.capture.timeperframe.denominator= g_camera_framerate; //30

parm.parm.capture.capturemode= g_capture_mode; //0

会调用到mxc_v4l2_s_param这个函数,打印出下面这些信息:

Currentcapabilities are 1001

Currentcapturemode is 0 change to 0

Currentframerate is 30 change to 30

clock_curr=mclk=24000000

g_fmt_capreturns widthxheight of input as 640 x 480

在这个mxc_v4l2_s_param函数中:
1
)如果开启了viewfinder的话,就先调用stop_preview关闭viewfinder

2)查看设备是否支持这些参数的变化,在内部通过调用vidioc_int_g_parm函数,最终调用到ov5640.c中的ioctl_g_parm函数,来获取cam->sensor里面的参数保存在currentparm里面。

3)查询完以后就可以调用vidioc_int_s_parm来设置这些参数了,最终调用到ov5640.c中的ioctl_s_parm函数来将应用程序中设置的parm参数设置到摄像头的寄存器中去。注意:这里说的是摄像头的寄存器,而不是ipu的寄存器,因为数据是摄像头采集的,摄像头本身也会有设置,改变的这些值需要设置到摄像头里面。

4)这时候查看经过s_parm后分辨率是否发生改变,如果发生改变的话,就需要对CSI重新设置,对CSI的设置就是通过调用vidioc_int_g_ifparm来设置信息到ifparm结构体里面,这个函数最终会调用到ov5640.c中的ioctl_g_ifparm函数,在里面设置ifparm结构体。然后根据ifparm中的值来设置csi_param的值,最后通过ipu_csi_init_interface函数来将csi_param的值设置到CSI_SENS_CONF寄存器中。


这一步的操作的mxc_v4l_open函数中操作过一次,但是与这里的操作有细微的差别,在mxc_v4l_open函数中只是设置了csi_param.ext_vsync= 0;但是在mxc_v4l2_s_param函数中首先设置了csi_param.ext_vsync= 0;然后根据vidioc_int_g_ifparm函数获得的ifparm值重新设置了

csi_param.ext_vsync= ifparm.u.bt656.bt_sync_correct;

最终导致在ipu_csi_init_interface函数中通过cfg_param.ext_vsync<<CSI_SENS_CONF_EXT_VSYNC_SHIFT来将CSI_SENS_CONF寄存器的值从0x00000900改变为0x00008900寄存器的这一位的意思是什么?它表示外部的VSYNC信号。


5)如果capturemode改变的话,crop的边界也会发生变化,所以通过vidioc_int_g_fmt_cap函数来从cam->sensor中获取新的fmt参数保存在cam_fmt中,然后根据cam_fmt的值通过ipu_csi_set_window_sizeipu_csi_set_window_pos函数来重新设置crop的位置和大小。



7. VIDIOC_S_INPUT ioctl调用

ioctl(fd_v4l,VIDIOC_S_INPUT, &g_input)

应用程序中这个g_input没有赋值,所以这个ioctl调用没有设置东西。



8. VIDIOC_G_CROP ioctl调用

ioctl(fd_v4l,VIDIOC_G_CROP, &crop)

应用程序通过这个ioctl调用,来获取cam_data结构体里面的crop_current参数,保存在crop结构体中。



9. VIDIOC_S_CROP ioctl调用

crop.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

crop.c.width= g_in_width;

crop.c.height= g_in_height;

crop.c.top= g_top;

crop.c.left= g_left;

ioctl(fd_v4l,VIDIOC_S_CROP, &crop)

获取到crop信息后,将这个crop结构体里面的信息设置成应用程序中指定的大小,然后调用VIDIOC_S_CROP这个ioctl调用来将这些设置保存。

但是在mxc_v4l_do_ioctl函数的VIDIOC_S_CROPioctol调用中,可以看出来,并不是应用程序的任意设置,驱动程序都会原封不动地去执行,而是会去与cam->crop_bounds相比较计算后再设置。


这时候,应用程序设置的大小为:352*288,所以最终cam->crop_current.width= =352, cam->crop_current.height =288。然后通过ipu_csi_set_window_size函数和ipu_csi_set_window_pos函数来设置。所以CSI_ACT_FRM_SIZE寄存器的值从0x01DF027F640*480)更改为0x011F015F352*288)。CSI_OUT_FRM_CTRL寄存器的值没有发生改变,因为应用程序中topleft的值没有发生改变。



10. VIDIOC_S_FMT icotl调用

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	fmt.fmt.pix.pixelformat = g_cap_fmt; //V4L2_PIX_FMT_YUV420
	fmt.fmt.pix.width = g_out_width; //352
	fmt.fmt.pix.height = g_out_height; //288
	fmt.fmt.pix.bytesperline = g_out_width; //352
	fmt.fmt.pix.priv = 0; 
	fmt.fmt.pix.sizeimage = 0;
ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)

最终会调用到mxc_v4l2_s_fmt函数,在这个函数中会对widthheightcam->crop_current的值进行计算。

在驱动程序中:

width = &f->fmt.pix.width; 
height = &f->fmt.pix.height;
size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; 
bytesperline = f->fmt.pix.width;
if (f->fmt.pix.bytesperline < bytesperline) 
	f->fmt.pix.bytesperline = bytesperline; 
else 
	bytesperline = f->fmt.pix.bytesperline; 

if (f->fmt.pix.sizeimage < size) 
	f->fmt.pix.sizeimage = size; 
else 
	size = f->fmt.pix.sizeimage; 

cam->v2f.fmt.pix = f->fmt.pix; 

if (cam->v2f.fmt.pix.priv != 0) { 
		if (copy_from_user(&cam->offset, 
				   (void *)cam->v2f.fmt.pix.priv, 
				   sizeof(cam->offset))) { 
			retval = -EFAULT; 
			break; 
		} 
	}

很重要的一点是在这个函数中对cam->v2f.fmt.pixcam->v2f.fmt.pix.priv进行了设置。虽然上面的很多步骤中都对crop或者widthheight等参数进行了设置,但是经过这个ioctl调用后,这些变量是最后一次设置,到此为止不再改变,然后就会根据这个参数来计算出sizeimagebytesperline等值。这些cam->v2f中的值在驱动中很多地方都需要使用。


最终打印出下面的语句:

InMVC: mxc_v4l2_s_fmt

type=V4L2_BUF_TYPE_VIDEO_CAPTURE

Endof mxc_v4l2_s_fmt: v2f pix widthxheight 352 x 288

Endof mxc_v4l2_s_fmt: crop_bounds widthxheight 640 x 480

Endof mxc_v4l2_s_fmt: crop_defrect widthxheight 640 x 480

Endof mxc_v4l2_s_fmt: crop_current widthxheight 352 x 288



11. VIDIOC_S_CTRL ioctl调用

ioctl(fd_v4l,VIDIOC_S_CTRL, &ctrl)

同样应用程序中这个ioctl没有设置东西。这个ioctl设置的是白平衡,亮度,反转等信息。



12. VIDIOC_REQBUFS ioctl调用

struct v4l2_requestbuffers req; 
	memset(&req, 0, sizeof (req)); 
	req.count = TEST_BUFFER_NUM;   //3
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	req.memory = V4L2_MEMORY_MMAP;
ioctl(fd_v4l, VIDIOC_REQBUFS, &req)

最终会调用到mxc_allocate_frame_buf函数中,在这个函数中通过dma_alloc_coherent函数来分配一致性DMA映射内存。分配的内存大小是cam->v2f.fmt.pix.sizeimage(上面分析过的cam->v2f参数),打印出来的信息显示这个值等于152064

InMVC:mxc_allocate_frame_buf - size=152064



13.VIDIOC_G_FMT ioctl调用

ioctl(fd_v4l,VIDIOC_G_FMT, &fmt)

之后进行了一次VIDIOC_G_FMT调用,在上面已经进行了VIDIOC_S_FMT调用,在这里执行VIDIOC_G_FMT调用来将这些信息读取出来,打印出如下信息:

InMVC: mxc_v4l2_g_fmt type=1

typeis V4L2_BUF_TYPE_VIDEO_CAPTURE

Endof mxc_v4l2_g_fmt: v2f pix widthxheight 352 x 288

Endof mxc_v4l2_g_fmt: crop_bounds widthxheight 640 x 480

Endof mxc_v4l2_g_fmt: crop_defrect widthxheight 640 x 480

Endof mxc_v4l2_g_fmt: crop_current widthxheight 352 x 288



14.VIDIOC_QUERYBUF ioctl调用

memset(&buf, 0, sizeof (buf)); 
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	buf.memory = V4L2_MEMORY_MMAP; 
	buf.index = i;
ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)

在应用程序中执行VIDIOC_QUERYBUFioctl调用,最终就会执行到mxc_v4l2_buffer_status函数中。

mxc_v4l2_buffer_status数的核心是这个:

memcpy(buf,&(cam->frame[buf->index].buffer), sizeof(*buf));

它将&(cam->frame[buf->index].buffer)里面的数据拷贝到buf中。为什么要将cam->frame[]里面的数据拷贝到用户空间?为什么不把摄像头采集到的数据拷贝过去?在《应用程序和驱动程序中buffer的传输流程》中分析了它们之间的关系。


InMVC:mxc_v4l2_buffer_status



15.mmap函数

buffers[i].length = buf.length; 
	buffers[i].offset = (size_t) buf.m.offset; 
	buffers[i].start = mmap (NULL, buffers[i].length, 
		PROT_READ | PROT_WRITE, MAP_SHARED, d_v4l, buffers[i].offset); 
	memset(buffers[i].start, 0xFF, buffers[i].length);

对于每一个buf,都使用mmap函数来映射它们,对应执行到mxc_v4l2_capture.c中的mxc_mmap函数,

它将根据上面一个VIDIOC_QUERYBUFioctl调用所获取到的buf参数来设置buffers[i]的参数,然后根据这些参数的值为buffers[i]映射内存空间。

这个mmap函数应该就不用看驱动程序中的mxc_mmap了,知道在应用程序中怎么使用mmap函数,以及每个参数是什么意义即可。

InMVC:mxc_mmap

pgoff=0x3c100,start=0x76d93000, end=0x76db9000

InMVC:mxc_v4l_ioctl

InMVC: mxc_v4l_do_ioctl c0445609

caseVIDIOC_QUERYBUF

InMVC:mxc_v4l2_buffer_status

InMVC:mxc_mmap

pgoff=0x3c140,start=0x76d6d000, end=0x76d93000

InMVC:mxc_v4l_ioctl

InMVC: mxc_v4l_do_ioctl c0445609

caseVIDIOC_QUERYBUF

InMVC:mxc_v4l2_buffer_status

InMVC:mxc_mmap

pgoff=0x3c180,start=0x76d47000, end=0x76d6d000



16.VIDIOC_QBUF ioctl调用

ioctl(fd_v4l, VIDIOC_QBUF, &buf)

会调用到mxc_v4l_do_ioctlVIDIOC_QBUFcase,将cam->frame[index].buffer.flags添加上V4L2_BUF_FLAG_QUEUED属性(本身这时候应该是V4L2_BUF_FLAG_MAPPED属性),然后cam->frame[index].queue添加到cam->ready_q队列中,之后通过buf->flags= cam->frame[index].buffer.flags,将buf->flags也设置成与cam->frame[index].buffer.flags相同的属性。从这里可以看出来,cam_data结构体里面的frame[index]与应用程序中的buf是一一对应的关系。


17.VIDIOC_STREAMON函数

ioctl(fd_v4l, VIDIOC_STREAMON, &type)

会调用到mxc_streamon函数,在mxc_streamon函数中首先判断cam->ready_q队列中至少有2buffers,然后调用cam->enc_enable(cam)函数来使能coding,这个函数就是在mxc_v4l_open函数中设置的cam_data结构体里面的函数指针,在mxc_v4l_open函数中将它指向了prp_enc_enabling_tasks函数。

然后通过下面的语句:

list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); 
	list_del(cam->ready_q.next); 
	list_add_tail(&frame->queue, &cam->working_q); 
	frame->ipu_buf_num = cam->ping_pong_csi; 
	err = cam->enc_update_eba(cam, frame->buffer.m.offset);

来将cam->ready_q队列的第一个buffer取出来放到cam->working_q队列中,然后调用cam->enc_update_eba(cam,frame->buffer.m.offset)函数来更新IDMAC的物理地址,为数据采集传输做准备。之后继续调用cam->enc_enable_csi函数来使能CSI设备。这时候就开始采集数据了。


在这个mxc_streamon函数中有三个重要的函数:

err= cam->enc_enable(cam);

err= cam->enc_update_eba(cam, frame->buffer.m.offset);

err= cam->enc_enable_csi(cam);


下面来仔细分析这三个函数:

这三个函数都是在mxc_v4l_open中通过err=prp_enc_select(cam);来调用到ipu_prp_enc.c文件中的prp_enc_select函数来分别为它们指定对应的函数。

		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;

17.1

err= cam->enc_enable(cam);

prp_enc_enabling_tasks函数:

static int prp_enc_enabling_tasks(void *private) 
{ 
	cam_data *cam = (cam_data *) private; 
	int err = 0; 
	CAMERA_TRACE("IPU:In prp_enc_enabling_tasks\n"); 

	cam->dummy_frame.vaddress = dma_alloc_coherent(0, 
			       PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), 
			       &cam->dummy_frame.paddress, 
			       GFP_DMA | GFP_KERNEL); 
	if (cam->dummy_frame.vaddress == 0) { 
		pr_err("ERROR: v4l2 capture: Allocate dummy frame " 
		       "failed.\n"); 
		return -ENOBUFS; 
	} 
	cam->dummy_frame.buffer.type = V4L2_BUF_TYPE_PRIVATE; 
	cam->dummy_frame.buffer.length = 
	    PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); 
	cam->dummy_frame.buffer.m.offset = cam->dummy_frame.paddress; 

	if (cam->rotation >= IPU_ROTATE_90_RIGHT) { 
		err = ipu_request_irq(cam->ipu, IPU_IRQ_PRP_ENC_ROT_OUT_EOF, 
				      prp_enc_callback, 0, "Mxc Camera", cam); 
	} else { 
		err = <span style="color:#FF0000;">ipu_request_irq</span>(cam->ipu, IPU_IRQ_PRP_ENC_OUT_EOF, 
				      prp_enc_callback, 0, "Mxc Camera", cam); 
	} 
	if (err != 0) { 
		printk(KERN_ERR "Error registering rot irq\n"); 
		return err; 
	} 

	err =<span style="color:#FF0000;"> prp_enc_setup</span>(cam); 
	if (err != 0) { 
		printk(KERN_ERR "prp_enc_setup %d\n", err); 
		return err; 
	} 

	return err; 
}

在这个函数里面重要的我用红色标出了,首先申请中断,这个ipu_request_irq函数在ipu_common.c文件中定义了,之后是调用prp_enc_setup函数,这个函数如下:

static int prp_enc_setup(cam_data *cam) 
{ 
	ipu_channel_params_t enc; 
	int err = 0; 
	dma_addr_t dummy = cam->dummy_frame.buffer.m.offset;

	CAMERA_TRACE("In prp_enc_setup\n"); 
	if (!cam) { 
		printk(KERN_ERR "cam private is NULL\n"); 
		return -ENXIO; 
	} 
	memset(&enc, 0, sizeof(ipu_channel_params_t)); 

	ipu_csi_get_window_size(cam->ipu, &enc.csi_prp_enc_mem.in_width, 
				&enc.csi_prp_enc_mem.in_height, cam->csi); 

	enc.csi_prp_enc_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; 
	enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.width; 
	enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.height; 
	enc.csi_prp_enc_mem.csi = cam->csi; 
	if (cam->rotation >= IPU_ROTATE_90_RIGHT) { 
		enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.height; 
		enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.width; 
	} 

	err =<span style="color:#FF0000;"> ipu_init_channel</span>(cam->ipu, CSI_PRP_ENC_MEM, &enc); 
	if (err != 0) { 
		printk(KERN_ERR "ipu_init_channel %d\n", err); 
		return err; 
	} 

	grotation = cam->rotation; 
	if (cam->rotation >= IPU_ROTATE_90_RIGHT) { 
		if (cam->rot_enc_bufs_vaddr[0]) { 
			dma_free_coherent(0, cam->rot_enc_buf_size[0], 
					  cam->rot_enc_bufs_vaddr[0], 
					  cam->rot_enc_bufs[0]); 
		} 
		if (cam->rot_enc_bufs_vaddr[1]) { 
			dma_free_coherent(0, cam->rot_enc_buf_size[1], 
					  cam->rot_enc_bufs_vaddr[1], 
					  cam->rot_enc_bufs[1]); 
		} 
		cam->rot_enc_buf_size[0] = 
		    PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); 
		cam->rot_enc_bufs_vaddr[0] = 
		    (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[0], 
					       &cam->rot_enc_bufs[0], 
					       GFP_DMA | GFP_KERNEL); 
		if (!cam->rot_enc_bufs_vaddr[0]) { 
			printk(KERN_ERR "alloc enc_bufs0\n"); 
			return -ENOMEM; 
		} 
		cam->rot_enc_buf_size[1] = 
		    PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); 
		cam->rot_enc_bufs_vaddr[1] = 
		    (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[1], 
					       &cam->rot_enc_bufs[1], 
					       GFP_DMA | GFP_KERNEL); 
		if (!cam->rot_enc_bufs_vaddr[1]) { 
			dma_free_coherent(0, cam->rot_enc_buf_size[0], 
					  cam->rot_enc_bufs_vaddr[0], 
					  cam->rot_enc_bufs[0]); 
			cam->rot_enc_bufs_vaddr[0] = NULL; 
			cam->rot_enc_bufs[0] = 0; 
			printk(KERN_ERR "alloc enc_bufs1\n"); 
			return -ENOMEM; 
		} 

		err = ipu_init_channel_buffer(cam->ipu, CSI_PRP_ENC_MEM, 
					      IPU_OUTPUT_BUFFER, 
					      enc.csi_prp_enc_mem.out_pixel_fmt, 
					      enc.csi_prp_enc_mem.out_width, 
					      enc.csi_prp_enc_mem.out_height, 
					      enc.csi_prp_enc_mem.out_width, 
					      IPU_ROTATE_NONE, 
					      cam->rot_enc_bufs[0], 
					      cam->rot_enc_bufs[1], 0, 0, 0); 
		if (err != 0) { 
			printk(KERN_ERR "CSI_PRP_ENC_MEM err\n"); 
			return err; 
		} 

		err = ipu_init_channel(cam->ipu, MEM_ROT_ENC_MEM, NULL); 
		if (err != 0) { 
			printk(KERN_ERR "MEM_ROT_ENC_MEM channel err\n"); 
			return err; 
		} 

		err = ipu_init_channel_buffer(cam->ipu, MEM_ROT_ENC_MEM, 
					      IPU_INPUT_BUFFER, 
					      enc.csi_prp_enc_mem.out_pixel_fmt, 
					      enc.csi_prp_enc_mem.out_width, 
					      enc.csi_prp_enc_mem.out_height, 
					      enc.csi_prp_enc_mem.out_width, 
					      cam->rotation, 
					      cam->rot_enc_bufs[0], 
					      cam->rot_enc_bufs[1], 0, 0, 0); 
		if (err != 0) { 
			printk(KERN_ERR "MEM_ROT_ENC_MEM input buffer\n"); 
			return err; 
		} 

		err = 
		    ipu_init_channel_buffer(cam->ipu, MEM_ROT_ENC_MEM, 
					    IPU_OUTPUT_BUFFER, 
					    enc.csi_prp_enc_mem.out_pixel_fmt, 
					    enc.csi_prp_enc_mem.out_height, 
					    enc.csi_prp_enc_mem.out_width, 
					    cam->v2f.fmt.pix.bytesperline / 
					    bytes_per_pixel(enc.csi_prp_enc_mem. 
							    out_pixel_fmt), 
					    IPU_ROTATE_NONE, 
					    dummy, dummy, 0, 
					    cam->offset.u_offset, 
					    cam->offset.v_offset); 
		if (err != 0) { 
			printk(KERN_ERR "MEM_ROT_ENC_MEM output buffer\n"); 
			return err; 
		} 

		err = ipu_link_channels(cam->ipu, 
					CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); 
		if (err < 0) { 
			printk(KERN_ERR 
			       "link CSI_PRP_ENC_MEM-MEM_ROT_ENC_MEM\n"); 
			return err; 
		} 

		err = ipu_enable_channel(cam->ipu, CSI_PRP_ENC_MEM); 
		if (err < 0) { 
			printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); 
			return err; 
		} 
		err = ipu_enable_channel(cam->ipu, MEM_ROT_ENC_MEM); 
		if (err < 0) { 
			printk(KERN_ERR "ipu_enable_channel MEM_ROT_ENC_MEM\n"); 
			return err; 
		} 

		ipu_select_buffer(cam->ipu, CSI_PRP_ENC_MEM, 
				  IPU_OUTPUT_BUFFER, 0); 
		ipu_select_buffer(cam->ipu, CSI_PRP_ENC_MEM, 
				  IPU_OUTPUT_BUFFER, 1); 
	} else { 
		err = 
		    <span style="color:#FF0000;">ipu_init_channel_buffer</span>(cam->ipu, CSI_PRP_ENC_MEM, 
					    IPU_OUTPUT_BUFFER, 
					    enc.csi_prp_enc_mem.out_pixel_fmt, 
					    enc.csi_prp_enc_mem.out_width, 
					    enc.csi_prp_enc_mem.out_height, 
					    cam->v2f.fmt.pix.bytesperline / 
					    bytes_per_pixel(enc.csi_prp_enc_mem. 
							    out_pixel_fmt), 
					    cam->rotation, 
					    dummy, dummy, 0, 
					    cam->offset.u_offset, 
					    cam->offset.v_offset); 
		if (err != 0) { 
			printk(KERN_ERR "CSI_PRP_ENC_MEM output buffer\n"); 
			return err; 
		} 
		err = <span style="color:#FF0000;">ipu_enable_channel</span>(cam->ipu, CSI_PRP_ENC_MEM); 
		if (err < 0) { 
			printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); 
			return err; 
		} 
	} 

	return err; 
}

在这个函数中完成了ipu_init_channel--->ipu_init_channel_buffer--->ipu_enable_channel这个流程。


先来看这个ipu_channel_params_t这个联合,在这个联合中对于每一种channel都有一个对应的结构体类型来保存channel的参数。因为现在这个channelcsi_prp_enc_mem,所以在这个prp_enc_setup函数中涉及的channel参数都保存在enc.csi_prp_enc_mem中。如下所示:

struct { 
		uint32_t in_width; 
		uint32_t in_height; 
		uint32_t in_pixel_fmt; 
		uint32_t out_width; 
		uint32_t out_height; 
		uint32_t out_pixel_fmt; 
		uint32_t outh_resize_ratio; 
		uint32_t outv_resize_ratio; 
		uint32_t csi; 
		uint32_t mipi_id; 
		uint32_t mipi_vc; 
		bool mipi_en; 
	} csi_prp_enc_mem;

看这个结构体,它包括输入的宽度,高度和pixel格式,同样包括输出的宽度,高度和pixel格式,以及输出的水平,垂直方向重定义大小的比例,另外还有csi号,有关mipi的信息等。


之后的任务就是填充这个结构体,通过ipu_csi_get_window_size函数来填充enc.csi_prp_enc_mem.in_widthenc.csi_prp_enc_mem.in_height,通过enc.csi_prp_enc_mem.in_pixel_fmt= IPU_PIX_FMT_UYVY;来指定in_pixel_fmt参数,然后根据cam->v2f.fmt.pix来填充enc.csi_prp_enc_mem.out_widthenc.csi_prp_enc_mem.out_heightenc.csi_prp_enc_mem.out_pixel_fmt参数。


在之前init_camera_struct函数的时候,一直不清楚cam->v2f.fmt.pix这个参数的作用,现在在这清楚了。因为应用程序中最后设置(经过VIDIOC_S_FMT宏设置)的widthheight,以及经过计算的出来的bytesperlinesizeimage等参数都保存在cam_data结构体里面的v2f.fmt.pix里面。


之后就是ipu_init_channel函数了。

ipu_init_channel函数中,

case CSI_PRP_ENC_MEM: 
		if (params->csi_prp_enc_mem.csi > 1) { 
			ret = -EINVAL; 
			goto err; 
		} 
		if ((ipu->using_ic_dirct_ch == MEM_VDI_PRP_VF_MEM) || 
			(ipu->using_ic_dirct_ch == MEM_VDI_MEM)) { 
			ret = -EINVAL; 
			goto err; 
		} 
		ipu->using_ic_dirct_ch = CSI_PRP_ENC_MEM; 

		ipu->ic_use_count++; 
		ipu->csi_channel[params->csi_prp_enc_mem.csi] = channel; 

		if (params->csi_prp_enc_mem.mipi_en) { 
			ipu_conf |= (1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + 
				params->csi_prp_enc_mem.csi)); 
			_ipu_csi_set_mipi_di(ipu, 
				params->csi_prp_enc_mem.mipi_vc, 
				params->csi_prp_enc_mem.mipi_id, 
				params->csi_prp_enc_mem.csi); 
		} else 
			ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + 
				params->csi_prp_enc_mem.csi)); 

		/*CSI0/1 feed into IC*/ 
		ipu_conf &= ~IPU_CONF_IC_INPUT; 
		if (params->csi_prp_enc_mem.csi) 
			ipu_conf |= IPU_CONF_CSI_SEL; 
		else 
			ipu_conf &= ~IPU_CONF_CSI_SEL; 

		/*PRP skip buffer in memory, only valid when RWS_EN is true*/ 
		reg = ipu_cm_read(ipu, IPU_FS_PROC_FLOW1); 
		ipu_cm_write(ipu, reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); 

		/*CSI data (include compander) dest*/ 
		_ipu_csi_init(ipu, channel, params->csi_prp_enc_mem.csi); 
		_ipu_ic_init_prpenc(ipu, params, true); 
		break;

这个ipu_init_channel函数中的第三个参数params也是之前说过的ipu_channel_params_t类型的,所以这个函数里面设置的同样都是params->csi_prp_enc_mem里面的值。


最终通过_ipu_csi_init(ipu,channel, params->csi_prp_enc_mem.csi);来设置CSI_SENS_CONF寄存器中的目的地址。



CSI_SENS_CONF寄存器中2426位表示CSI中数据的目的地址,

int _ipu_csi_init(struct ipu_soc *ipu, ipu_channel_t channel, uint32_t csi) 
{ 
	uint32_t csi_sens_conf, csi_dest; 
	int retval = 0; 

	switch (channel) { 
	case CSI_MEM0: 
	case CSI_MEM1: 
	case CSI_MEM2: 
	case CSI_MEM3: 
		csi_dest = CSI_DATA_DEST_IDMAC; 
		break; 
	case CSI_PRP_ENC_MEM: 
	case CSI_PRP_VF_MEM: 
		csi_dest = CSI_DATA_DEST_IC; 
		break; 
	default: 
		retval = -EINVAL; 
		goto err; 
	} 

	csi_sens_conf = ipu_csi_read(ipu, csi, CSI_SENS_CONF); 
	csi_sens_conf &= ~CSI_SENS_CONF_DATA_DEST_MASK; 
	ipu_csi_write(ipu, csi, csi_sens_conf | (csi_dest << 
		CSI_SENS_CONF_DATA_DEST_SHIFT), CSI_SENS_CONF); 
err: 
	return retval; 
}

可以看出来,

CSI_DATA_DEST_IC= 2L,

CSI_DATA_DEST_IDMAC= 4L,

CSI_SENS_CONF_DATA_DEST_SHIFT= 24,

所以这个_ipu_csi_init函数就是用来设置从CSI获取到数据的目的地址。目的地址只有两个ICIDMAC。很明显,对于CSI_PRP_ENC_MEM这个channel来说,它的目的地址就是IC了,所以下面就应该设置IC了,就是_ipu_ic_init_prpenc函数。


而且从这个函数中可以看出来,只有CSI_MEM0CSI_MEM3,CSI_PRP_ENC_MEMCSI_PRP_VF_MEM这几个channel会调用到_ipu_csi_init函数,通过这个函数来设置CSI的目的地址。因为这几个channel需要使用到CSI设备,在使用之前需要先初始化以后才能使用。


打印出来的信息:

imx-ipuv32400000.ipu: *********************In _ipu_csi_init function !

imx-ipuv32400000.ipu: *********************Before : CSI_SENS_CONF =0x00008900

imx-ipuv32400000.ipu: *********************After : CSI_SENS_CONF = 0x02008900


可以看出来,这个寄存器中的2426位被设置为2L,与推断的一样。



之后是_ipu_ic_init_prpenc函数,

int _ipu_ic_init_prpenc(struct ipu_soc *ipu, ipu_channel_params_t *params, 
			bool src_is_csi) 
{ 
	uint32_t reg, ic_conf; 
	uint32_t downsizeCoeff, resizeCoeff; 
	ipu_color_space_t in_fmt, out_fmt; 
	int ret = 0; 

	/* Setup vertical resizing */ 
	if (!params->mem_prp_enc_mem.outv_resize_ratio) { 
		ret = _calc_resize_coeffs(ipu, 
					params->mem_prp_enc_mem.in_height, 
					params->mem_prp_enc_mem.out_height, 
					&resizeCoeff, &downsizeCoeff); 
		if (ret < 0) { 
			dev_err(ipu->dev, "failed to calculate prpenc height " 
				"scaling coefficients\n"); 
			return ret; 
		} 

		reg = (downsizeCoeff << 30) | (resizeCoeff << 16); 
	} else 
		reg = (params->mem_prp_enc_mem.outv_resize_ratio) << 16; 

	/* Setup horizontal resizing */ 
	if (!params->mem_prp_enc_mem.outh_resize_ratio) { 
		ret = _calc_resize_coeffs(ipu, params->mem_prp_enc_mem.in_width, 
					params->mem_prp_enc_mem.out_width, 
					&resizeCoeff, &downsizeCoeff); 
		if (ret < 0) { 
			dev_err(ipu->dev, "failed to calculate prpenc width " 
				"scaling coefficients\n"); 
			return ret; 
		} 

		reg |= (downsizeCoeff << 14) | resizeCoeff; 
	} else 
		reg |= params->mem_prp_enc_mem.outh_resize_ratio; 

	ipu_ic_write(ipu, reg, IC_PRP_ENC_RSC); 

	ic_conf = ipu_ic_read(ipu, IC_CONF); 

	/* Setup color space conversion */ 
	in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); 
	out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); 
	if (in_fmt == RGB) { 
		if ((out_fmt == YCbCr) || (out_fmt == YUV)) { 
			/* Enable RGB->YCBCR CSC1 */ 
			_init_csc(ipu, IC_TASK_ENCODER, RGB, out_fmt, 1); 
			ic_conf |= IC_CONF_PRPENC_CSC1; 
		} 
	} 
	if ((in_fmt == YCbCr) || (in_fmt == YUV)) { 
		if (out_fmt == RGB) { 
			/* Enable YCBCR->RGB CSC1 */ 
			_init_csc(ipu, IC_TASK_ENCODER, YCbCr, RGB, 1); 
			ic_conf |= IC_CONF_PRPENC_CSC1; 
		} else { 
			/* TODO: Support YUV<->YCbCr conversion? */ 
		} 
	} 

	if (src_is_csi) 
		ic_conf &= ~IC_CONF_RWS_EN; 
	else 
		ic_conf |= IC_CONF_RWS_EN; 

	ipu_ic_write(ipu, ic_conf, IC_CONF); 

	return ret; 
}

这个函数首先根据params->mem_prp_enc_mem里面保存的in_heightout_height等等的值通过_calc_resize_coeffs函数来计算出对应的resizeCoeffdownsizeCoeff,这两个值是垂直方向的系数。然后将in_widthout_width通过_calc_resize_coeffs函数来计算出对应的resizeCoeffdownsizeCoeff,这两个值是水平方向的系数。


打印出来的信息如下:

imx-ipuv32400000.ipu: *****************In _ipu_ic_init_prpenc function !

imx-ipuv32400000.ipu: *******Before : reg = 0.

imx-ipuv32400000.ipu: resizing from 288 -> 288 pixels, downsize=0,resize=1.0 (reg=8192)

imx-ipuv32400000.ipu: ***vertical*********resizeCoeff = 8192, downsizeCoeff =0.

imx-ipuv32400000.ipu: resizing from 352 -> 352 pixels, downsize=0,resize=1.0 (reg=8192)

imx-ipuv32400000.ipu: ***horizontal********resizeCoeff = 8192, downsizeCoeff =0.

imx-ipuv32400000.ipu: *******After : reg = 536879104.

imx-ipuv32400000.ipu: *************Before : IPU_CONF = 0x00000000

imx-ipuv32400000.ipu: *************After : IPU_CONF = 0x00000000


将计算出来这四个值保存在IC_PRP_ENC_RSC寄存器中,关于IC_PRP_ENC_RSC寄存器,如下所示:



首先经过第一个_calc_resize_coeffs函数计算,resizeCoeff= 8192, downsizeCoeff = 0,然后reg= (downsizeCoeff << 30) | (resizeCoeff << 16);

经过第二次_calc_resize_coeffs函数计算,resizeCoeff= 8192, downsizeCoeff = 0,然后reg|= (downsizeCoeff << 14) | resizeCoeff

对比上面的图,每一位的意义很清楚了。所以最后reg的值是536879104



写完IC_PRP_ENC_RSC寄存器后,就该写IC_CONF寄存器了。

根据params->mem_prp_enc_mem.in_pixel_fmtparams->mem_prp_enc_mem.out_pixel_fmt在这里,params->mem_prp_enc_mem.in_pixel_fmtparams->mem_prp_enc_mem.out_pixel_fmt都通过format_to_colorspace函数转化成RGB或者YCBCR格式,然后通过_init_csc函数决定使能RGB->YCBCRCSC1还是YCBCR->RGBCSC1_init_csc函数没有分析。然后将IC_CONF寄存器中的IC_CONF_PRPENC_CSC1置位,然后根据传入的src_is_csi参数来决定是否将IC_CONF_RWS_EN位置位,最后写入IC_CONF寄存器中。


打印信息如下:

imx-ipuv32400000.ipu: *************Before : IPU_CONF = 0x00000000

imx-ipuv32400000.ipu: *************After : IPU_CONF = 0x00000000


发现并没有修改这个IPU_CONF寄存器的值。



小总结:

可以看出来,在ipu_init_channel函数中,会根据对应的channel来决定使用哪个模块。

比如对于CSI_MEM0CSI_MEM3,就会使用SMFC_ipu_smfc_init函数),然后通过_ipu_csi_init函数来指定数据的目的地址。

对于CSI_PRP_ENC_MEMchannel,就会使用IC,使用的是ICprpenc功能,就需要使用_ipu_ic_init_prpenc函数,同样需要使用通过_ipu_csi_init函数来指定数据的目的地址。


对于CSI_PRP_VF_MEMchannel,需要使用到IC,但是使用的是ICprpvf功能,就需要使用_ipu_ic_init_prpvf函数。同样需要使用通过_ipu_csi_init函数来指定数据的目的地址。


上面几个函数都使用到了CSI设备,所以都使用了_ipu_csi_init函数来指定数据的目的地址。对于有些channel没有使用CSI设备,肯定就不用使用_ipu_csi_init这个函数了。


比如对于MEM_VDI_MEMchannel,它使用到VDI模块,只需要通过_ipu_vdi_init函数来设置即可。

比如MEM_ROT_VF_MEMchannel,它需要使用到ICrotateviewfinder功能,就需要通过_ipu_ic_init_rotate_vf函数来设置了。



然后就是ipu_init_channel_buffer函数了,关于这个函数里面每个IDMACchannel2words每一位的介绍在手册的:37.4.2.10CPMEM - Channel Parameter Memory这一章介绍。


在之前的打印信息中打印过这个函数传入参数的值:

imx-ipuv32400000.ipu: ipu_init_channel_buffer func entry: channel = 19,pixel_fmt = 808596553, width = 352, height = 288, stride = 352, u =0, v = 0.

imx-ipuv32400000.ipu: phyaddr0 = 1008467968, phyaddr1 = 1008467968, phyaddr2 =0.

imx-ipuv32400000.ipu: ipu_init_channel_buffer : dma_chan = 20

imx-ipuv32400000.ipu: initializing idma ch 20 @ c0900500

imx-ipuv32400000.ipu: _ipu_ch_param_init func entry: dma_chan = 20, pixel_fmt= 808596553, width = 352, height = 288, stride = 352, u = 0, v = 0.

imx-ipuv32400000.ipu: phyaddr0 = 1008467968, phyaddr1 = 1008467968, phyaddr2 =0.

imx-ipuv32400000.ipu: ipu_init_channel_buffer : burst_size = 16.


在这个ipu_init_channel_buffer函数中,dma_chan= 20, 通过_ipu_is_ic_chan函数,可以判断出这个dma_chicchannel。之后就会调用_ipu_ch_param_init函数。


对于这个word填充的方式,在《ipu_param_mem.h头文件分析》中已经分析过了,主要就是用的小端方式来填充的。

关于这个小端方式,可以这样理解:

160位为例:


将需要填充的十进制数转换成二进制的,然后按照图中位的位置来填充。

比如,在_ipu_ch_param_init函数中,

_ipu_ch_param_init(ipu,dma_chan, pixel_fmt, width, height, stride, u, v, 0,

phyaddr_0, phyaddr_1, phyaddr_2);

传入的参数分别为

_ipu_ch_param_init(ipu,20,808596553,352,288,352,0,0,0,

1008467968,1008467968,0);

然后在_ipu_ch_param_init函数中分别设置了以下的值:

其中某些值都是经过计算的:

pixel_fmt的值等于808596553(十进制),转换成十六进制后与ASCII码做比较,得出fourcc码为'I''4' '2' '0',对应的pixel_fmt格式为IPU_PIX_FMT_YUV420P

uv_stride= stride / 2 = 176

u_offset= stride * height; ==> u_offset = 352×288=101376;

v_offset= u_offset + (uv_stride * height / 2); ==>

v_offset= 101376 + (176*288/2)=126720;

bs= 31;

对应的word设置如下:

word     位        值    值所对应的参数名称

0     125137    351       width-1

0     138150    287       height-1

0     4667     12672      u_offset/8

0     6889     15840      v_offset/8


word      位         值           值所对应的参数名称

1     102115      351           stride-1

1       028    126058496        addr0>>3 addr0= 1008467968

1      2957    126058496        addr1>>3 addr1= 1008467968

1      8588        2            像素格式为420

1      7884       31            bs

1      128141    175            uv_stride-1


根据上面关于小端方式的理解,我们可以自己填充这个160位的word

注意填充的时候,需要将填充的数字转换成二进制,从0位开始按顺序填充,填充程序中指定的位数。(这种情况只是我们手工填充时候的一个简便方法,对于在驱动程序中填充这种数据,需要使用驱动中ipu_param_mem.h文件中提供的函数来填充。)

首先填充word0:(下面几个数字的表示是从右到左依次为031位,它们与内存中的存储方式相同,都是小端模式,0位在最右边,31位在最左边)



图中黑色的背景的部分对应打印出来的word0的值,

为:00000000 0C60 0000 0003 DE00 E000 0000 0004 7C2B

可以对比看出来,分析是正确的。



下面分析word1

对于word1,更能好好理解数字在内存中是怎么存储的。

比如addr0addr1,它们的值是1008467968,它的二进制表示如下:

|31                           0|

00111100 0001 1100 0000 0000 0000 0000

word1中保存的是addr0addr1右移3位以后的值,如果将它右移3位的话,就直接在内存中右移3位,丢弃最右边3位,左边用0填充(右移的话就直接按照内存中保存的位置直接移动):

|31                          0|

00000111 1000 0011 1000 0000 0000 0000


(下面几个数字的表示是从右到左依次为031位,它们与内存中的存储方式相同,都是小端模式,0位在最右边,31位在最左边)


图中黑色的背景的部分对应打印出来的word1的值,

为:07838000 00F0 7000 0047 C000 0000 57C0 0000 00AF

对比打印出来的word1的值,分析是正确的。



之后通过下面两个语句修改了word[1]里面的值:

_ipu_ch_param_set_burst_size(ipu,dma_chan, 16);

ipu_ch_param_mod_field_io(ipu_ch_param_addr(ipu,ch), 1, 78, 7, 15);

修改word[1]里面的7884位为0001111(小端模式);


_ipu_ch_param_set_axi_id(ipu,dma_chan, ipu->normal_axi);

ipu_ch_param_mod_field_io(ipu_ch_param_addr(ipu,ch), 1, 93, 2, id);

修改word[1]里面的9394位为id

从打印信息中可以推断出id=10_ipu_ch_param_set_axi_id(ipu,dma_chan, ipu->normal_axi)函数中这个ipu->normal_axi这个值是在ipu_probe函数中通过ipu->normal_axi= iputype->normal_axi;设置的,而这个iputype->normal_axi是从staticstruct ipu_platform_type ipu_type_imx6q中获取到的,从源码中可以看到.normal_axi= 1,所以93位为1,94位为0,与推断出来的一致。


修改后word[1]里面的数字就如下所示了:

这是打印信息:

imx-ipuv32400000.ipu: ********* In ipu_init_channel_buffer function!********

imx-ipuv32400000.ipu: initializing idma ch 20 @ c0900500

imx-ipuv32400000.ipu: **********Before : after the _ipu_ch_param_initfunction. ************

imx-ipuv32400000.ipu: ch 20 word 0 - 00000000 0C600000 0003DE00 E000000000047C2B

imx-ipuv32400000.ipu: ch 20 word 1 - 07838000 00F07000 0047C000000057C0 000000AF

imx-ipuv32400000.ipu: PFS 0x2,

imx-ipuv32400000.ipu: BPP 0x0,

imx-ipuv32400000.ipu: NPB 0x1f

imx-ipuv32400000.ipu: FW 351,

imx-ipuv32400000.ipu: FH 287,

imx-ipuv32400000.ipu: EBA0 0x3c1c0000

imx-ipuv32400000.ipu: EBA1 0x3c1c0000

imx-ipuv32400000.ipu: Stride 351

imx-ipuv32400000.ipu: scan_order 0

imx-ipuv32400000.ipu: uv_stride 175

imx-ipuv32400000.ipu: u_offset 0x18c00

imx-ipuv32400000.ipu: v_offset 0x1ef00

imx-ipuv32400000.ipu: Width0 0+1,

imx-ipuv32400000.ipu: Width1 0+1,

imx-ipuv32400000.ipu: Width2 0+1,

imx-ipuv32400000.ipu: Width3 0+1,

imx-ipuv32400000.ipu: Offset0 15,

imx-ipuv32400000.ipu: Offset1 5,

imx-ipuv32400000.ipu: Offset2 0,

imx-ipuv32400000.ipu: Offset3 0

imx-ipuv32400000.ipu: *****************After : ***************************

imx-ipuv32400000.ipu: ch 20 word 0 - 00000000 0C600000 0003DE00 E000000000047C2B

imx-ipuv32400000.ipu: ch 20 word 1 - 07838000 00F07000 2043C000000057C0 000000AF

imx-ipuv32400000.ipu: PFS 0x2,

imx-ipuv32400000.ipu: BPP 0x0,

imx-ipuv32400000.ipu: NPB 0xf

imx-ipuv32400000.ipu: FW 351,

imx-ipuv32400000.ipu: FH 287,

imx-ipuv32400000.ipu: EBA0 0x3c1c0000

imx-ipuv32400000.ipu: EBA1 0x3c1c0000

imx-ipuv32400000.ipu: Stride 351

imx-ipuv32400000.ipu: scan_order 0

imx-ipuv32400000.ipu: uv_stride 175

imx-ipuv32400000.ipu: u_offset 0x18c00

imx-ipuv32400000.ipu: v_offset 0x1ef00

imx-ipuv32400000.ipu: Width0 0+1,

imx-ipuv32400000.ipu: Width1 0+1,

imx-ipuv32400000.ipu: Width2 0+1,

imx-ipuv32400000.ipu: Width3 0+1,

imx-ipuv32400000.ipu: Offset0 15,

imx-ipuv32400000.ipu: Offset1 5,

imx-ipuv32400000.ipu: Offset2 0,

imx-ipuv32400000.ipu: Offset3 0


下面要分析两个word中每一位的含义。


首先要确定数据是隔行还是非隔行模式的,因为对于每种模式,这个CPMEM各个位所表示的意义是不同的,这两种模式的区别如下(在37.4.2.10CPMEM - Channel Parameter Memory这一节):



对于这两种模式,IPU根据CPMEM里面的PixelFormat Select(PFS)位来决定是哪一种模式。这个PFS位于W1[88:85]

下面这个是非隔行模式的W1[88:85]


下面这个是隔行模式的W1[88:85]


总结一下,这W1[88:85]总共有4位,也就是说最多可以表示16种不同的数据格式。IPU就是根据4位来确定使用哪个CPMEM。但是这几位是在什么时候设置的??是在_ipu_ch_param_init这个函数中,根据传入的pixel_fmt的值,根据不同的case设置的。

从打印信息中可以看出来,这几位为:0100,所以应该为partialinterleaved 4:2:0,非隔行模式。


以下就以这个模式来分析,从上面的代码可以发现,这个CPMEM的值,都是在ipu_init_channel_buffer函数中设置的,其中大部分是在ipu_init_channel_buffer函数里面的_ipu_ch_param_init函数设置的,那就根据这两个函数的流程来追踪:

(这两个函数传入的值,已经在上面分析了,粘贴出来)

imx-ipuv32400000.ipu: ipu_init_channel_buffer func entry: channel = 19,pixel_fmt = 808596553, width = 352, height = 288, stride = 352, u =0, v = 0.

imx-ipuv32400000.ipu: phyaddr0 = 1008467968, phyaddr1 = 1008467968, phyaddr2 =0.

imx-ipuv32400000.ipu: ipu_init_channel_buffer : dma_chan = 20

imx-ipuv32400000.ipu: initializing idma ch 20 @ c0900500

imx-ipuv32400000.ipu: _ipu_ch_param_init func entry: dma_chan = 20, pixel_fmt= 808596553, width = 352, height = 288, stride = 352, u = 0, v = 0.

imx-ipuv32400000.ipu: phyaddr0 = 1008467968, phyaddr1 = 1008467968, phyaddr2 =0.

imx-ipuv32400000.ipu: ipu_init_channel_buffer : burst_size = 16.

_ipu_ch_param_init函数中,

_ipu_ch_param_init(ipu,dma_chan, pixel_fmt, width, height, stride, u, v, 0,

phyaddr_0, phyaddr_1, phyaddr_2);

传入的参数分别为

_ipu_ch_param_init(ipu,20,808596553,352,288,352,0,0,0,

1008467968,1008467968,0);

然后在_ipu_ch_param_init函数中分别设置了以下的值:

其中某些值都是经过计算的:

pixel_fmt的值等于808596553(十进制),转换成十六进制后与ASCII码做比较,得出fourcc码为'I''4' '2' '0',对应的pixel_fmt格式为IPU_PIX_FMT_YUV420P

uv_stride= stride / 2 = 176

u_offset= stride * height; ==> u_offset = 352×288=101376;

v_offset= u_offset + (uv_stride * height / 2); ==>

v_offset= 101376 + (176*288/2)=126720;

bs= 31;


首先,在_ipu_ch_param_init函数中,

1.通过memset(&params,0, sizeof(params));

来将params中的值都清零。这个paramsstructipu_ch_param类型的,即代表这两个word


2.ipu_ch_param_set_field(&params,0, 125, 13, width - 1);

这一步是设置Width,将word[0]里面的125137位设置成width-1。图中的解释是一行中的像素个数。


3.

	if (((ch == 8) || (ch == 9) || (ch == 10)) && !ipu->vdoa_en) { 
		ipu_ch_param_set_field(&params, 0, 138, 12, (height / 2) - 1); 
		ipu_ch_param_set_field(&params, 1, 102, 14, (stride * 2) - 1); 
	} else { 
		/* note: for vdoa+vdi- ch8/9/10, always use band mode */ 
		ipu_ch_param_set_field(&params, 0, 138, 12, height - 1); 
		ipu_ch_param_set_field(&params, 1, 102, 14, stride - 1); 
	}

这一步就是设置HeightStride,其中heightword[0]中的138149位,strideword[1]中的102115位,对于dma_ch8/9/10的话,保存的(height/ 2) - 1)(stride* 2) – 1)。原因估计跟idmachannel的原理有关。以后分析。



 

4.

ipu_ch_param_set_field(&params,1, 0, 29, addr0 >> 3);

ipu_ch_param_set_field(&params,1, 29, 29, addr1 >> 3);

这一步填充addr0addr1,word[1]028位为addr0,word[1]2957位为addr1.


5.switch (pixel_fmt)

根据传入的pixel_fmt的值来选择一个case来执行,pixel_fmt的值等于808596553(十进制),转换成十六进制后与ASCII码做比较,得出fourcc码为'I''4' '2' '0',对应的pixel_fmt格式为IPU_PIX_FMT_YUV420P

case IPU_PIX_FMT_YUV420P: 
		ipu_ch_param_set_field(¶ms, 1, 85, 4, 2);	/* pix format */ 

		if (uv_stride < stride / 2) 
			uv_stride = stride / 2; 

		u_offset = stride * height; 
		v_offset = u_offset + (uv_stride * height / 2); 
		if ((ch == 8) || (ch == 9) || (ch == 10)) { 
			ipu_ch_param_set_field(¶ms, 1, 78, 7, 15);  /* burst size */ 
			uv_stride = uv_stride*2; 
		} else { 
			if (_ipu_is_smfc_chan(ch) && 
				ipu->smfc_idmac_12bit_3planar_bs_fixup) 
				bs = 15; 
			else 
				bs = 31; 
			ipu_ch_param_set_field(¶ms, 1, 78, 7, bs);  /* burst size */ 
		} 
		break;

在这里面,首先对于burstsize

对于uv_stride


对于u_offset

对于v_offset


先看看上面这几个图中的解释,然后再具体分析几个术语的含义:

widthNumberof pixels in one row一行中的像素数,单位是pixels


heightNumberof pixels in one column,一列中的像素数,即一列中的行数,单位是lines


strideAddressvertical scaling factor in bytes for memory access.内存中垂直方向上的缩放因子。Alsonumber of maximum bytes in the "Y" component row accordingto memory limitations. 同样也是Y”元素在一行上的bytes。可以简单理解为bytesper line.

下面要分析u_offset,v_offsetuv_offset,在这之前需要理解Y:U:V的简单知识,关于这一块的知识可以看《几种常见的yuv格式.pdf

http://blog.sina.com.cn/s/blog_820338290100zeci.html

关于这一方面的资料很多,我暂时先找到这个适合我们的分析。


由于我们函数执行到这里的时候,对应的pixel_fmt格式为IPU_PIX_FMT_YUV420P类型,所以Y:U:V几种元素存储方式如下所示:


uv_stride对比上面stride的定义,大致就知道uv_stride的意义了:U或者V元素一行上的bytes数。这个数目是基于pixel_fmt格式的,不同的格式这个值的计算方式不同,对于这个Y:U:V420格式来说,uv_stride就直接等于stride/2即可。



u_offsetDoublebuffer destination address offset for Y:U:V (U pointer) formats.buffer模式下U元素目的地址偏移值。通过上图可以看出来,这个

u_offset= stride * height;数据会按上图那种方式存放。


v_offsetDoublebuffer destination address offset for Y:U:V (V pointer) format.buffer模式下V元素目的地址的偏移值。通过上图可以看出来,这个

v_offset= u_offset + (uv_stride * height /2)u_offset加上U元素所占的字节数就是V元素的偏移值,从上图可以清楚地看出来)。

上面这几个值都是与pixel_fmt所相关的概念,都与pixel_fmt息息相关,对于不同的pixel_fmt计算方式不同。



下面分析这个burstsize的含义,关于这个burstsizeDMA的相关知识,可以参考《DMAburst基本概念.pdf》和DMA使用的几个概念,burst.pdf

http://blog.csdn.net/sunjiajiang/article/details/7945057

http://blog.sina.com.cn/s/blog_533074eb0101e277.html


先理解cache的作用
CPU
在访问内存时,首先判断所要访问的内容是否在Cache中,如果在,就称为“命中(hit)”,此时CPU直接从Cache中调用该内容;否则,就称为“不命中”,CPU只好去内存中调用所需的子程序或指令了。CPU不但可以直接从Cache中读出内容,也可以直接往其中写入内容。由于Cache的存取速率相当快,使得CPU的利用率大大提高,进而使整个系统的性能得以提升。

Cache
的一致性就是指Cache中的数据,与对应的内存中的数据是一致的。

DMA
是直接操作总线地址的,这里先当作物理地址来看待吧(系统总线地址和物理地址只是观察内存的角度不同)。如果cache缓存的内存区域不包括DMA分配到的区域,那么就没有一致性的问题。但是如果cache缓存包括了DMA目的地址的话,会出现什么什么问题呢?

问题出在,经过DMA操作,cache缓存对应的内存数据已经被修改了,而CPU本身不知道(DMA传输是不通过CPU的),它仍然认为cache中的数据就是内存中的数据,以后访问Cache映射的内存时,它仍然使用旧的Cache数据。这样就发生Cache与内存的数据“不一致性”错误。


由上面可以看出,DMA如果使用cache,那么一定要考虑cache的一致性。解决DMA导致的一致性的方法最简单的就是禁止DMA目标地址范围内的cache功能。但是这样就会牺牲部分性能。


因此在DMA是否使用cache的问题上,可以根据DMA缓冲区期望保留的的时间长短来决策。DMA的映射就分为:一致性DMA映射和流式DMA映射。


使用一致性DMA映射的函数有dma_alloc_coherent函数和dma_alloc_writecombine两个函数这两个函数都是一致性DMA


burstDMA实际上是一次一次的申请总线,把要传的数据总量分成一个一个小的数据块。比如要传64个字节,那么DMA内部可能分为2次,一次传64/2=32个字节,这个2(a)次呢,就叫做burst。这个burst是可以设置的。这32个字节又可以分为32*8或者16*16来传输。

transfersize:就是数据宽度,比如8位、32位,一般跟外设的FIFO相同。

burstsize就是一次传几个transfersize.

所以对应不同的pixel_fmt,以及根据dma_ch的值来选定一个对应的burstsize的值。比如对应这个IPU_PIX_FMT_YVU420P,同时dma_ch20,选定的bs值就是31


回顾函数的调用过程,在prp_enc_enabling_tasks函数中调用了dma_alloc_coherent函数为cam->dummy_frame.vaddress分配了DMA空间,然后又调用了prp_enc_setup函数,在这个函数中调用了ipu_init_channel函数,同时调用了ipu_init_channel_buffer函数,然后在ipu_init_channel_buffer函数里面的_ipu_ch_param_init函数指定了burstsize的值


最后调用fill_cpmem(ipu,ch, &params);函数来将设定好的params的值填充到对应的word里面。


跳出_ipu_ch_param_init函数,继续回到ipu_init_channel_buffer函数中,

if (_ipu_is_ic_chan(dma_chan) || _ipu_is_vdi_out_chan(dma_chan)) { 
		if ((width % 16) == 0) 
			_ipu_ch_param_set_burst_size(ipu, dma_chan, 16); 
		else 
			_ipu_ch_param_set_burst_size(ipu, dma_chan, 8); 
	}

这个dma_ch号是20,是ic_chan,同时width=352,所以会调用到_ipu_ch_param_set_burst_size(ipu,dma_chan, 16); 这一句

继续调用到ipu_ch_param_mod_field_io(ipu_ch_param_addr(ipu,ch), 1, 78, 7,

burst_pixels - 1);

所以最终设置的是ipu_ch_param_mod_field_io(ipu_ch_param_addr(ipu,ch), 1, 78, 7, 15);修改word[1]里面的7884位为0001111(小端模式);又重新设置burstsize = 16

if (_ipu_is_ic_chan(dma_chan) || _ipu_is_irt_chan(dma_chan) || 
		_ipu_is_vdi_out_chan(dma_chan)) { 
		burst_size = _ipu_ch_param_get_burst_size(ipu, dma_chan); 
		_ipu_ic_idma_init(ipu, dma_chan, width, height, burst_size, 
			rot_mode); 
	}

然后通过_ipu_ch_param_get_burst_size(ipu,dma_chan);函数来读取对应dma_chword[1]7884位来获取到burstsize的值保存在burst_size,调用_ipu_ic_idma_init函数来设置IC里面的IDMA通道里面的burstsize


设置完burstsize以后,因为IC需要用到IDMA,所以还需要设置IC里面的IDMA,就是通过这个函数来设置的。在这个函数中需要设置3个寄存器的值,分别为IC_IDMAC_1,IC_IDMAC_2IC_IDMAC_3

_ipu_ic_idma_init函数入口处打印信息如下:

imx-ipuv32400000.ipu: *****************In _ipu_ic_idma_init function !

imx-ipuv32400000.ipu: Entry : dma_chan = 20, width = 352, height = 288,burst_size = 16, rot = 0.

在之后的设置中,先将width--height--再设置的。


代码如下:

if (dma_chan == 20) {	/* PRP ENC output - CB0 */ 
		if (burst_size == 16) 
			ic_idmac_1 |= IC_IDMAC_1_CB0_BURST_16; 
		else 
			ic_idmac_1 &= ~IC_IDMAC_1_CB0_BURST_16; 

		if (need_hor_flip) 
			ic_idmac_1 |= IC_IDMAC_1_PRPENC_FLIP_RS; 
		else 
			ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_FLIP_RS; 

		ic_idmac_2 &= ~IC_IDMAC_2_PRPENC_HEIGHT_MASK; 
		ic_idmac_2 |= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET; 

		ic_idmac_3 &= ~IC_IDMAC_3_PRPENC_WIDTH_MASK; 
		ic_idmac_3 |= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET; 

	} 

这三个寄存器分别如下所示:

IC_IDMAC_1寄存器:




IC_IDMAC_1_CB0_BURST_16= 1, 0位设置成IC_IDMAC_1_CB0_BURST_16

打印信息如下:

imx-ipuv32400000.ipu: *****************Before : IC_IDMAC_1 = 0x00000001

imx-ipuv32400000.ipu: *****************After : IC_IDMAC_1 = 0x00000001

推论是正确的。


IC_IDMAC_2寄存器:


ic_idmac_2|= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET;

IC_IDMAC_2_PRPENC_HEIGHT_OFFSET= 0,

所以将height= 287设置到09位上面,打印信息如下:

imx-ipuv32400000.ipu: *****************Before : IC_IDMAC_2 = 0x0000011F

imx-ipuv32400000.ipu: *****************After : IC_IDMAC_2 = 0x0000011F


设置的是Encoding情况下的frameheight


IC_IDMAC_3寄存器:

设置的是Ecoding情况下的framewidth

ic_idmac_3|= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET;

IC_IDMAC_3_PRPENC_WIDTH_OFFSET= 0,

所以将width= 351设置到09位上面,打印信息如下:

imx-ipuv32400000.ipu: *****************Before : IC_IDMAC_3 = 0x0000015F

imx-ipuv32400000.ipu: *****************After : IC_IDMAC_3 = 0x0000015F


通过这个函数发现,在知道使用IC的情况下,都需要使用_ipu_ic_idma_init函数来设置IDMA的一些信息。


通过这个if...else...语句可以看出来,对于ic_chan/irt_chan/vdi_chan都需要设置IC,而对于smfc_chan就不需要设置IC了,就需要调用_ipu_smfc_set_burst_size函数来设置SMFC里面的burstsize了。


之后会根据dma_chan语句来调用到_ipu_ch_param_set_axi_id(ipu,dma_chan,ipu->normal_axi);函数,继续调用到ipu_ch_param_mod_field_io(ipu_ch_param_addr(ipu,ch), 1, 93, 2, id);修改word[1]里面的9394位为id

这个axi也是内核中的一种总线,在IPU那个图中可以看出来。


之后是

if(idma_is_set(ipu, IDMAC_CHA_PRI(dma_chan), dma_chan) &&

ipu->devtype == IPUv3H) {

里面修改了reg= IDMAC_CH_LOCK_EN_1(ipu->devtype);这个寄存器的值,没有打印语句,暂时没有分析。



跳出ipu_init_channel_buffer函数回到prp_enc_setup函数中,还有一个ipu_enable_channel函数,先来看打印出来的信息:

imx-ipuv32400000.ipu: ********* In ipu_enable_channel function!********

imx-ipuv32400000.ipu: ****************out_dma = 20, in_dma = 63.**************

imx-ipuv32400000.ipu: ************Before : ****************

imx-ipuv32400000.ipu: IPU_CONF = 0x00000000

imx-ipuv32400000.ipu: ************ After: ****************

imx-ipuv32400000.ipu: IPU_CONF = 0x00000004

imx-ipuv32400000.ipu: ************Before : ****************

imx-ipuv32400000.ipu: IDMAC_CHA_EN(out_dma) = 0x00000000

imx-ipuv32400000.ipu: ************ After: ****************

imx-ipuv32400000.ipu: IDMAC_CHA_EN(out_dma) = 0x00100000


在这个函数中,会计算出out_dmain_dma的值,打印出来了:

out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); 
	in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER);

然后就是

ipu_conf = ipu_cm_read(ipu, IPU_CONF);
if (ipu->ic_use_count > 0) 
		ipu_conf |= IPU_CONF_IC_EN;
ipu_cm_write(ipu, ipu_conf, IPU_CONF);

看打印出来的信息,我们就发现只有第3位置位了,ipu_conf这个寄存器各个位的含义如下:

说明只使能了IC,符合我们的流程。


然后通过

if (idma_is_valid(in_dma)) { 
		reg = ipu_idmac_read(ipu, IDMAC_CHA_EN(in_dma)); 
		ipu_idmac_write(ipu, reg | idma_mask(in_dma), IDMAC_CHA_EN(in_dma)); 
	} 
	if (idma_is_valid(out_dma)) { 
		reg = ipu_idmac_read(ipu, IDMAC_CHA_EN(out_dma)); 
		ipu_idmac_write(ipu, reg | idma_mask(out_dma), IDMAC_CHA_EN(out_dma)); 
	}

来将使用到的dmachannel置位。

#defineidma_mask(ch) (idma_is_valid(ch) ? (1UL << (ch & 0x1F)) :0)

#defineIDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32))

#defineIPU_IDMAC_REG(offset) (offset)

所以会将1<<20位后写到IDMAC_CH_EN_1寄存器中。同时对比打印出来的信息,IDMAC_CHA_EN(out_dma)= 0x00100000,确实是正确的,将IDMAC_CH_EN_20置位了。

但是在in_dma那里也添加了打印信息,但是没有打印出来。显示的in_dma= 63


之后通过

		if (_ipu_is_ic_chan(in_dma) || _ipu_is_ic_chan(out_dma) || 
		_ipu_is_irt_chan(in_dma) || _ipu_is_irt_chan(out_dma) || 
		_ipu_is_vdi_out_chan(out_dma)) 
		_ipu_ic_enable_task(ipu, channel);

来调用到_ipu_ic_enable_task函数,在这个函数中只是根据channel来将ic_conf寄存器中的某一位置位,这个函数在ipu_ic.c


打印信息如下:

imx-ipuv32400000.ipu: *****************In _ipu_ic_enable_task function !

imx-ipuv32400000.ipu: ************** Before : ic_conf = 0x00000000

imx-ipuv32400000.ipu: ************** After : ic_conf = 0x00000001


ic_conf|= IC_CONF_PRPENC_EN;

IC_CONF_PRPENC_EN= 0x00000001,

所以这一步也是正确的。


至此,mxc_streamon函数中的cam->enc_enable(cam)才执行完毕。


17.2

cam->enc_update_eba(cam,frame->buffer.m.offset);

prp_enc_eba_update函数:

static int prp_enc_eba_update(void *private, dma_addr_t eba) 
{ 
	int err = 0; 
	cam_data *cam = (cam_data *) private; 
	struct ipu_soc *ipu = cam->ipu; 
	int *buffer_num = &cam->ping_pong_csi; 

	pr_debug("eba %x\n", eba); 
	if (grotation >= IPU_ROTATE_90_RIGHT) { 
		err = ipu_update_channel_buffer(ipu, MEM_ROT_ENC_MEM, 
						IPU_OUTPUT_BUFFER, *buffer_num, 
						eba); 
	} else { 
		err = <span style="color:#FF0000;">ipu_update_channel_buffer</span>(ipu, CSI_PRP_ENC_MEM, 
						IPU_OUTPUT_BUFFER, *buffer_num, 
						eba); 
	} 
	if (err != 0) { 
		if (grotation >= IPU_ROTATE_90_RIGHT) { 
			ipu_clear_buffer_ready(ipu, MEM_ROT_ENC_MEM, 
					       IPU_OUTPUT_BUFFER, 
					       *buffer_num); 
			err = ipu_update_channel_buffer(ipu, MEM_ROT_ENC_MEM, 
							IPU_OUTPUT_BUFFER, 
							*buffer_num, 
							eba); 
		} else { 
			ipu_clear_buffer_ready(ipu, CSI_PRP_ENC_MEM, 
					       IPU_OUTPUT_BUFFER, 
					       *buffer_num); 
			err = ipu_update_channel_buffer(ipu, CSI_PRP_ENC_MEM, 
							IPU_OUTPUT_BUFFER, 
							*buffer_num, 
							eba); 
		} 

		if (err != 0) { 
			pr_err("ERROR: v4l2 capture: fail to update " 
			       "buf%d\n", *buffer_num); 
			return err; 
		} 
	} 

	if (grotation >= IPU_ROTATE_90_RIGHT) { 
		ipu_select_buffer(ipu, MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, 
				  *buffer_num); 
	} else { 
		<span style="color:#FF0000;">ipu_select_buffer</span>(ipu, CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 
				  *buffer_num); 
	} 

	*buffer_num = (*buffer_num == 0) ? 1 : 0; 
	return 0; 
}

mxc_streamon函数中,会调用两次下面的代码来分别设置phyaddr_0phyaddr_1

		frame = list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); 
		list_del(cam->ready_q.next); 
		list_add_tail(&frame->queue, &cam->working_q); 
		frame->ipu_buf_num = cam->ping_pong_csi; 
		err = cam->enc_update_eba(cam, frame->buffer.m.offset);

这两个地址值打印出来了,如下所示:

Firsttime : frame->buffer.m.offset = 1007681536.

Secondtime : frame->buffer.m.offset = 1007943680.


最终会调用到prp_enc_eba_update函数,在这个函数里面就会调用到ipu_update_channel_buffer函数和ipu_select_buffer函数来设置phyaddr_0phyaddr_1


首先从cam->ready_q队列中取出第一个buffer,将它的buffer.m.offset填充到word中,先来看ipu_update_channel_buffer函数,会根据传入的bufNum参数的值来决定读取哪一个寄存器的值,这个bufNum参数就是prp_enc_eba_update函数里面的

int*buffer_num = &cam->ping_pong_csi;它被初始化为0.

然后重要的一个函数是_ipu_ch_param_set_buffer,通过这个函数来根据dma_chan的值来设置其中CPMEM里面word的值。

在之前word的分析里面,word[1]里面的0282958位存放的是地址值。它是在ipu_init_channel_buffer函数中将phyaddr_0phyaddr_1填充进去的,现在要更新的就是这两个地址值。

之后是ipu_select_buffer函数,在之前ipu_update_channel_buffer函数中根据bufNum参数的值来读取到的寄存器的值,在ipu_select_buffer函数中同样根据bufNum参数通过ipu_cm_write函数来将寄存器的值写进去。


下面再来说说mxc_streamon函数中怎么通过调用两次相同的代码来更新了phyaddr_0phyaddr_1。首先在mxc_streamon函数中有一个重要的变量cam->ping_pong_csi= 0;然后在第一次调用cam->enc_update_eba(cam,frame->buffer.m.offset); 的时候,来将frame->buffer.m.offset作为phyaddr_0通过ipu_update_channel_buffer函数写到word[1]029位上去。具体实现是:

prp_enc_eba_update(void*private, dma_addr_t eba)

int*buffer_num = &cam->ping_pong_csi;

err= ipu_update_channel_buffer(ipu, CSI_PRP_ENC_MEM,

IPU_OUTPUT_BUFFER,*buffer_num, eba);

_ipu_ch_param_set_buffer(ipu,dma_chan, bufNum, phyaddr);

ipu_ch_param_mod_field_io(ipu_ch_param_addr(ipu,ch), 1,

29* bufNum, 29, phyaddr / 8);


之后在prp_enc_eba_update函数中,通过*buffer_num= (*buffer_num == 0) ? 1 :0;来设置mxc_streamon函数中的cam->ping_pong_csi这个值。如果这个值等于0的话,现在将它设置为1;如果等于1的话,就将它设置为0.这样的话,再第二次调用相同的代码的时候,重复上面这个过程,在最后通过ipu_ch_param_mod_field_io函数写到word[1]里面的时候,就会根据29* bufNum来指定写到028还是2958了。


同时也正是因为这个cam->ping_pong_csi01之间跳跃,所以才给它起了这样一个名字吧,ping_pong乒乓球的意思。


注意,在第二次更新地址的时候,系统又再次通过代码从cam->ready_q队列中取出第一个buffer,更新的是这个buffer.m.offset地址值。


但是在这也有很多不理解的地方,首先是只执行了两次这个enc_update_eba函数,更新了两个地址,后面的buffer怎么更新地址?还是后面的buffer不需要更新地址,只需要继续往这两个地址存放就行,填充满的话就使用DQBUF来将buffer退出队列??


后来分析清楚了这个流程:在使用过程中,如果填满一个buffer的话,内核中就会产生一个中断,而这个中断就是在ipu_request_irq中申请的中断,一般是一个EOF中断,然后就会调用到申请的中断处理函数prp_enc_callback,这个函数是在init_camera_struct中通过cam->enc_callback=camera_callback,在camera_callback函数中,会将cam->working_q队列中的buffer删除,然后添加到cam->done_q队列中。如果这时候还有buffer的话,就会继续调用cam->enc_update_eba函数来更新CPMEM中的地址,继续填充数据。


具体的分析可以查看《应用程序和驱动程序中buf的传输流程.pdf》这个文件。



17.3

err= cam->enc_enable_csi(cam);

prp_enc_enable_csi函数:

static int prp_enc_enable_csi(void *private) 
{ 
	cam_data *cam = (cam_data *) private; 

	return ipu_enable_csi(cam->ipu, cam->csi); 
}

int32_t ipu_enable_csi(struct ipu_soc *ipu, uint32_t csi) 
{ 
	uint32_t reg; 

	if (csi > 1) { 
		dev_err(ipu->dev, "Wrong csi num_%d\n", csi); 
		return -EINVAL; 
	} 

	_ipu_get(ipu); 
	mutex_lock(&ipu->mutex_lock); 
	ipu->csi_use_count[csi]++; 

	if (ipu->csi_use_count[csi] == 1) { 
		reg = ipu_cm_read(ipu, IPU_CONF); 
		if (csi == 0) 
			ipu_cm_write(ipu, reg | IPU_CONF_CSI0_EN, IPU_CONF); 
		else 
			ipu_cm_write(ipu, reg | IPU_CONF_CSI1_EN, IPU_CONF); 
	} 
	mutex_unlock(&ipu->mutex_lock); 
	_ipu_put(ipu); 
	return 0; 
} 
EXPORT_SYMBOL(ipu_enable_csi);

这个就是使能置位,很简单。



18.VIDIOC_DQBUFioctl调用

在应用程序中:

while (count-- > 0) 
{ 
	memset(&buf, 0, sizeof (buf)); 
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	buf.memory = V4L2_MEMORY_MMAP; 
	if (ioctl (fd_v4l, VIDIOC_DQBUF, &buf) < 0)	{ 
          printf("VIDIOC_DQBUF failed.\n"); 
}
fwrite(buffers[buf.index].start, fmt.fmt.pix.sizeimage, 1, fd_y_file);


驱动程序中:

	frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); 
	list_del(cam->done_q.next);
	cam->frame[frame->index].buffer.field = cam->device_type ? 
				V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; 

	buf->bytesused = cam->v2f.fmt.pix.sizeimage; 
	buf->index = frame->index; 
	buf->flags = frame->buffer.flags; 
	buf->m = cam->frame[frame->index].buffer.m; 
	buf->timestamp = cam->frame[frame->index].buffer.timestamp; 
	buf->field = cam->frame[frame->index].buffer.field;

驱动程序中将cam->done_q.next队列中已经填充好的数据buffer的地址指针从cam->frame[frame->index]中复制给buf这个中间变量,然后这个buf返回给应用程序中,应用程序就获得了这个buffer的地址,然后调用fwrite函数,就可以将数据写到fd_y_file中了。


至此,追踪应用程序分析函数的执行过程就分析完毕。


  • 2
    点赞
  • 1
    评论
  • 4
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值