NXP

4.2 ipu_init_channel函数的详细分析

2019-07-12 13:41发布

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--->MEMCSI0-->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函数为它在数组中挑选一个位置,然后将这个位置设置为truefalse*/ 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); } /*paramscsi_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函数来初始化smfcSMFC_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_MEMMEM_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中前几个用到csicase,里面都设置了这一项。*/ /*同时在params这个参数中,它是ipu_channel_params_t类型的联合,其中包含的两个关于csi的结构体csi_memcsi_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