data/attach/1907/odek19sa0k0xyf7bj01dlqy10qu1cdhr.jpgdata/attach/1907/64nu8dje1u5no1zrptq3rq077f0lqqi9.jpg
在看这个函数之前,先对ipu内部的channel有所了解,先来看看ipu内部的flow的定义:
对于每个flow,代表数据从摄像头采集到显示(或者保存在内存中)的过程中的一个分步骤,每个flow都对应一个或几个channel,而每个channel的数据流通过程中,是通过DMA传输的,所以每个channel里面又包含一个或几个dma_channel。在这不太好理解,看看手册中的介绍(captureflow):
其中上面和程序中所说的channel对应图中的Taskschain,对于这个channel的理解需要根据ipu内部架构图来理解,如下所示:
以上面CSI0-->SMFC-->MEM这个channel为例,它代表数据从CSI经过SMFC到达内存MEM中,因为数据是从CSI中获得的,所以physicalDMA
channel中的videoinput为空,而数据从这个channel输出的话,就需要通过DMAchannel了,从图上可以看出来,physicalDMA
channel中的videooutput可以为IDMAC_CH_0~
IDMAC_CH_3。
同样对于captureflow,还可以通过CSI0-->SMFC--->MEM和CSI0-->VDIC-->MEM这两种方式将摄像头采集到的数据存放到内存中。
再来看手册中的Processingflows的图:
以MEM-->IC-->MEM这个channel为例,根据IPU内部框架图可以看到,从内存中取出数据,经过IC处理以后再放入内存中,那么取出数据的时候,就使用到DMAchannel了,从这个图中可以看出来,使用的是IDMAC_CH12,再次放入内存中的时候就使用到IDMAC_CH20。
对于其他channel就暂时不一一分析了。再来看看程序中是怎样定义channel的。
内核中使用ipu_channel_t枚举来表示一个channel:
typedef enum {
CHAN_NONE = -1,
MEM_ROT_ENC_MEM = _MAKE_CHAN(1, 45, NO_DMA, NO_DMA, 48),
MEM_ROT_VF_MEM = _MAKE_CHAN(2, 46, NO_DMA, NO_DMA, 49),
MEM_ROT_PP_MEM = _MAKE_CHAN(3, 47, NO_DMA, NO_DMA, 50),
MEM_PRP_ENC_MEM = _MAKE_CHAN(4, 12, 14, 17, 20),
MEM_PRP_VF_MEM = _MAKE_CHAN(5, 12, 14, 17, 21),
MEM_PP_MEM = _MAKE_CHAN(6, 11, 15, 18, 22),
MEM_DC_SYNC = _MAKE_CHAN(7, 28, NO_DMA, NO_DMA, NO_DMA),
MEM_DC_ASYNC = _MAKE_CHAN(8, 41, NO_DMA, NO_DMA, NO_DMA),
MEM_BG_SYNC = _MAKE_CHAN(9, 23, NO_DMA, 51, NO_DMA),
MEM_FG_SYNC = _MAKE_CHAN(10, 27, NO_DMA, 31, NO_DMA),
MEM_BG_ASYNC0 = _MAKE_CHAN(11, 24, NO_DMA, 52, NO_DMA),
MEM_FG_ASYNC0 = _MAKE_CHAN(12, 29, NO_DMA, 33, NO_DMA),
MEM_BG_ASYNC1 = _MAKE_ALT_CHAN(MEM_BG_ASYNC0),
MEM_FG_ASYNC1 = _MAKE_ALT_CHAN(MEM_FG_ASYNC0),
DIRECT_ASYNC0 = _MAKE_CHAN(13, NO_DMA, NO_DMA, NO_DMA, NO_DMA),
DIRECT_ASYNC1 = _MAKE_CHAN(14, NO_DMA, NO_DMA, NO_DMA, NO_DMA),
CSI_MEM0 = _MAKE_CHAN(15, NO_DMA, NO_DMA, NO_DMA, 0),
CSI_MEM1 = _MAKE_CHAN(16, NO_DMA, NO_DMA, NO_DMA, 1),
CSI_MEM2 = _MAKE_CHAN(17, NO_DMA, NO_DMA, NO_DMA, 2),
CSI_MEM3 = _MAKE_CHAN(18, NO_DMA, NO_DMA, NO_DMA, 3),
CSI_MEM = CSI_MEM0,
CSI_PRP_ENC_MEM = _MAKE_CHAN(19, NO_DMA, NO_DMA, NO_DMA, 20),
CSI_PRP_VF_MEM = _MAKE_CHAN(20, NO_DMA, NO_DMA, NO_DMA, 21),
/* for vdi mem->vdi->ic->mem , add graphics plane and alpha*/
MEM_VDI_PRP_VF_MEM_P = _MAKE_CHAN(21, 8, 14, 17, 21),
MEM_VDI_PRP_VF_MEM = _MAKE_CHAN(22, 9, 14, 17, 21),
MEM_VDI_PRP_VF_MEM_N = _MAKE_CHAN(23, 10, 14, 17, 21),
/* for vdi mem->vdi->mem */
MEM_VDI_MEM_P = _MAKE_CHAN(24, 8, NO_DMA, NO_DMA, 5),
MEM_VDI_MEM = _MAKE_CHAN(25, 9, NO_DMA, NO_DMA, 5),
MEM_VDI_MEM_N = _MAKE_CHAN(26, 10, NO_DMA, NO_DMA, 5),
/* fake channel for vdoa to link with IPU */
MEM_VDOA_MEM = _MAKE_CHAN(27, NO_DMA, NO_DMA, NO_DMA, NO_DMA),
MEM_PP_ADC = CHAN_NONE,
ADC_SYS2 = CHAN_NONE,
} ipu_channel_t;
#define _MAKE_CHAN(num, v_in, g_in, a_in, out)
((num << 24) | (v_in << 18) | (g_in << 12) | (a_in << 6) | out)
这个枚举就包含了所有的channel,如果对于上面讲解的过程理解的话,很容易根据channel的过程在这个枚举中找到对应的channel名字。同时可以看到在channel通过_MAKE_CHAN宏的构造过程中,每个channel里面都包含了输入输出dmachannel号。
分析完对channel的理解过程以后,再来看看具体的函数实现:
ipu_init_channel函数
int32_t ipu_init_channel(struct ipu_soc *ipu, ipu_channel_t channel,
ipu_channel_params_t *params)
/*这个函数有3个参数,第一个参数ipu代表正在使用的ipu。第二个参数是想要初始化的channel,它其实就是一个数字,可以理解为ID,第三个参数params是想要将这个channel初始化成什么样子,它里面包含channel的一些信息,会根据params参数来初始化这个channel。关于这个结构体的详细讲解可以看《ipu_channel_params_t结构体详解》*/
{
int ret = 0;
bool bad_pixfmt;
uint32_t ipu_conf, reg, in_g_pixel_fmt, sec_dma;
dev_dbg(ipu->dev, "init channel = %d
", IPU_CHAN_ID(channel));
ret = pm_runtime_get_sync(ipu->dev);
if (ret < 0) {
dev_err(ipu->dev, "ch = %d, pm_runtime_get failed:%d!
",
IPU_CHAN_ID(channel), ret);
dump_stack();
return ret;
}
/*
* Here, ret could be 1 if the device's runtime PM status was
* already 'active', so clear it to be 0.
*/
/*看注释,可以看出来,上面一个函数如果执行成功的话,它的返回值是1,所以在下面需要将这个ret清零。跟踪源码,确实是这样的。*/
ret = 0;
_ipu_get(ipu);
mutex_lock(&ipu->mutex_lock);
/* Re-enable error interrupts every time a channel is initialized */
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(5));
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(6));
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(9));
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(10));
/*在初始化每一个channel的时候都需要重新使能错误中断。*/
if (ipu->channel_init_mask & (1L << IPU_CHAN_ID(channel))) {
dev_warn(ipu->dev, "Warning: channel already initialized %d
",
IPU_CHAN_ID(channel));
}
/*这个ipu_soc结构体里面的channel_init_mask项是uint32_t类型的,每个channel对应这个数字里面的某一位,如果这个channel进行过初始化操作的话,就将它的那一位置1.通过这个来判断其中的某一个channel是否进行过初始化。所以在这个初始化函数中肯定有将它置位的操作,我们搜索源码可以发现,在这个函数的后面确实有这样的操作。先在这粘贴一下:
ipu->channel_init_mask|= 1L << IPU_CHAN_ID(channel);
*/
ipu_conf = ipu_cm_read(ipu, IPU_CONF);
/*下面的switch判断语句就是根据channel的值来初始化不同的channel,所以这么多case就包括了所有的channel。*/
switch (channel) {
case CSI_MEM0:
case CSI_MEM1:
case CSI_MEM2:
case CSI_MEM3:
if (params->csi_mem.csi > 1) { //csi有2个,取值为0和1.
ret = -EINVAL;
goto err;
}
if (params->csi_mem.interlaced)
ipu->chan_is_interlaced[channel_2_dma(channel,
IPU_OUTPUT_BUFFER)] = true;
else
ipu->chan_is_interlaced[channel_2_dma(channel,
IPU_OUTPUT_BUFFER)] = false;
/*ipu->chan_is_interlaced是一个bool型的数组,根据params里面的csi_mem.interlaced参数,来设置ipu->chan_is_interlaced数组的某一项。怎么确定是数组的第几项?根据channel的值通过channel_2_dma函数为它在数组中挑选一个位置,然后将这个位置设置为true或false。*/
ipu->smfc_use_count++; //增加smfc使用计数,这几个channel都会使用到smfc。
ipu->csi_channel[params->csi_mem.csi] = channel;
/*根据params->csi_mem.csi来确定是ipu->csi_channel数组的第几项,然后将channel赋给它。这个ipu->csi_channel数组包含两项,意思就是每个ipu有两个csi接口,每个接口选择哪个channel,都在这里进行保存。*/
/*SMFC setting*/
if (params->csi_mem.mipi_en) {
ipu_conf |= (1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET +
params->csi_mem.csi));
_ipu_smfc_init(ipu, channel, params->csi_mem.mipi_vc,
params->csi_mem.csi);
_ipu_csi_set_mipi_di(ipu, params->csi_mem.mipi_vc,
params->csi_mem.mipi_id, params->csi_mem.csi);
} else {
ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET +
params->csi_mem.csi));
_ipu_smfc_init(ipu, channel, 0, params->csi_mem.csi);
}
/*params中csi_mem.mipi_en是个bool类型的值,如果它为1的话,表示使用的是mipi引脚。
下面来看看ipu_conf寄存器:
这里设置的是ipu_conf寄存器的28或者29位,看看这两位的介绍:
这两位的含义是选择CSI的数据来源,我们知道,CSI接口可以接并行接口或者MIPI接口,驱动会根据mipi_en的值来选择将这两位的数据来源设置为并口或者MIPI接口。如果是MIPI接口的话,需要通过_ipu_csi_set_mipi_di函数来设置MIPI相关的寄存器。
同时,不论是哪一种接口,都需要使用SMFC,就需要通过_ipu_smfc_init函数来初始化smfc的SMFC_MAP寄存器,在这个_ipu_smfc_init函数中根据不同的case选择不同的SMFC_MAP_CH;然后再调用_ipu_csi_set_mipi_di函数来初始化CSI_MIPI_DI寄存器。这个寄存器是mipidata
identifier的意思。
*/
/*CSI data (include compander) dest*/
_ipu_csi_init(ipu, channel, params->csi_mem.csi);
break;
/*最后通过_ipu_csi_init函数初始化csi。*/
/*上面的channel流程就是下图这种模式:数据从CSI中通过SMFC直接到达IDMAC,然后通过IDMAC来进行数据的存放等操作。
*/
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->using_ic_dirct_ch是个ipu_channel_t类型的变量,usingic
direct channel的意思,只能使用ICchannel而不能使用VDIchannel,所以是MEM_VDI_PRP_VF_MEM和MEM_VDI_MEM的话就会报错。最后再把这一位置位成CSI_PRP_ENC_MEM。*/
ipu->ic_use_count++; //增加ic的引用计数。
ipu->csi_channel[params->csi_prp_enc_mem.csi] = channel;
/*这个ipu_soc结构体中有一项ipu_channel_tcsi_channel[2];因为每个ipu有两个csi,所以这个数组里面保存的是每个csi选用的channel通道。在每一个需要用到csi接口的channel中都需要设置这一项,可以看到在这个switch中前几个用到csi的case,里面都设置了这一项。*/
/*同时在params这个参数中,它是ipu_channel_params_t类型的联合,其中包含的两个关于csi的结构体csi_mem和csi_prp_enc_mem,这两个结构体里面都含有一个uint32_tcsi,就是通过它来确定想要使用的是哪一个csi设备。
*/
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));
/*这个channel没有用到smfc,所以不用初始化它,其他跟上面那个channel一样。设置IPU_CONF寄存器中的28,29位。如果是MIPI接口的话,还需要通过_ipu_csi_set_mipi_di函数来初始化MIPI相关的寄存器。*/
/*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;
/*设置ipu_conf中的IPU_CONF_IC_INPUT位和CSI_SEL位。这两位的含义如下:
在初始化这个channel的时候,我们应该首先想到这个channel使用到了IPU内部的哪些模块,在上面一个channel中,使用到了CSI和