8.2 子模块分析之CSI

1)概述

每个IPU有两个同样的CSI接口,下图是两个IPUCSI模块示意图:

8.2 子模块分析之CSI_第1张图片

每个CSI包括同步单元,逻辑接口,数据处理单元和sensor接口控制单元组成,如下所示:

8.2 子模块分析之CSI_第2张图片

CSI被外围的通用寄存器控制,同时是双buffer模式,CSI的主要作用是从sensor中获取数据,根据IPU时钟同步数据和控制信号然后将处理过的数据发送到DATA_DEST寄存器里面的目的地,目的地可以为SMFCICVDI等等,这个过程可以从下图理解:

8.2 子模块分析之CSI_第3张图片

2CSI接口

从第一个图里面可以看出来,CSI支持两种类型的接口:并行接口和MIPI接口。所使用的接口类型需要在IPUx_CONF寄存器中的DATA_SOURCE位配置。

8.2 子模块分析之CSI_第4张图片

2.1 并行接口

在并行接口模式下,每个时钟信号下传输一个值(除了BT.1120模式,每个时钟信号下传输两个值),每个值可以为816位。具体是多少位的,需要在对应的IPUx_CSIx_SENS_CONF寄存器中的DATA_WIDTH位配置,如下所示:8.2 子模块分析之CSI_第5张图片

8.2 子模块分析之CSI_第6张图片

CSI可以支持几种不同数据格式,具体使用哪个数据格式,需要在IPUx_CSIx_SENS_CONF寄存器中的CSI0_SENS_DATA_FORMAT中设置,解释如下:

8.2 子模块分析之CSI_第7张图片

有时候在芯片手册中的时序图与CSI中的时序图中的极性是相反的,这时候就需要设置输入的极性,在IPUx_CSIx_SENS_CONF寄存器中SENS_PIX_CLK_POL,DATA_POL, HSYNC_POL and VSYNC_POL中配置。

8.2 子模块分析之CSI_第8张图片


2.2 MIPI接口

MIPI接口模式下每个时钟信号下传输两个值,每个值是8bit

当工作在这个模式下时,CSI能够处理4路输入信号,每一路信号都包含virtualchannel号和这一路的数据类型,通过DI(dataidentifier)来区分。只有MIPI_DI0这一路里面的数据能够发送到所有的目的地,其他路MIPI_DI1-3的数据只能发送到SMFC中。

在这种模式下,SENS_DATA_FORMATDATA_WIDTH位都被忽略,因为CSI的数据是从MCT_DI中传入的。具体有关MCT_DI的解释,需要去查看手册的《Chapter19 MIPI CSI to IPU Gasket (CSI2IPU)》那一章,暂时先不说。下面是IPUx_CSI0_DI的图示:8.2 子模块分析之CSI_第9张图片

3)测试模式

IPUx_CSI0_TST_CTRL寄存器中的TEST_GEN_MODE位置1的话,就开启了测试模式,在这个模式下CSI会产生虚假的数据发送到目的地。

主要是设置IPUx_CSI0_TST_CTRL寄存器,如下所示:

8.2 子模块分析之CSI_第10张图片


4sensorCSI的帧关系

sensor产生图像大小与CSI接收的大小是不同的,具体看下图就基本清楚了。

8.2 子模块分析之CSI_第11张图片


AVSYNC信号确定的图像框。

BHSYNC信号确定的图像框。

Csensor传过来的图像框,这个框在SENS_FRM_WIDTHand SENS_FRM_HEIGHT里面配置。

DCSI选择的图像框。其中HSC,VSC, ACT_FRM_HEIGHT ACT_FRM_WIDTH都是可以配置的。


5Timing/Datamode protocols

CSI可以工作在几个不同的Timing/Datamodeprotocols下,根据IPUx_CSI0_SENS_CONF寄存器中的CSI0_SENS_PRTCL位来选择使用哪种模式/协议。

8.2 子模块分析之CSI_第12张图片

下面分析几种模式/协议

5.1 Gated Mode

在这个模式下,VSYNC信号在每一帧开始的时候触发,HSYNC信号在每一行的开始和结束的时候触发,同时时钟信号一直在走。时序图如下:8.2 子模块分析之CSI_第13张图片

5.2 Non-Gated Mode

