在Linux中,使用V4L2框架管理所有的视频编解码设备。针对我们开发板的V4L2框架结构已经在之前的TVP5150驱动程序编写和OV5642驱动程序编写的说明文档中进行了详细的分析,所以这里不再对整体架构方面的问题进行论述,直接讲解对于TVP7002的Linux驱动程序开发需要修改内核中的哪些文件。

 

1        TVP7002 Linux驱动程序的编写

在Linux内核中已经有现成的TVP7002驱动程序,但是程序不够完善,并且寄存器配置有些错误,需要进行一定修改。对于tvp7002的Linux驱动程序开发,需要修改的内核文件不多,只有三个:tvp7002.c、vpif_capture.c和board-dm646x-evm.c,下面分别对这三个文件所做的修改进行详细讲解。

1.1  修改($KERNEL_DIR)/drivers/media/video/tvp7002.c

对于tvp7002.c文件的修改主要是需要修改其寄存器配置。由于我们已经在CCS中成功采集到了高清视频,所以可以直接使用在CCS中的TVP7002寄存器配置方案。

因此,在tvp7002_setstd函数中进行以下修改。其中”---”表示需要删除的代码,”+++”表示需要添加的代码。

 

---

         err = tvp7002_set_format_params(sd, tvp7002formats);

+++

         if (id == V4L2_STD_1080I_60) {

                   tvp7002_set_1080i60(sd);

         }

         else {

                   err = tvp7002_set_format_params(sd, tvp7002formats);

         }

 

对于这里使用到的tvp7002_set_1080i60()函数是手动添加的,主要负责在输入视频为1080I60格式时的TVP7002寄存器配置,该函数的代码如下所示。

 

static int tvp7002_set_1080i60(struct v4l2_subdev *sd) {

         tvp7002_write_reg(sd, 0x2b, 0x7f);   msleep( 100 );

         tvp7002_write_reg(sd, 0x2b, 0x00);   msleep( 100 );

         tvp7002_write_reg(sd, 0x01, 0x89);       tvp7002_write_reg(sd, 0x02, 0x80);

         tvp7002_write_reg(sd, 0x03, 0x98);       tvp7002_write_reg(sd, 0x04, 0x80);

         tvp7002_write_reg(sd, 0x05, 0x32);       tvp7002_write_reg(sd, 0x06, 0x20);

         tvp7002_write_reg(sd, 0x07, 0x60);       tvp7002_write_reg(sd, 0x0e, 0x1b);

         tvp7002_write_reg(sd, 0x0f, 0x2e);         tvp7002_write_reg(sd, 0x10, 0x5d);

         tvp7002_write_reg(sd, 0x11, 0x47);       tvp7002_write_reg(sd, 0x12, 0x01);

         tvp7002_write_reg(sd, 0x13, 0x00);       tvp7002_write_reg(sd, 0x15, 0x47);

         tvp7002_write_reg(sd, 0x16, 0x01);

         tvp7002_write_reg(sd, 0x17, 0x00);       tvp7002_write_reg(sd, 0x18, 0x01);

         tvp7002_write_reg(sd, 0x19, 0x00);       tvp7002_write_reg(sd, 0x1a, 0x67);

         tvp7002_write_reg(sd, 0x21, 0x08);       tvp7002_write_reg(sd, 0x22, 0x00);

         tvp7002_write_reg(sd, 0x26, 0x80);       tvp7002_write_reg(sd, 0x28, 0x53);

         tvp7002_write_reg(sd, 0x2a, 0x07);       tvp7002_write_reg(sd, 0x2b, 0x00);

         tvp7002_write_reg(sd, 0x2c, 0x50);        tvp7002_write_reg(sd, 0x2d, 0x00);

         tvp7002_write_reg(sd, 0x2e, 0x80);       tvp7002_write_reg(sd, 0x31, 0x5a);

         tvp7002_write_reg(sd, 0x34, 0x02);       tvp7002_write_reg(sd, 0x3f, 0x01);

         tvp7002_write_reg(sd, 0x40, 0x06);       tvp7002_write_reg(sd, 0x41, 0x01);

         tvp7002_write_reg(sd, 0x42, 0x8a);       tvp7002_write_reg(sd, 0x43, 0x08);

         tvp7002_write_reg(sd, 0x44, 0x02);       tvp7002_write_reg(sd, 0x45, 0x02);

         tvp7002_write_reg(sd, 0x46, 0x16);       tvp7002_write_reg(sd, 0x47, 0x17);

         return 0;

 

}

 

