全志MIPI CSI Multi channel

一,背景介绍。需要做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中使用,节约资源,防止算法出错。

 

你可能感兴趣的:(car)