在这个模式下,VSYNC用来确认一帧的开始,只有当有传输数据的时候,时钟信号才走,HSYNC信号没有使用。当在MIPI模式下的时候,应该使用这种模式。8.2 子模块分析之CSI_第14张图片


5.3 BT.656 mode

在这种模式下,CSI工作在ITU-RBT.656协议下,时序信号(framestart, frame end, line start, lineend)嵌入在数据流中。有两个定时基准信号,一个在每个视频数据块的开始(Startof ActiveVideoSAV),另一个在每个视频数据块的结束(Endof Active VideoEAV);每个定时基准信号由4个字的序列组成,格式如下:FF00 00 XY 16进制)头三个是固定前缀,第4个字包含定义第二场标识、场消隐状态和行消隐状态的信息。其中这4个字中的前三个字在CCIR_PRECOMCCIR_CODE_3)中配置,第四个字在CCIR_CODE_1CCIR_CODE_2中配置。为什么这里的寄存器名称为CCIR这么奇怪的名字???因为CCIR656ITU-RBT656的旧称。同理,在程序中有关6561120模式的名称都使用的CCIR8.2 子模块分析之CSI_第15张图片


关于这个协议的详细介绍可以从网上查,《入门视频采集与处理(BT656简介)

http://www.cnblogs.com/s_agapo/archive/2012/04/08/2437775.html

ITU-RBT.656 协议

http://www.cnblogs.com/crazybingo/archive/2011/03/27/1996974.html


5.4 BT.1120 mode

这个模式又分为SDRmodeDDRmode8.2 子模块分析之CSI_第16张图片8.2 子模块分析之CSI_第17张图片

上面这几种模式都涉及到IPUx_CSI0_CCIR_CODE_13这几个寄存器,如下所示:

8.2 子模块分析之CSI_第18张图片 8.2 子模块分析之CSI_第19张图片 8.2 子模块分析之CSI_第20张图片


上述的时钟模式设置在内核中是通过ipu_csi_init_interface函数来设置的。


6Packingto memory

CSI中传入SMFC的数据是128-bit的,所以CSI需要对数据进行处理后才能发送给SMFC,下图是处理过程:

8.2 子模块分析之CSI_第21张图片

7Skippingframes

许多帧数据不需要发送到SMFC,所以就可以在CSI中跳过这些数据,不发送到SMFC。需要操作IPUx_CSI0_SKIP寄存器中的CSI0_SKIP_SMFCCSI0_MAX_RATIO_SKIP_SMFC位,如下所示:

8.2 子模块分析之CSI_第22张图片

8CSI缺点

sensor的时钟不能比IPU时钟(HSP_CLK)大。

SENS_FRM_HEIGHT>= VSC + ACT_FRM_HEIGHT

SENS_FRM_WIDTH>= HSC + ACT_FRM_WIDTH


9)代码实现

上述有关CSI的一些设置,是需要在驱动中通过代码来实现的,下面来分析有关的代码:

9.1驱动中定义了一个有关CSI的结构体类型:

typedef struct { 
	unsigned data_width:4; 
	unsigned clk_mode:3; 
	unsigned ext_vsync:1; 
	unsigned Vsync_pol:1; 
	unsigned Hsync_pol:1; 
	unsigned pixclk_pol:1; 
	unsigned data_pol:1; 
	unsigned sens_clksrc:1; 
	unsigned pack_tight:1; 
	unsigned force_eof:1; 
	unsigned data_en_pol:1; 
	unsigned data_fmt; 
	unsigned csi; 
	unsigned mclk; 
} ipu_csi_signal_cfg_t

这个结构体中定义了CSI需要用到的很多参数,信号的极性和模式。后面我们就可以看到,在驱动中去设置这个结构体,并根据这个结构体里面的参数去设置IPU中有关CSI的寄存器的值。


9.2 开发板可以支持多种摄像头设备,而每个摄像头设备的具体参数也是不同的,所以,驱动程序中需要使用vidioc_int_g_ifparmvidioc_int_g_fmt_cap函数来询问当前的摄像头设备它的各个参数是什么,然后根据获得的参数来设置有关CSI的寄存器。

ov5640_mipi为例来说明,当驱动程序中调用vidioc_int_g_ifparm时,最终会调用到ov5640_mipi.c中的ioctl_g_ifparm函数:

