(1)概述
IC包含三个处理模块:缩放(downsizing),主处理流程(main processing)和翻转(rotation)。这几个模块是通过外围寄存器控制的,所需的参数是ARM通过AHB总线写到Task Parameter Memory中的。这个Task Parameter Memory在后面介绍。
先来看一个整体图:
这几个模块如下所示:
(2)Processing tasks
这三个模块中任何一个都可以最多进行三个处理任务,这三个处理任务如下所示:
1)Preprocessing task for encoding.(编码预处理)
2)Preprocessing task for displaying image from sensor (viewfinder)(VF显示处理)
3)Postprocessing task.(后加工)
这些任务通过单个硬件来实现,ARM平台会在使能这些任务之前配置好它们。
任务切换对于ARM平台而言是透明的。任务切换到downsizing模块所花的时间单元等于处理一个8pixels 突发(burst)所花的时间;任务切换到main processing模块所花的时间单元等于处理一个图像一行所花的时间;任务切换到rotation模块所花的时间单元等于处理一帧图像所花的时间。
这三个处理任务包含相似的操作控制命令,下图就是这些命令的定义:
ARM平台将这些命令写到IC_CONF寄存器中,IC_CONF寄存器如下所示:
ARM平台如果想要修改IC task参数的话,必须先关闭这个task。在关闭task的过程中,IC会继续执行完当前帧,当这一帧处理完后,IPU向ARM平台发送一个中断信号,通知ARM平台可以改变task的参数,ARM平台修改后使能这个task,并从下一帧开始。
(3) Downsizing Section
在这个模块中,有三个内存模块,Input Buffer Memory, Downsizing Temporary Memory, Downsizing Output Memory,同时每个内存模块都有一个控制器,核心是Downsizing Arithmetic Unit模块以及与之对应的Downsizing Control控制器。下面会讲述它们分别有什么作用。
从CSI中来的数据会写到Input Buffer Memory中的一个FIFO中,可以通过编程控制,这个FIFO里面的数据能够通过IDMAC发送到系统内存中或者直接发送到Downsizing Unit中。
通过IDMAC发送到系统内存中这种情况,数据是通过ARM平台处理,然后通过Input Buffer Memory中的另一个FIFO来发送到IDMAC中的。
在后加工这个task中,IDMAC将数据从系统内存中传送到第三个FIFO中,然后Downsizing Unit模块来读取这个数据。
这三个FIFO各含有128页,每一页可以保存有2或者4个字大小的突发,对应8或者16pixels。具体突发的大小在对应channel的CB#_BURST_SIZE位里面定义的。其中,在这个memory中每一个字的大小是128bits,每个字里面包含相连的颜色元素或者16bytes的通用数据(例如Bayer)。这几个FIFO是通过InputBuffer Memory Control来控制的。
Downsizing Unit提供图像像素水平方向和垂直方向上的平均和采样根据下面的公式:
IPr,c代表输入像素,HPR,c代表经过水平缩放的像素值,VPR,C代表在水平缩放的基础上再次经过垂直缩放后的像素值。DS_R_H和DS_R_V分别代表水平和垂直方向上的缩放比例因子。其中这两个缩放因子是在IC_PRP_ENC_RSC,IC_PRP_VF_RSC 和IC_PP_RSC寄存器中设置的。这三个寄存器如下所示:
仔细来看这两个公式,倒是不太难理解,例如水平方向的缩放,缩放因子是6的话,就将水平方向上面的6个像素值求和后除以6即可,垂直方向上的缩放相似。最终的计算结果向8取整。
三个缩放任务的每一个所处理的数据是一个8 pixels的突发。通常情况下,当前正在执行的任务会一直执行到FIFO里面的数据清空为止。然后如果FIFO接收到一个新任务突发的情况下,Downsizing Unit就会切换到下一个更高优先级的新任务。
水平方向上的像素平均会首先进行,每个像素的所有颜色元素并行处理,之后,会将产生的新数据按行保存到Downsizing Temporary Memory中,之后再进行垂直方向的像素平均。在这个Downsizing Temporary Memory中,会存放三行数据对应这三个任务。这个内存中每一个字的宽度是36 bits。
当完成垂直方向上的像素平均以后,数据会输出到Downsizing Output Memory中,在这个内存中每一个字的宽度是48 bits(包含两个输出像素)。对于每一个任务,Downsizing Output Memory内存中一行含有两个buffer,Downsizing Unit填充这两个buffer中的前背景部分,Main Processing Unit填充这两个buffer的后背景部分。
(4)Main Processing Section
对于每一个任务,Main Processing Unit都会按照下面的步骤来处理:
1.将从Downsizing Output Memory里面所读取到数据进行翻转(可选操作,因为是是否翻转是根据对应dma channel的CPMEM中的有关VF,HF和ROT参数来决定的),encoding任务使用IDMAC channel #20,viewfinder任务使用IDMAC channel #21,postprocessing任务使用IDMAC channel #22。这几个channel在程序中都体现了。
2.将从Downsizing Output Memory中获取到的数据进行水平方向上的大小调整根据下面的公式:
RS_C_H代表当前水平方向上的大小调整系数,这个系数的计算结果是向8bits取整的。这个系数的计算公式如下:
RS_R_H代表水平方向上的大小调整比率,这个比率是在IC_PRP_ENC_RSC,IC_PRP_VF_RSC 和IC_PP_RSC寄存器中设置的,这三个寄存器在上面已经显示过了,在这就不再显示。
经过这一步的计算后,结果保存在Task Parameter Memory中。
3.继续从Task Parameter Memory中取出已经经过水平方向上大小调整的数据,然后进行垂直方向上的大小调整,之后继续保存到Task Parameter Memory中。垂直方向上的大小调整是按照下面的公式:
RS_C_V代表当前垂直方向上的大小调整系数,这个系数的计算结果同样是向8 bits取整的。这个系数的计算公式如下:
RS_R_V代表垂直方向上的大小调整比率,这个比率同样是在IC_PRP_ENC_RSC,IC_PRP_VF_RSC 和IC_PP_RSC寄存器中设置的。
注意,在这个Main Processing Unit中进行的是ReSizing操作,而在Downsizing Section Unit中进行的是DownSizing操作,这两个相似的系数都保存在IC_PRP_ENC_RSC,IC_PRP_VF_RSC 和IC_PP_RSC这三个寄存器中,仔细看这几个寄存器中的各位的名字,可以区分它们。
4.第一次颜色空间转换(CSC)的时候(YUV->RGB或者RGB->YUV)需要使用到转换矩阵CSC1。转换矩阵是可以通过编程控制的,它们保存在Task Parameter Memory中。转换矩阵如下:
当在YUV->RGB情况下时:X0=Y, X1 =U, X2 =V, Z0 =R, Z1=G, Z2 =B;
当在RGB->YUV情况下时:X0=R, X1 =G, X2 =B, Z0 =Y, Z1=U, Z2 =V。
转换矩阵里面的所有参数都是通过ARM平台写到Task Parameter Memory中去的。
5.将图像整合起来,有三种整合方式:
• local alpha blending,
• global alpha blending,
• use of key color.
相比alpha blending而言,color keying方式具有更高的优先级。
整合模式是可以通过在IC_CONF寄存器的28,19位中选择的:
整合公式如下所示:
IGP代表an input graphics pixel,
IVP代表an input video pixel,α=(A+floor(A/128))/256代表an alpha value,A代表a global or local transparency parameter。global A写在IC_CMBP_1寄存器中,如下所示:
当color keying使能并且有一个pixel匹配key color的时候,graphics pixel变得透明。
graphics data是从位于Main Processing Memory里面的FIFO里面读取出来的。这个FIFO包含32页,里面的数据格式为RGB或者RGBA或者YUV4:4:4或者YUVA。这些graphics data是通过IDMAC从系统内存中加载进去的。
以上所有的操作是通过统一的处理单元有序执行的。第一步和第二步不会被其他的任务所打断,其他步骤会被具有更高优先级的任务所打断,Preprocessing任务的优先级比postprocessing任务的优先级高。
这个统一处理单元对于三个颜色元素都有对应的三个独立部分,所以这三个颜色元素可以并行处理。
处理结果会通过一个输出FIFO一行一行地填充到Main Processing Output Memory中。最终IDMAC会将输出突发传送到系统内存中或者直接通过DMFC到显示器来显示(使用dma channel #21)。这个Main Processing Memory为每个任务准备了三个buffer:the temporary row buffer, the graphics FIFO和the output FIFO.
(5)Rotation Section
这个翻转模块包含:
the Rotation Memory:用来存放一个8×8像素的输入矩形块;
an output FIFO:包含四页,并且每页是8 pixels。
这个Rotation Memory中字的宽度等于四个相连的像素的宽度--96bits。
Rotation Unit重写每一个从输入块到输出块内像素数据的相对位置。对于这三个任务来说,旋转/左右翻转/上下翻转是分开使能的。配置这些翻转参数通过对应dma channel里面的CPMEM里面的VF,HF & ROT这几个参数。
preprocessing task for encoding使用IDMAC channel #45;
preprocessing task for viewfinder使用IDMACc hannel #46;
postprocessing task使用IDMAC channel #47;
这些翻转如下所示:
图中FLR表示:Flipping Left/Right
FUD表示:Flipping Up/Down
当完成这些翻转任务的时候,IDMAC将输出FIFO里面的内容返回给系统内存。
(6)IC Task Parameter Memory
这一节就直接看手册吧,就是一张表格,没法分析。
(7)IC's DMA channels
下面这个图表示IC所使用的dma channel,其中IC's channel name表示在IC层面看来的channel名字。
(8)代码实现
对于IC,因为IC可以执行三个任务:
1)Preprocessing task for encoding.(编码预处理)
2)Preprocessing task for displaying image from sensor (viewfinder)(VF显示处理)
3)Postprocessing task.(后加工)
所以会有几个相似的channel使用到IC模块,如:
CSI_PRP_ENC_MEM,CSI_PRP_VF_MEM,MEM_PRP_VF_MEM,MEM_VDI_PRP_VF_MEM,MEM_PRP_ENC_MEM等等。
同时,在上面分析过,IC里面一共有三个Unit:downsizing Unit,main processing Unit和rotation Unit,其中,在downsizing Unit中,会将数据进行水平和垂直方向的平均或采样(DownSizing);在main processing Unit中会对数据进行水平和垂直方向上的重新调整大小(ReSizing),然后进行颜色空间转换;在rotation Unit中,对数据进行翻转等操作。
所以,其实在代码中就很简单了,首先计算出DownSizing和ReSizing的比例系数,他们都保存在IC_PRP_ENC_RSC,IC_PRP_VF_RSC 和IC_PP_RSC这三个寄存器中,然后根据输入和输出的数据格式来进行颜色空间转换。至于翻转等操作,对于channel来说,是一个可选操作。
以CSI_PRP_ENC_MEM为例:
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->ic_use_count++;
ipu->csi_channel[params->csi_prp_enc_mem.csi] = channel;
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));
<span style="color:#FF0000;">/*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; </span>
/*PRP skip buffer in memory, only valid when RWS_EN is true*/
reg = ipu_cm_read(ipu, IPU_FS_PROC_FLOW1);
ipu_cm_write(ipu, reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1);
/*CSI data (include compander) dest*/
_ipu_csi_init(ipu, channel, params->csi_prp_enc_mem.csi);
<span style="color:#FF0000;">_ipu_ic_init_prpenc(ipu, params, true); </span>
break;
与IC相关的代码就是我标红的,首先设置IPU_CONF寄存器中IC的数据来源,因为IC的输入数据来源在采集的时候只能是VDI或者CSI,所以这些代码就是来设置这些内容的:
之后有关IC的就是_ipu_ic_init_prpenc函数,如下所示:
int _ipu_ic_init_prpenc(struct ipu_soc *ipu, ipu_channel_params_t *params,
bool src_is_csi)
{
uint32_t reg, ic_conf;
uint32_t downsizeCoeff, resizeCoeff;
ipu_color_space_t in_fmt, out_fmt;
int ret = 0;
/* Setup vertical resizing */
if (!params->mem_prp_enc_mem.outv_resize_ratio) {
ret = _calc_resize_coeffs(ipu,
params->mem_prp_enc_mem.in_height,
params->mem_prp_enc_mem.out_height,
&resizeCoeff, &downsizeCoeff);
if (ret < 0) {
dev_err(ipu->dev, "failed to calculate prpenc height "
"scaling coefficients\n");
return ret;
}
reg = (downsizeCoeff << 30) | (resizeCoeff << 16);
} else
reg = (params->mem_prp_enc_mem.outv_resize_ratio) << 16;
/* 上面的代码是计算垂直方向上的DownSizing和ReSizing比例系数。 */
/* Setup horizontal resizing */
if (!params->mem_prp_enc_mem.outh_resize_ratio) {
ret = _calc_resize_coeffs(ipu, params->mem_prp_enc_mem.in_width,
params->mem_prp_enc_mem.out_width,
&resizeCoeff, &downsizeCoeff);
if (ret < 0) {
dev_err(ipu->dev, "failed to calculate prpenc width "
"scaling coefficients\n");
return ret;
}
reg |= (downsizeCoeff << 14) | resizeCoeff;
} else
reg |= params->mem_prp_enc_mem.outh_resize_ratio;
/* 上面的代码是计算水平方向上的DownSizing和ReSizing比例系数。 */
ipu_ic_write(ipu, reg, IC_PRP_ENC_RSC);
/* 将这些比例系数写到IC_PRP_ENC_RSC寄存器中。 */
ic_conf = ipu_ic_read(ipu, IC_CONF);
/* Setup color space conversion */
in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt);
out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt);
if (in_fmt == RGB) {
if ((out_fmt == YCbCr) || (out_fmt == YUV)) {
/* Enable RGB->YCBCR CSC1 */
_init_csc(ipu, IC_TASK_ENCODER, RGB, out_fmt, 1);
ic_conf |= IC_CONF_PRPENC_CSC1;
}
}
if ((in_fmt == YCbCr) || (in_fmt == YUV)) {
if (out_fmt == RGB) {
/* Enable YCBCR->RGB CSC1 */
_init_csc(ipu, IC_TASK_ENCODER, YCbCr, RGB, 1);
ic_conf |= IC_CONF_PRPENC_CSC1;
} else {
/* TODO: Support YUV<->YCbCr conversion? */
}
}
/* 进行颜色空间转换 */
if (src_is_csi)
ic_conf &= ~IC_CONF_RWS_EN;
else
ic_conf |= IC_CONF_RWS_EN;
/* 设置IC_CONF寄存器中的RWS_EN位。 */
ipu_ic_write(ipu, ic_conf, IC_CONF);
return ret;
}
从这一段代码中可以看出来,它完成了计算DownSizing和ReSizing比例系数的计算,然后将这4个系数写到IC_PRP_ENC_RSC寄存器中即可。之后完成颜色空间的转换功能,通过_init_csc函数来完成,在这个_init_csc函数中,就包含了颜色空间转换所使用到的转换矩阵CSC1,同时,由于使用到了颜色转换这个功能,所以需要将IC_CONF寄存器中的PRPENC_CSC1位置位。
在这里还有一个问题,就是在CSI_PRP_ENC_MEM这种情况下,在它的ipu_init_channel函数中,会使用到ipu_channel_params_t这个结构体联合,对应CSI_PRP_ENC_MEM,应该使用的是ipu_channel_params_t中的csi_prp_enc_mem结构体,但是在_ipu_ic_init_prpenc函数中,却使用的是mem_prp_enc_mem这个结构体来计算比例系数,但是一直找不到MEM_PRP_ENC_MEM这个channel初始化的地方或者mem_prp_enc_mem这个结构体赋值的地方,甚是不解。。。
最终,问虎哥后找到答案,这个是C语言的语法问题,大致意思是:
当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union),同时它有以下几个特点:
1)联合体是一个结构;
2)它的所有成员相对于基地址的偏移量都为0;
3)此结构空间要大到足够容纳最“宽”的成员;
4)其对其方式主要是要适合其中所有的成员;
也就是说,如果对ipu_channel_params_t.csi_prp_enc_mem.in_width赋值的话,ipu_channel_params_t.mem_prp_enc_mem.in_width中保存的也是同样的值。所以就可以使用mem_prp_enc_mem中的值来进行计算了。
对于其他功能如:_ipu_ic_init_prpvf,_ipu_ic_init_pp等等,它们的实现是大致相似的,暂时先不分析了.