另外,还需要修改tvp7002_probe函数,删除其中对TVP7002进行初始化的代码。这是因为tvp7002_probe函数是在内核启动时运行的,而这个时候还没有通过CPLD选通并使能TVP7002,所以对其进行I2C寄存器配置是会失败的。代码修改如下所示。

 

---

         err = tvp7002_initialize(sd);

 

1.2  修改($KERNEL_DIR)/drivers/media/video/davinci/vpif_capture.c

对于vpif_capture.c文件的修改比较少,只需要在vpif_streamon()和vpif_streamoff()两个函数中添加开启和关闭channel1的相关代码。

在vpif_streamon()函数中的修改如下所示。

 

---

         if ((VPIF_CHANNEL1_VIDEO == ch->channel_id) ||

             (common->started == 2)) {

                   channel1_intr_assert();

                   channel1_intr_enable(1);

                   enable_channel1(1);

         }

+++

         if ((VPIF_CHANNEL1_VIDEO == ch->channel_id) ||

             (common->started == 2) ||

             (vpif->std_info.capture_format == 1)||

             (ch->vpifparams.std_info.hd_sd == 1)) {

                   channel1_intr_assert();

                   channel1_intr_enable(1);

                   enable_channel1(1);

         }

 

在vpif_streamoff()函数中的修改如下所示。

 

---

         if (VPIF_CHANNEL0_VIDEO == ch->channel_id) {

                   enable_channel0(0);

                   channel0_intr_enable(0);

         } else {

                   enable_channel1(0);

                   channel1_intr_enable(0);

         }

+++

         enable_channel0(0);

         channel0_intr_enable(0);

         enable_channel1(0);

         channel1_intr_enable(0);

 

1.3  修改($KERNEL_DIR)/arch/arm/mach-davinci/board-dm646x-evm.c

在board-dm646x-evm.c文件中的修改也不多,主要包括设置I2C参数、配置CPLD选通TVP7002。

首先,需要在setup_vpif_input_channel_mode()函数中通过设置CPLD的reg1来选通和使能TVP7002,修改方法如下所示。

 

---

         if (mux_mode) {

                   val &= VPIF_INPUT_TWO_CHANNEL;

                   value |= VIDCH1CLK;

                   value1 |= VIDCH1CLKSRCCH1;

         } else {

                   val |= VPIF_INPUT_ONE_CHANNEL;

                   value &= ~VIDCH1CLK;

                   value1 &= VIDCH1CLKSRCCH0;

         }

+++

         if (mux_mode) {

                   val &= VPIF_INPUT_TWO_CHANNEL;

                   value |= VIDCH1CLK;

                   value1 |= VIDCH1CLKSRCCH1;

         } else if (!mux_mode && !capture_format){

                   val |= 0x30;//VPIF_INPUT_ONE_CHANNEL;

                   value &= ~VIDCH1CLK;

                   value1 &= VIDCH1CLKSRCCH0;

         } else if (!mux_mode && capture_format) { // CCD/CMOS input

                   val = 0xbf;//val |= VPIF_INPUT_ONE_CHANNEL;

                   value &= ~VIDCH1CLK;

                   value1 &= VIDCH1CLKSRCCH0;

         }

 

接下来需要配置TVP7002的I2C驱动所需的参数(可以只用修改其I2C地址的值),这需要在static struct vpif_subdev_info vpif_capture_sdev_info[]这个数组中修改TVP7002的相关参数,修改如下所示。

 

---

         {

                   .name = "tvp7002",

                   .board_info = {

                            I2C_BOARD_INFO("tvp7002", 0x5d),

                            .platform_data = &tvp7002_pdata,

                   },

                   .vpif_if = {

                            .if_type = VPIF_IF_BT1120,

                            .hd_pol = 1,

                            .vd_pol = 1,

                            .fid_pol = 0,

                   },

         },