static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) 
{ 
	if (s == NULL) { 
		pr_err("   ERROR!! no slave device set!\n"); 
		return -1; 
	} 

	memset(p, 0, sizeof(*p)); 
	p->u.bt656.clock_curr = ov5640_data.mclk; 
	pr_debug("   clock_curr=mclk=%d\n", ov5640_data.mclk); 
	p->if_type = V4L2_IF_TYPE_BT656; 
	p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; 
	p->u.bt656.clock_min = OV5640_XCLK_MIN; 
	p->u.bt656.clock_max = OV5640_XCLK_MAX; 
	p->u.bt656.bt_sync_correct = 1;  /* Indicate external vsync */ 

	return 0; 
}

之后驱动程序会根据上面设置的这些内容来对csi_param进行设置,这个csi_param9.1中所说的ipu_csi_signal_cfg_t类型的,如下所示:

	csi_param.data_width = 0; 
	csi_param.clk_mode = 0; 
	csi_param.ext_vsync = 0; 
	csi_param.Vsync_pol = 0; 
	csi_param.Hsync_pol = 0; 
	csi_param.pixclk_pol = 0; 
	csi_param.data_pol = 0; 
	csi_param.sens_clksrc = 0; 
	csi_param.pack_tight = 0; 
	csi_param.force_eof = 0; 
	csi_param.data_en_pol = 0; 
	csi_param.data_fmt = 0; 
	csi_param.csi = cam->csi; 
	csi_param.mclk = 0; 

	pr_debug("   clock_curr=mclk=%d\n", ifparm.u.bt656.clock_curr); 
	if (ifparm.u.bt656.clock_curr == 0) 
		csi_param.clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED; 
	else 
		csi_param.clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; 

	csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; 

	if (ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) { 
		csi_param.data_width = IPU_CSI_DATA_WIDTH_8; 
	} else if (ifparm.u.bt656.mode 
				== V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) { 
		csi_param.data_width = IPU_CSI_DATA_WIDTH_10; 
	} else { 
		csi_param.data_width = IPU_CSI_DATA_WIDTH_8; 
	} 

	csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; 
	csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; 
	csi_param.ext_vsync = ifparm.u.bt656.bt_sync_correct;

驱动程序中继续调用vidioc_int_g_fmt_cap函数来获取摄像头的像素格式,以及宽度高度等信息,最终会调用到ov5640_mipi.c中的ioctl_g_fmt_cap函数:

static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) 
{ 
	struct sensor_data *sensor = s->priv; 
	f->fmt.pix = sensor->pix; 
	return 0; 
}

获取到这些参数后,根据它们来设置驱动中cam结构体里面的参数,如下所示:

	cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); 
	pr_debug("   g_fmt_cap returns widthxheight of input as %d x %d\n", 
			cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); 

	csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; 

	cam->crop_bounds.top = cam->crop_bounds.left = 0; 
	cam->crop_bounds.width = cam_fmt.fmt.pix.width; 
	cam->crop_bounds.height = cam_fmt.fmt.pix.height;

