(1)概述
每个IPU有两个同样的CSI接口,下图是两个IPU的CSI模块示意图:
每个CSI包括同步单元,逻辑接口,数据处理单元和sensor接口控制单元组成,如下所示:
CSI被外围的通用寄存器控制,同时是双buffer模式,CSI的主要作用是从sensor中获取数据,根据IPU时钟同步数据和控制信号然后将处理过的数据发送到DATA_DEST寄存器里面的目的地,目的地可以为SMFC,IC,VDI等等,这个过程可以从下图理解:
(2)CSI接口
从第一个图里面可以看出来,CSI支持两种类型的接口:并行接口和MIPI接口。所使用的接口类型需要在IPUx_CONF寄存器中的DATA_SOURCE位配置。
2.1 并行接口
在并行接口模式下,每个时钟信号下传输一个值(除了BT.1120模式,每个时钟信号下传输两个值),每个值可以为8~16位。具体是多少位的,需要在对应的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_FORMAT和DATA_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寄存器,如下所示:
(4)sensor与CSI的帧关系
sensor产生图像大小与CSI接收的大小是不同的,具体看下图就基本清楚了。
A:VSYNC信号确定的图像框。
B:HSYNC信号确定的图像框。
C:sensor传过来的图像框,这个框在SENS_FRM_WIDTHand SENS_FRM_HEIGHT里面配置。
D:CSI选择的图像框。其中HSC,VSC, ACT_FRM_HEIGHT 和ACT_FRM_WIDTH都是可以配置的。
(5)Timing/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 ActiveVideo,SAV),另一个在每个视频数据块的结束(Endof Active Video,EAV);每个定时基准信号由4个字的序列组成,格式如下:FF00 00 XY (16进制)头三个是固定前缀,第4个字包含定义第二场标识、场消隐状态和行消隐状态的信息。其中这4个字中的前三个字在CCIR_PRECOM(CCIR_CODE_3)中配置,第四个字在CCIR_CODE_1和CCIR_CODE_2中配置。为什么这里的寄存器名称为CCIR这么奇怪的名字???因为CCIR656是ITU-RBT656的旧称。同理,在程序中有关656和1120模式的名称都使用的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
上面这几种模式都涉及到IPUx_CSI0_CCIR_CODE_1~3这几个寄存器,如下所示:
上述的时钟模式设置在内核中是通过ipu_csi_init_interface函数来设置的。
(6)Packingto memory
从CSI中传入SMFC的数据是128-bit的,所以CSI需要对数据进行处理后才能发送给SMFC,下图是处理过程:
(7)Skippingframes
许多帧数据不需要发送到SMFC,所以就可以在CSI中跳过这些数据,不发送到SMFC。需要操作IPUx_CSI0_SKIP寄存器中的CSI0_SKIP_SMFC和CSI0_MAX_RATIO_SKIP_SMFC位,如下所示:
(8)CSI缺点
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_ifparm和vidioc_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_param是9.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_size和ipu_csi_set_window_pos,这两个函数比较简单,就不分析了。
通过如上的代码,就可以设置好CSI相关的寄存器。