对于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\n", 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参数,在应用程序中进行了如下的设置:
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<
5)如果capturemode改变的话,crop的边界也会发生变化,所以通过vidioc_int_g_fmt_cap函数来从cam->sensor中获取新的fmt参数保存在cam_fmt中,然后根据cam_fmt的值通过ipu_csi_set_window_size和ipu_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寄存器的值从0x01DF027F(640*480)更改为0x011F015F(352*288)。CSI_OUT_FRM_CTRL寄存器的值没有发生改变,因为应用程序中top和left的值没有发生改变。
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函数,在这个函数中会对width,height和cam->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.pix和cam->v2f.fmt.pix.priv进行了设置。虽然上面的很多步骤中都对crop或者width,height等参数进行了设置,但是经过这个ioctl调用后,这些变量是最后一次设置,到此为止不再改变,然后就会根据这个参数来计算出sizeimage,bytesperline等值。这些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_ioctl的VIDIOC_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队列中至少有2个buffers,然后调用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 = ipu_request_irq(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 = prp_enc_setup(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 = ipu_init_channel(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 =
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,
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 = 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;
}
}
return err;
}
在这个函数中完成了ipu_init_channel--->ipu_init_channel_buffer--->ipu_enable_channel这个流程。
先来看这个ipu_channel_params_t这个联合,在这个联合中对于每一种channel都有一个对应的结构体类型来保存channel的参数。因为现在这个channel为csi_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_width和enc.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_width,enc.csi_prp_enc_mem.out_height和enc.csi_prp_enc_mem.out_pixel_fmt参数。
在之前init_camera_struct函数的时候,一直不清楚cam->v2f.fmt.pix这个参数的作用,现在在这清楚了。因为应用程序中最后设置(经过VIDIOC_S_FMT宏设置)的width,height,以及经过计算的出来的bytesperline,sizeimage等参数都保存在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寄存器中24~26位表示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获取到数据的目的地址。目的地址只有两个IC和IDMAC。很明显,对于CSI_PRP_ENC_MEM这个channel来说,它的目的地址就是IC了,所以下面就应该设置IC了,就是_ipu_ic_init_prpenc函数。
而且从这个函数中可以看出来,只有CSI_MEM0~CSI_MEM3,CSI_PRP_ENC_MEM,CSI_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
可以看出来,这个寄存器中的24~26位被设置为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_height和out_height等等的值通过_calc_resize_coeffs函数来计算出对应的resizeCoeff和downsizeCoeff,这两个值是垂直方向的系数。然后将in_width和out_width通过_calc_resize_coeffs函数来计算出对应的resizeCoeff和downsizeCoeff,这两个值是水平方向的系数。
打印出来的信息如下:
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_fmt和params->mem_prp_enc_mem.out_pixel_fmt在这里,params->mem_prp_enc_mem.in_pixel_fmt和params->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_MEM0~CSI_MEM3,就会使用SMFC(_ipu_smfc_init函数),然后通过_ipu_csi_init函数来指定数据的目的地址。
对于CSI_PRP_ENC_MEMchannel,就会使用IC,使用的是IC的prpenc功能,就需要使用_ipu_ic_init_prpenc函数,同样需要使用通过_ipu_csi_init函数来指定数据的目的地址。
对于CSI_PRP_VF_MEMchannel,需要使用到IC,但是使用的是IC的prpvf功能,就需要使用_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,它需要使用到IC的rotate和viewfinder功能,就需要通过_ipu_ic_init_rotate_vf函数来设置了。
然后就是ipu_init_channel_buffer函数了,关于这个函数里面每个IDMACchannel的2words每一位的介绍在手册的: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_ch是icchannel。之后就会调用_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 125~137 351 width-1
0 138~150 287 height-1
0 46~67 12672 u_offset/8
0 68~89 15840 v_offset/8
word 位 值 值所对应的参数名称
1 102~115 351 stride-1
1 0~28 126058496 addr0>>3 (addr0= 1008467968)
1 29~57 126058496 addr1>>3 (addr1= 1008467968)
1 85~88 2 像素格式为4:2:0
1 78~84 31 bs
1 128~141 175 uv_stride-1
根据上面关于小端方式的理解,我们可以自己填充这个160位的word。
注意填充的时候,需要将填充的数字转换成二进制,从0位开始按顺序填充,填充程序中指定的位数。(这种情况只是我们手工填充时候的一个简便方法,对于在驱动程序中填充这种数据,需要使用驱动中ipu_param_mem.h文件中提供的函数来填充。)
首先填充word0:(下面几个数字的表示是从右到左依次为0~31位,它们与内存中的存储方式相同,都是小端模式,0位在最右边,31位在最左边)
图中黑色的背景的部分对应打印出来的word0的值,
为:00000000 0C60 0000 0003 DE00 E000 0000 0004 7C2B
可以对比看出来,分析是正确的。
下面分析word1:
对于word1,更能好好理解数字在内存中是怎么存储的。
比如addr0和addr1,它们的值是1008467968,它的二进制表示如下:
|31 0|
00111100 0001 1100 0000 0000 0000 0000
word1中保存的是addr0和addr1右移3位以后的值,如果将它右移3位的话,就直接在内存中右移3位,丢弃最右边3位,左边用0填充(右移的话就直接按照内存中保存的位置直接移动):
|31 0|
00000111 1000 0011 1000 0000 0000 0000
(下面几个数字的表示是从右到左依次为0~31位,它们与内存中的存储方式相同,都是小端模式,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]里面的78~84位为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]里面的93~94位为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(¶ms,0, sizeof(params));
来将params中的值都清零。这个params是structipu_ch_param类型的,即代表这两个word。
2.ipu_ch_param_set_field(¶ms,0, 125, 13, width - 1);
这一步是设置Width,将word[0]里面的125~137位设置成width-1。图中的解释是一行中的像素个数。
3.
if (((ch == 8) || (ch == 9) || (ch == 10)) && !ipu->vdoa_en) {
ipu_ch_param_set_field(¶ms, 0, 138, 12, (height / 2) - 1);
ipu_ch_param_set_field(¶ms, 1, 102, 14, (stride * 2) - 1);
} else {
/* note: for vdoa+vdi- ch8/9/10, always use band mode */
ipu_ch_param_set_field(¶ms, 0, 138, 12, height - 1);
ipu_ch_param_set_field(¶ms, 1, 102, 14, stride - 1);
}
这一步就是设置Height和Stride,其中height在word[0]中的138~149位,stride在word[1]中的102~115位,对于dma_ch是8/9/10的话,保存的是(height/ 2) - 1)和(stride* 2) – 1)。原因估计跟idmachannel的原理有关。以后分析。
4.
ipu_ch_param_set_field(¶ms,1, 0, 29, addr0 >> 3);
ipu_ch_param_set_field(¶ms,1, 29, 29, addr1 >> 3);
这一步填充addr0和addr1,word[1]的0~28位为addr0,word[1]的29~57位为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
先看看上面这几个图中的解释,然后再具体分析几个术语的含义:
width:Numberof pixels in one row,一行中的像素数,单位是pixels;
height:Numberof pixels in one column,一列中的像素数,即一列中的行数,单位是lines;
stride:Addressvertical 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_offset和uv_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_offset:Doublebuffer destination address offset for Y:U:V (U pointer) formats.双buffer模式下U元素目的地址偏移值。通过上图可以看出来,这个
u_offset= stride * height;数据会按上图那种方式存放。
v_offset:Doublebuffer 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的含义,关于这个burstsize是DMA的相关知识,可以参考《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。
burst:DMA实际上是一次一次的申请总线,把要传的数据总量分成一个一个小的数据块。比如要传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_ch为20,选定的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, ¶ms);函数来将设定好的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]里面的78~84位为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_ch的word[1]的78~84位来获取到burstsize的值保存在burst_size中,调用_ipu_ic_idma_init函数来设置IC里面的IDMA通道里面的burstsize值。
设置完burstsize以后,因为IC需要用到IDMA,所以还需要设置IC里面的IDMA,就是通过这个函数来设置的。在这个函数中需要设置3个寄存器的值,分别为IC_IDMAC_1,IC_IDMAC_2,IC_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设置到0~9位上面,打印信息如下:
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设置到0~9位上面,打印信息如下:
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]里面的93~94位为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_dma和in_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 = ipu_update_channel_buffer(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 {
ipu_select_buffer(ipu, CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER,
*buffer_num);
}
*buffer_num = (*buffer_num == 0) ? 1 : 0;
return 0;
}
在mxc_streamon函数中,会调用两次下面的代码来分别设置phyaddr_0和phyaddr_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_0和phyaddr_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]里面的0~28和29~58位存放的是地址值。它是在ipu_init_channel_buffer函数中将phyaddr_0和phyaddr_1填充进去的,现在要更新的就是这两个地址值。
之后是ipu_select_buffer函数,在之前ipu_update_channel_buffer函数中根据bufNum参数的值来读取到的寄存器的值,在ipu_select_buffer函数中同样根据bufNum参数通过ipu_cm_write函数来将寄存器的值写进去。
下面再来说说mxc_streamon函数中怎么通过调用两次相同的代码来更新了phyaddr_0和phyaddr_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]的0~29位上去。具体实现是:
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来指定写到0~28还是29~58了。
同时也正是因为这个cam->ping_pong_csi在0和1之间跳跃,所以才给它起了这样一个名字吧,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中了。
至此,追踪应用程序分析函数的执行过程就分析完毕。