一,背景介绍。需要做360全景功能,需要分别获取四个摄像头的图像进行合成。
目前nvp6324的配置如下。
static int sensor_g_mbus_config(struct v4l2_subdev *sd,
struct v4l2_mbus_config *cfg)
{
cfg->type = V4L2_MBUS_CSI2;
cfg->flags = V4L2_MBUS_CSI2_4_LANE|V4L2_MBUS_CSI2_CHANNELS;
return 0;
}
这个时候,就可以是4lane,4 virtual channel输入了。
想切换到不同的输入,就open不同的vin_core节点。也就是分别打开/dev/video0;/dev/video1~3;
全志user spec中写了有一个video360接口,做法就是同时打开这四个节点。模仿着写了一下,同时打开4个节点获取的画面是黑屏,找了一天都没找到原因。决定换一条路了,模仿全志tvd输入的mulit_channel,一个buffer 包含4个输入得了。
一,tvd的mulit_channel解析。
从代码来看,tvd的mulit_channel是通过添加了一个mulit_channel节点,打开节点后,通过变量mulit_channel_mode,在open,ioctl等操作的时候,将所有的tvd设备全部初始化一次。这些重复操作模仿不难,还得看看 buffer合成的位置,也就是四个通道的buffer从dma通道出来,合成一幅图的位置。
tvd streamon的时候,struct tvd_dmaqueue *dma_q = &dev->vidq; ret = vb2_streamon(&dev->vb_vidq, i);
buf = list_entry(dma_q->active.next, struct tvd_buffer, list); __tvd_set_addr(dev, buf);
只有一个addr,一个dmaqueue,在__tvd_set_addr函数中,却是存在
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i)
tvd_set_wb_addr(
i, addr_org + dev->channel_offset_y[i],
addr_org + c_offset + dev->channel_offset_c[i]);
跟进tvd_set_wb_addr函数,
s32 tvd_set_wb_addr(u32 sel,u32 addr_y,u32 addr_c)
{
tvd_device[sel]->tvd_wb3.bits.ch1_y_addr = addr_y;
tvd_device[sel]->tvd_wb4.bits.ch1_c_addr = addr_c;
tvd_device[sel]->tvd_wb1.bits.wb_addr_valid = 1;
return 0;
}
也就是说,这个函数的本意,是DMA操作的时候,只申请一个大的目标内存地址。然后四个通道的DMA,按照一定的偏移,分别把数据传输进来。那么,这里怎么进行同步的呢?也就是如何确认数据每次4个通道的数据都传完了呢?
这次进入中断函数,tvd_isr
有部分函数如下:
if (dev->mulit_channel_mode) {
tvf = tvd_valid_frame_setting(dev->sel, dev);
if (!tvf)
goto unlock;
}
/**
* @name :tvd_valid_frame_setting
* @brief :for multichannel mode,make sure all channel is locked
* @param[IN] :sel:index of tvd module
* @return :
*/
里面的代码看不懂,但大致上,是使用了一个静态结构体tvd_status。先是先判断通道是否使用,如果通道被使用了,那么进一步判断本次的irq是否被触发了。如果所有的irq都触发了,则认为当前buffer填充好了,然后通知应用结束select。
二,mipi的mulit_channel 仿写
已经确定需要做的,就是将 nvp6324->mipi.0->csi.0->isp.0->(scaler.0~3)->vin_cap. 这条通路搞定。
由于懒,不想再添加一个vinc8节点了,决定直接再vinc0的基础上,通过设置一些参数,把问题搞定。其余的在上层sdk进行操作。
第一步,可以通过vidioc_s_parm的时候,传入的reserved[4]参数,将的isp_tx_ch改成vinc0_isp_tx_ch = 0x0f(意味着使用0~3通道)。上层传入的图像尺寸,也进行相应的变化。
(1) vinc->isp_tx_ch = parms->parm.capture.reserved[4];
(2) 在vin.c中,pipeline_start_stream的时候,根据isp_tx_ch的值,判断是否需要使能 0~3这四个 tx 通道。
if((vin_cap->vinc->isp_tx_ch == 0x0f))
{
for(i=0;i<4;i++)
{
csic_vipp_input_select(vind->id, i,
vin_cap->vinc->isp_sel, i);
}
}else
{
csic_vipp_input_select(vind->id, vin_cap->vinc->vipp_sel,
vin_cap->vinc->isp_sel, vin_cap->vinc->isp_tx_ch);
}
第二部,通知scaler当前需要使用4个。
添加 sunxi_scaler_subdev_s_parm函数,以及 静态变量 scs_isp_tx_ch = param->parm.capture.reserved[4];这样在scaler中,就能够得知当前需要使用多个,后续每次操作scaler0的时候,都进行4次循环操作。至于怎么找到别的scaler?呵呵,全志初始化了一个scaler_drv_list。
第三步,看dma那部分如何操作。
这里需要做两件事情,1,给出dma的输出地址。2,当4张图片都完成dma之后,才将数据传给上层。
原生的代码中,有一个large_image的存在,无法确定具体是干什么的,也许这个就是全志的360拼接吧,但是不会用。。。
在vin_isr函数中,通过阅读nvp6324的寄存器0xa0,判断四个通道是不是都有输入。如果都有输入。不对,NVP6324的输出是一路MIPI,4个virtual channel,即使没有连接输入也会有黑屏帧输出。检测功能后面再开发吧,先默认四个输入都有吧。
vin_set_addr函数中,加入vinc->isp_tx_ch==0x0f判断,然后创建静态结构体用于存储irq状态。四个vipp_sel都产生中断后,再通知app去获取拼接后图像,如果irq不全,就先退出中断处理函数。
每个vipp需要分别set_addr,这里面还需要根据不同的格式,计算出图像偏移。
恩,框架差不多就这样了。明天实际尝试一下。
第四步,具体实现。
最终还是使用large_image来进行判断,setParams传入值如果是0x0f,则代表着当前是 4in1模式。此时传入dma的尺寸会有变化, linecount以及buffer size都需要重新计算。
全志的dma包含三次中断。1)vsync中断,表示当前DMA已经获取到传入的地址了,开始传输了。这之后再修改Dma_output的地址,就是下一帧生效。2)linecount中断,全志默认的是传输了3/4的linecount后,触发。从代码来看,触发后会手动修改output的地址,保证下一帧能够传输到新地址。3)frame_done中断,表示一帧传输完毕,通知v4l2 buffer准备好了。
(一)DMA的新地址计算方式,
if (0x0f == vinc->large_image) // 4in1 mode
{
for(int scalerId=1;scalerId<4;scalerId++)
{
y_stride =(scalerId%2) * (frame->o_width / 2) + (scalerId/2) * (frame->o_width * frame->o_height/ 2);
u_stride =(scalerId%2) * (frame->o_width / 2) + (scalerId/2) * (frame->o_width * frame->o_height/ 4);
v_stride =(scalerId%2) * (frame->o_width / 2) + (scalerId/2) * (frame->o_width * frame->o_height/ 4);
csic_dma_buffer_address(scalerId, CSI_BUF_0_A, paddr->y + y_stride);
csic_dma_buffer_address(scalerId, CSI_BUF_1_A, paddr->cb + u_stride);
csic_dma_buffer_address(scalerId, CSI_BUF_2_A, paddr->cr + v_stride);
}
}
(二)帧同步。
由于4个dma共享一个buffer,因此存在一个buffer frame_done的时候,另外几个dma还没有传输完毕的情况。nvp6324的好处是,即使没有图像数据,mipi仍然能定时发出中断,这样不会担心有通道不触发中断,而导致select超时的情况。
目前同步的方法是,虽然使用了4个vinc以及vin_cap的irq,但是在 frame_done的处理函数中,只使用vinc0与vin_cap0进行操作。使用vinc0的成员变量merge_cnt来计数。如果merge_cnt为0x07,表示当前通道4还没有触发frame_done的中断。当通道4中断触发后,merge_cnt就被置为 0x0f,然后使用vinc0以及vin_cap的buf操作函数,通知应用buffer_done。
阶段性成功了,后续会想办法实现4个节点打开的方式。上层开四个线程,分别获取buffer输出。然后传给360算法。
同时,通过sensor nvp6324 检测到的reg status,判断当前是几路输入。动态的通知上层,或者在中断、dma中使用,节约资源,防止算法出错。