NXP

8.2 子模块分析之CSI

2019-07-12 12:49发布

data/attach/1907/7fjxuufiqjxx0cmzfm93cjxyl12ci9j1.jpgdata/attach/1907/fgayqys5wmscvy0rqpwnc7d7k4vj9v53.jpgdata/attach/1907/govhkothz1dr5ealgxohby0vbk2jxsmx.jpg 1)概述 每个IPU有两个同样的CSI接口,下图是两个IPUCSI模块示意图: 每个CSI包括同步单元,逻辑接口,数据处理单元和sensor接口控制单元组成,如下所示: CSI被外围的通用寄存器控制,同时是双buffer模式,CSI的主要作用是从sensor中获取数据,根据IPU时钟同步数据和控制信号然后将处理过的数据发送到DATA_DEST寄存器里面的目的地,目的地可以为SMFCICVDI等等,这个过程可以从下图理解: 2CSI接口 从第一个图里面可以看出来,CSI支持两种类型的接口:并行接口和MIPI接口。所使用的接口类型需要在IPUx_CONF寄存器中的DATA_SOURCE位配置。 2.1 并行接口 在并行接口模式下,每个时钟信号下传输一个值(除了BT.1120模式,每个时钟信号下传输两个值),每个值可以为816位。具体是多少位的,需要在对应的IPUx_CSIx_SENS_CONF寄存器中的DATA_WIDTH位配置,如下所示: CSI可以支持几种不同数据格式,具体使用哪个数据格式,需要在IPUx_CSIx_SENS_CONF寄存器中的CSI0_SENS_DATA_FORMAT中设置,解释如下: 有时候在芯片手册中的时序图与CSI中的时序图中的极性是相反的,这时候就需要设置输入的极性,在IPUx_CSIx_SENS_CONF寄存器中SENS_PIX_CLK_POL,DATA_POL, HSYNC_POL and VSYNC_POL中配置。
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的图示: 3)测试模式 IPUx_CSI0_TST_CTRL寄存器中的TEST_GEN_MODE位置1的话,就开启了测试模式,在这个模式下CSI会产生虚假的数据发送到目的地。 主要是设置IPUx_CSI0_TST_CTRL寄存器,如下所示:
4sensorCSI的帧关系 sensor产生图像大小与CSI接收的大小是不同的,具体看下图就基本清楚了。
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位来选择使用哪种模式/协议。 下面分析几种模式/协议 5.1 Gated Mode 在这个模式下,VSYNC信号在每一帧开始的时候触发,HSYNC信号在每一行的开始和结束的时候触发,同时时钟信号一直在走。时序图如下: 5.2 Non-Gated Mode 在这个模式下,VSYNC用来确认一帧的开始,只有当有传输数据的时候,时钟信号才走,HSYNC信号没有使用。当在MIPI模式下的时候,应该使用这种模式。
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模式的名称都使用的CCIR
关于这个协议的详细介绍可以从网上查,《入门视频采集与处理(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 这个模式又分为SDRmodeDDRmode 上面这几种模式都涉及到IPUx_CSI0_CCIR_CODE_13这几个寄存器,如下所示:
上述的时钟模式设置在内核中是通过ipu_csi_init_interface函数来设置的。
6Packingto memory CSI中传入SMFC的数据是128-bit的,所以CSI需要对数据进行处理后才能发送给SMFC,下图是处理过程: 7Skippingframes 许多帧数据不需要发送到SMFC,所以就可以在CSI中跳过这些数据,不发送到SMFC。需要操作IPUx_CSI0_SKIP寄存器中的CSI0_SKIP_SMFCCSI0_MAX_RATIO_SKIP_SMFC位,如下所示: 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! "); return -1; } memset(p, 0, sizeof(*p)); p->u.bt656.clock_curr = ov5640_data.mclk; pr_debug(" clock_curr=mclk=%d ", ov5640_data.mclk); p->if_type = V4L2_IF_TYPE_BT656; p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; p->u.bt656.clock_min = OV5640_XCLK_MIN; p->u.bt656.clock_max = OV5640_XCLK_MAX; p->u.bt656.bt_sync_correct = 1; /* Indicate external vsync */ return 0; }
之后驱动程序会根据上面设置的这些内容来对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 ", 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 ", 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 "); 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相关的寄存器。