+++

         {

                   .name = "tvp7002",

                   .board_info = {

                            I2C_BOARD_INFO("tvp7002", 0x5c),

                            .platform_data = &tvp7002_pdata,

                   },

                   .vpif_if = {

                            .if_type = VPIF_IF_BT1120,

                            .hd_pol = 1,

                            .vd_pol = 1,

                            .fid_pol = 0,

                   },

         },

 

1.4  配置编译选项

在Ubuntu终端中切换到kernel所在路径,使用makemenuconfig命令进行编译选项配置。在出现图形界面配置对话框之后,依次进入”Device Drivers”->”Multimedia support”->”Video captureadapters”->”Encoders,decoders,sensors and other helper chips”,然后选中其中的”TexasInstruments TVP7002 video decoder”,并取消选中”TexasInstruments TVP5150 video decoder”,如图 1所示。

由于当前内核代码不能兼容TVP5150和TVP7002同时工作,在选中了TVP7002之后TVP5150就处于不工作状态。这个问题可以通过后期修改内核代码来解决。

 

1 Linux内核编译配置

 

配置好之后,保存退出,然后在终端中使用makeuImage命令编译内核镜像文件,在编译成功之后将其拷贝到/tftpboot目录下。


2        TVP7002 Linux驱动程序的验证

在之前撰写的OV5642 Linux驱动程序编写的文档中对如何编写简单程序对驱动进行测试有很详细的论述,这里也就不再赘述,只列出对于测试TVP7002驱动程序时所需要注意哪些地方,如果对于测试例程有问题可以翻看之前的OV5642相关文档。

2.1  Linux下的TVP7002测试例程编写

对于测试例程的编写只需要注意三点:一是设置输入通道,选择TVP7002输入;二是设置正确的视频标准(video standard);三是设置正确的帧格式(frame format)。

首先,必须正确设置输入通道,这是通过V4L2提供的VIDIOC_S_INPUT命令实现。在内核的($KERNEL_DIR)/arch/arm/mach-davinci/board-dm646x-evm.c文件中,dm6467_ch0_inputs[]数组定义了channel0可选择的各种输入通道,每个通道有一个index参数,也即我们需要通过VIDIOC_S_INPUT配置的参数。

 

static const struct vpif_input dm6467_ch0_inputs[] = {

         {

                   .input = {

                            .index = 0,

                            .name = "Composite",

                            .type = V4L2_INPUT_TYPE_CAMERA,

                            .std = V4L2_STD_PAL,

                   },

                   .subdev_name = "tvp5150",

         },

         {

                   .input = {

                            .index = 1,

                            .name = "Component",

                            .type = V4L2_INPUT_TYPE_CAMERA,

                            .std = TVP7002_STD_ALL,

                   },

                   .subdev_name = "tvp7002",

         },

         {

                   .input = {

                            .index = 2,//0,change from 0 to 2 on July 31th.

                            .name = "Camera",

                            .type = V4L2_INPUT_TYPE_CAMERA,

                            .std = V4L2_STD_CAMERA_PAL,

                   },

                   .subdev_name = "ov5642",

         },

};

 

从上面可以看出,TVP7002的index为1,所以配置输入通道选择的代码如下所示。

 

         unsigned int                        input                 = 1 ;/* 0 for 5150; 2 for 5642; 1 for 7002 */

ioctl (tvp7002, VIDIOC_G_INPUT, &input);

 

然后,根据($KERNEL_DIR)/include/media/davinci/videohd.h文件中的定义,选择所使用的视频格式为V4L2_STD_1080I_60。

 

/* Digital TV standards */

#define V4L2_STD_525P_60        ((v4l2_std_id)(0x0001000000000000ULL))

#define V4L2_STD_625P_50        ((v4l2_std_id)(0x0002000000000000ULL))

#define V4L2_STD_720P_60        ((v4l2_std_id)(0x0004000000000000ULL))

#define V4L2_STD_720P_50        ((v4l2_std_id)(0x0008000000000000ULL))

#define V4L2_STD_1080I_60       ((v4l2_std_id)(0x0010000000000000ULL))

#define V4L2_STD_1080I_50       ((v4l2_std_id)(0x0020000000000000ULL))

#define V4L2_STD_1080P_60       ((v4l2_std_id)(0x0040000000000000ULL))