最终通过上面这些函数调用,驱动程序已经知道了摄像头设备的信号极性,模式,像素信息等等,接下来就需要将这些信息设置到CSI相关的寄存器中,最主要的是ipu_csi_init_interface函数:

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) 
{ 
	uint32_t data = 0; 
	uint32_t csi = cfg_param.csi; 

	/* Set SENS_DATA_FORMAT bits (8, 9 and 10) 
	   RGB or YUV444 is 0 which is current value in data so not set 
	   explicitly 
	   This is also the default value if attempts are made to set it to 
	   something invalid. */ 
	switch (pixel_fmt) { 
	case IPU_PIX_FMT_YUYV: 
		cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV; 
		break; 
	case IPU_PIX_FMT_UYVY: 
		cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; 
		break; 
	case IPU_PIX_FMT_RGB24: 
	case IPU_PIX_FMT_BGR24: 
		cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; 
		break; 
	case IPU_PIX_FMT_GENERIC: 
	case IPU_PIX_FMT_GENERIC_16: 
		cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; 
		break; 
	case IPU_PIX_FMT_RGB565: 
		cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; 
		break; 
	case IPU_PIX_FMT_RGB555: 
		cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555; 
		break; 
	default: 
		return -EINVAL; 
	} 

	/* Set the CSI_SENS_CONF register remaining fields */ 
	data |= cfg_param.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | 
		cfg_param.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT | 
		cfg_param.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | 
		cfg_param.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | 
		cfg_param.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | 
		cfg_param.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | 
		cfg_param.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | 
		cfg_param.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | 
		cfg_param.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT | 
		cfg_param.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT | 
		cfg_param.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT; 

	_ipu_get(ipu); 

	mutex_lock(&ipu->mutex_lock); 

	ipu_csi_write(ipu, csi, data, CSI_SENS_CONF); 
/* 将摄像头的信号信息写到 CSI_SENS_CONF寄存器中,可以对照芯片手册来看看这些位都设置成了什么。 */

	/* Setup sensor frame size */ 
	ipu_csi_write(ipu, csi, (width - 1) | (height - 1) << 16, CSI_SENS_FRM_SIZE); 
/* 将摄像头的frame宽度和高度信息写到 CSI_SENS_FRM_SIZE寄存器中。 */

	/* Set CCIR registers */ 
	if (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) { 
		ipu_csi_write(ipu, csi, 0x40030, CSI_CCIR_CODE_1); 
		ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3); 
	} else if (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED) { 
		if (width == 720 && height == 625) { 
			/* PAL case */ 
			/* 
			 * Field0BlankEnd = 0x6, Field0BlankStart = 0x2, 
			 * Field0ActiveEnd = 0x4, Field0ActiveStart = 0 
			 */ 
			ipu_csi_write(ipu, csi, 0x40596, CSI_CCIR_CODE_1); 
			/* 
			 * Field1BlankEnd = 0x7, Field1BlankStart = 0x3, 
			 * Field1ActiveEnd = 0x5, Field1ActiveStart = 0x1 
			 */ 
			ipu_csi_write(ipu, csi, 0xD07DF, CSI_CCIR_CODE_2); 

			ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3); 

		} else if (width == 720 && height == 525) { 
			/* NTSC case */ 
			/* 
			 * Field0BlankEnd = 0x7, Field0BlankStart = 0x3, 
			 * Field0ActiveEnd = 0x5, Field0ActiveStart = 0x1 
			 */ 
			ipu_csi_write(ipu, csi, 0xD07DF, CSI_CCIR_CODE_1); 
			/* 
			 * Field1BlankEnd = 0x6, Field1BlankStart = 0x2, 
			 * Field1ActiveEnd = 0x4, Field1ActiveStart = 0 
			 */ 
			ipu_csi_write(ipu, csi, 0x40596, CSI_CCIR_CODE_2); 
			ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3); 
		} else { 
			dev_err(ipu->dev, "Unsupported CCIR656 interlaced " 
					"video mode\n"); 
			mutex_unlock(&ipu->mutex_lock); 
			_ipu_put(ipu); 
			return -EINVAL; 
		} 
		_ipu_csi_ccir_err_detection_enable(ipu, csi); 
	} else if ((cfg_param.clk_mode == 
			IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR) || 
		(cfg_param.clk_mode == 
			IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR) || 
		(cfg_param.clk_mode == 
			IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR) || 
		(cfg_param.clk_mode == 
			IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR)) { 
		ipu_csi_write(ipu, csi, 0x40030, CSI_CCIR_CODE_1); 
		ipu_csi_write(ipu, csi, 0xFF0000, CSI_CCIR_CODE_3); 
		_ipu_csi_ccir_err_detection_enable(ipu, csi); 
	} else if ((cfg_param.clk_mode == IPU_CSI_CLK_MODE_GATED_CLK) || 
		   (cfg_param.clk_mode == IPU_CSI_CLK_MODE_NONGATED_CLK)) { 
		_ipu_csi_ccir_err_detection_disable(ipu, csi); 
	} 
/* 这一段代码是有关CCIR相关的设置,在上面介绍数据传输协议的时候说了,当用BT.656或者BT.1120模式传输的时候,需要设置 定时基准信号中4个字的序列组成,就是在这几个寄存器中设置的。 */

	mutex_unlock(&ipu->mutex_lock); 

	_ipu_put(ipu); 

	return 0; 
} 
EXPORT_SYMBOL(ipu_csi_init_interface);

另外,设置CSI的相关函数还有ipu_csi_set_window_sizeipu_csi_set_window_pos,这两个函数比较简单,就不分析了。


通过如上的代码,就可以设置好CSI相关的寄存器。


你可能感兴趣的:(i.MX6-IPU子系统)