#define V4L2_STD_1080P_50       ((v4l2_std_id)(0x0080000000000000ULL))

 

确定了视频格式之后通过ioctl函数设置参数。

 

         vid_std_id = ((v4l2_std_id)(0x0010000000000000ULL));

         ioctl (tvp7002, VIDIOC_S_STD, &vid_std_id);

 

接下来需要设置视频帧格式。对于1080I60格式的视频,分辨率为1920X 1080,颜色空间采用V4L2_PIX_FMT_NV16,隔行扫描传输。另外,由于像素格式为YCbCr 4:2:2,所以一帧图像的总大小为1920 X 1080 X 2字节。

 

         memset(&fmt, 0, sizeof(fmt));

         fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

         fmt.fmt.pix.width = 1920;

         fmt.fmt.pix.height = 1080;

         fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV16;

         fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

         fmt.fmt.pix.bytesperline = 1920;

         fmt.fmt.pix.sizeimage = 1920 * 1080 *2;

         ioctl (tvp7002, VIDIOC_S_FMT, &fmt);

 

编写好测试例程之后,需要在终端中输入以下命令,交叉编译,生成可以在目标平台上运行的二进制可执行程序。

 

arm-none-linux-gnueabi-gcc tvp7002.c -o tvp7002

 

接下来可以在当前目录看到生成的tvp7002文件,将其拷贝到NFS共享的目标路径,等待开发板上电之后运行。

2.2  修改loadmodule.sh配置内存

对于高清视频采集,需要使用大量的内存。依据前面的计算,1080P输入时,一帧图像的大小为1920 X 1080 X 2 = 4147200字节(4MB),这是很大的一片内存区域,需要合理分配内存才能保证程序的正常运行。在SDK中提供的loadmodule.sh脚本文件对内存的分配只适用于标清视频采集播放,在处理高清视频时如果仍然使用该配置方案则会出现内存分配不足的现象,所以需要进行修改。

DaVinci平台使用CMEM提供的pool机制管理内存,在处理高清视频时,对于CMEM的pool分配需要根据实际情况多规划一些较大的内存区域。考虑到一帧图像大小为4147200字节,所以CMEM的pool多设置一些块的大小为4147200字节。另外,考虑到总的CMEM模块总共能够分配的内存大小为110M,所以在在规划pool块大小时也要注意该限制,确保在不超过总大小的情况下最优地分配内存。最终的内存pool分配如下所示。

 

---

insmod cmemk.ko phys_start=0x84C00000 phys_end=0x8ba00000  pools=2x921600,1x460800,1x1048576,1x345600,2x86400,11x564528,5x677376,14x5396480,3x4147200,4x1451520,4x1843200

+++

insmod cmemk.ko phys_start=0x84C00000 phys_end=0x8ba00000 pools=7x5396480,10x4147200,1x3458400,10x1434240,11x663552,4x60000

 

2.3  使用Matlab显示图像

TVP7002的测试例程是将一帧图像存储到一个文件中,我们需要解码该文件然后显示出一幅正确的1920 X 1080分辨率的图像,这就需要通过Matlab来实现。由于之前已经使用Matlab处理过CCS中获取的视频帧,现在要处理Linux获取的一帧视频数据,只需在原来的例程上进行小小的修改。

由于Linux内核中对VPIF的配置与原来在CCS中配置不一样,导致VPIF存储到DDR2 SDRAM的视频帧格式有所不同,在CCS中配置的是使用交错存储格式,也即top field和bottom field分开存储,存储顺序依次为top field luma、top field chroma、bottom field luma和bottom field chroma。而在Linux中使用的是正常存储格式,是在deinterlace之后先存储亮度信号再存储色度信号的格式。针对这种格式,在Matlab中读取其数据时应该按照以下方法进行。

 

for i = 1:ver

    yt(i,:) = fread(fid, hor, 'uint8' );

    yb(i,:) = fread(fid, hor, 'uint8' );

end

for i = 1:ver

    ct(i,:) = fread(fid, hor, 'uint8' );

    cb(i,:) = fread(fid, hor, 'uint8' );

end

 

运行Matlab程序,得到高清视频图像,如图 2所示。

 

2 高清视频图像示例

Logo

更多推荐