ov9650摄像头驱动之——linux内核v4l2架构分析

ov9650摄像头驱动之——linux内核v4l2架构分析1

 本系列准备分为3-4篇来讲,因为说的太多会比较乱

 

v4l2视频驱动主要涉及几个知识点:

    摄像头方面的知识(摄像头厂家提供的芯片手册可以查看

要了解选用的摄像头的特性,包括访问控制方法、各种参数的配置方法、信号输出类型等。

    Camera解码器、控制器(主控芯片的芯片手册里面有摄像头相关的寄存器设置,比如2410里,里面主要是设置相关控制功能使能,芯片内部自己的架构

如果摄像头是模拟量输出的,要熟悉解码器的配置。最后数字视频信号进入camera控制器后,还要熟悉camera控制器的操作。

    V4L2API和数据结构控制(主要是用户空间需要的一些v4l2的操作,然后针对这些操作必须在底层实现相应的驱动

编写驱动前要熟悉应用程序访问V4L2的方法及设计到的数据结构。

    V4L2的驱动架构(这个是在底层写驱动,为用户空间提供相应的访问接口,可以参照内核里面的/drivers/media/video//zc301/zc301_core.c 中的ZC301视频驱动代码

它是内核提供的非常完善的v4l2架构的例子,基本上都可以在它的基础上进行修改!

最后编写出符合V4L2规范的视频驱动。

 

NO.1 摄像头方面的知识

 

ov9650摄像头,暂时先不说,先了解一下camera解码器、控制器,不同的主控芯片的camera控制器都差不多

 

static  struct  ov9650_reg
 
{
 
unsigned char  subaddr;
 
unsigned char  value;
 
}regs[] = {
 
/* OV9650 intialization parameter table for VGA application */
 
{0x12, 0x40}, // Camera Soft reset. Self cleared after reset.
 
{CHIP_DELAY, 10},
 
{0x11,0x81},{0x6a,0x3e},{0x3b,0x09},{0x13,0xe0},{0x01,0x80},{0x02,0x80},{0x00,0x00},{0x10,0x00},
 
{0x13,0xe5},{0x39,0x43},{0x38,0x12},{0x37,0x91},{0x35,0x91},{0x0e,0xa0},{0x1e,0x04},{0xA8,0x80},
 
{0x14,0x40},{0x04,0x00},{0x0c,0x04},{0x0d,0x80},{0x18,0xc6},{0x17,0x26},{0x32,0xad},{0x03,0x00},
 
{0x1a,0x3d},{0x19,0x01},{0x3f,0xa6},{0x14,0x2e},{0x15,0x10},{0x41,0x02},{0x42,0x08},{0x1b,0x00},
 
{0x16,0x06},{0x33,0xe2},{0x34,0xbf},{0x96,0x04},{0x3a,0x00},{0x8e,0x00},{0x3c,0x77},{0x8B,0x06},
 
{0x94,0x88},{0x95,0x88},{0x40,0xc1},{0x29,0x3f},{0x0f,0x42},{0x3d,0x92},{0x69,0x40},{0x5C,0xb9},
 
{0x5D,0x96},{0x5E,0x10},{0x59,0xc0},{0x5A,0xaf},{0x5B,0x55},{0x43,0xf0},{0x44,0x10},{0x45,0x68},
 
{0x46,0x96},{0x47,0x60},{0x48,0x80},{0x5F,0xe0},{0x60,0x8c},{0x61,0x20},{0xa5,0xd9},{0xa4,0x74},
 
{0x8d,0x02},{0x13,0xe7},{0x4f,0x3a},{0x50,0x3d},{0x51,0x03},{0x52,0x12},{0x53,0x26},{0x54,0x38},
 
{0x55,0x40},{0x56,0x40},{0x57,0x40},{0x58,0x0d},{0x8C,0x23},{0x3E,0x02},{0xa9,0xb8},{0xaa,0x92},
 
{0xab,0x0a},{0x8f,0xdf},{0x90,0x00},{0x91,0x00},{0x9f,0x00},{0xa0,0x00},{0x3A,0x01},{0x24,0x70},
 
{0x25,0x64},{0x26,0xc3},{0x2a,0x00},{0x2b,0x00},{0x6c,0x40},{0x6d,0x30},{0x6e,0x4b},{0x6f,0x60},
 
{0x70,0x70},{0x71,0x70},{0x72,0x70},{0x73,0x70},{0x74,0x60},{0x75,0x60},{0x76,0x50},{0x77,0x48},
 
{0x78,0x3a},{0x79,0x2e},{0x7a,0x28},{0x7b,0x22},{0x7c,0x04},{0x7d,0x07},{0x7e,0x10},{0x7f,0x28},
 
{0x80,0x36},{0x81,0x44},{0x82,0x52},{0x83,0x60},{0x84,0x6c},{0x85,0x78},{0x86,0x8c},{0x87,0x9e},
 
{0x88,0xbb},{0x89,0xd2},{0x8a,0xe6},
 
};

  

 

上面是需要顺序写ov9650的寄存器的地址和写入的数值(采用I2C子系统传输)

I2C子系统传输已经分析过,平台设备的资源可以在板文件中初始化:

 

1.修改vi drivers/i2c/busses/Kconfig

 

修改

config I2C_S3C2410

tristate "S3C2410 I2C Driver"

depends on ARCH_S3C2410 || ARCH_S3C64XX

help

  Say Y here to include support for I2C controller in the

  Samsung S3C2410 based System-on-Chip devices.

 

为:

config I2C_S3C2410

tristate "S3C2410 I2C Driver"

depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100

help

  Say Y here to include support for I2C controller in the

  Samsung S3C2410 based System-on-Chip devices.

2.内核配置并重新编译内核

$ make menuconfig

Device Drivers  --->

<*> I2C support  --->

<*>   I2C device interface

I2C Hardware Bus support  --->

<*> S3C2410 I2C Driver

3.修改vi arch/arm/mach-s5pc100/mach-smdkc100.c

 

查看原理图可以知道摄像头是接在I2C-0或1上,假设在1上,根据原理图修改i2c_devs1添加ov9650的内容,主要是ov9650的地址,这个在芯片手册上可以查到是0x60

而下面为什么是0x30呢?在我的另外一篇I2C子系统分析里面讲过。给个链接解释

修改:

static struct i2c_board_info i2c_devs1[] __initdata = {

};

 

为:

static struct i2c_board_info i2c_devs1[] __initdata = {

{

I2C_BOARD_INFO("ov9650", 0x30),

},

};

 

添加s5pc100 摄像头控制器平台设备相关内容,这些内容我们可以通过查看S5PC100的芯片手册查到

static struct resource s3c_camif_resource[] = {

[0] = {

.start = 0xEE200000,

.end   = 0xEE200000 + SZ_1M - 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_FIMC0,

.end   = IRQ_FIMC0,

.flags = IORESOURCE_IRQ,

}

 

};

 

static u64 s3c_device_camif_dmamask = 0xffffffffUL;

 

struct platform_device s3c_device_camif = {

.name  = "s5pc100-camif",

.id  = 0,

.num_resources  = ARRAY_SIZE(s3c_camif_resource),

.resource  = s3c_camif_resource,

.dev              = {

.dma_mask = &s3c_device_camif_dmamask,

.coherent_dma_mask = 0xffffffffUL

}

};

 

EXPORT_SYMBOL(s3c_device_camif);

 

注册摄像头控制平台设备:

smdkc100_devices中添加s3c_device_camif

 

static struct platform_device *smdkc100_devices[] __initdata = {

&s3c_device_camif,  //添加内容

};

 

4. 添加驱动(video

Make menuconfig

Device Drivers  --->  

 <*> Multimedia support  --->

 <*>   Video For Linux 

[*]     Enable Video For Linux API 1 (DEPRECATED) (NEW)

[*]   Video capture adapters (NEW)  --->

[*]   V4L USB devices (NEW)  ---> 

<*>   USB Video Class (UVC) 

[*]     UVC input events device support (NEW)

 <*>   USB ZC0301[P] webcam support (DEPRECATED)

 

这样device已经注册好了!

 

/* write a register */

static int ov9650_reg_write(struct i2c_client *client, u8 reg, u8 val)

{

int ret;

u8 _val;

unsigned char data[2] = { reg, val };

struct i2c_msg msg = {

.addr= client->addr,

.flags= 0,

.len= 2,

.buf= data,

};

//构建i2c_msg 

ret = i2c_transfer(client->adapter, &msg, 1);  //I2C适配器和I2C设备之间的一组消息的交换

return 0;

}

 

 

 

static void ov9650_init_regs(void)

{

int i;

for (i=0; i<ARRAY_SIZE(regs); i++)

 

{

if (regs[i].subaddr == 0xff) 

{

mdelay(regs[i].value);

continue;

}

ov9650_reg_write(ov9650_client, regs[i].subaddr, regs[i].value);

}

}

 至此,通过I2C总线已经将摄像头的寄存器初始化好了。


ov9650摄像头驱动之——linux内核v4l2架构分析2

NO.2 Camera解码器、控制器

 

1.根据camera控制器的描述,图像传输有两个DMA通道,我们用的是C通道,所以先将DMA内存初始化,因为在V4L2操作中有VIDIOC_REQBUFS中分配的数据缓存转换成物理地址的操作

所以DMA在用之前要初始化,包括实际物理地址的计算

init_image_buffer(camera_dev);// 初始化

 

 

 

复制代码
static int __inline__ init_image_buffer(struct s5pc100_camera_device *cam)

{

unsigned long size;

unsigned int order;

cam->frame = img_buff;

 

size = MAX_WIDTH * MAX_HEIGHT * formats[3].depth / 8; //sizeof image buffer is 600KBytes 

 

 

printk("each image buffer is %dKBytes.\n", (int)(size/1024));

 

order = get_order(size); //系统函数,size应该是2的n次幂,内存按页分配

img_buff[0].order = order;

img_buff[0].virt_base = __get_free_pages(GFP_KERNEL|GFP_DMA, img_buff[0].order);//申请DMA空间,该函数可分配多个页并返回分配内存的首地址,分配的页数为2的order次幂,分配的页也不清零。order允许的最大值是10(即1024页)或者11(即2048页),具体依赖于硬件平台。

img_buff[0].img_size = size;

img_buff[0].phy_base = img_buff[0].virt_base - PAGE_OFFSET + PHYS_OFFSET;// the DMA address.申请的DMA的物理地址,怎么计算的呢?首先要减去PAGE_OFFSET

why?因为在linux系统中,进程的4G空间被分为用户空间和内核空间两部分,用户空间的地址一般分布为0-3G(即RAGE_OFFSET),这样剩下的3-4G为内核空间,然后再加上 +PHYS_OFFSET(这个是由具体的cpu决定的,RAM的物理起始地址),这样的话phy_base就对应上了真正的物理地址

printk("get pages for img_buff[0..3] done.\n");

return 0;

error0:

return -ENOMEM;

}
复制代码

 

 

 

 

2.camera控制器的初始化

 

  • 图像源的格式设置
  • window cut的设置
  • 目标图像格式的设置
  • 图像的缩放、旋转设置
  • (可选,如果是用本地LCD显示的话)将输出buffer地址定位在Framebuffer显存地址中(即内存重叠,这样的话LCD就能直接显示了),因为这里没用到LCD,所以这个就省略

 

具体代码:

 

复制代码
init_camif_config(camera_dev);

 

static void init_camif_config(struct s5pc100_camera_device* c)

{

struct s5pc100_camera_device*cam = c;

 

cam->format = 3;// FIXME, C-path default format, see formats[] for detail.选择C通道

 

 

cam->srcHsize = 640;//  FIXME, the OV9650's horizontal output pixels.设置图像源的大小

 

cam->srcVsize = 480;// FIXME, the OV9650's verical output pixels.

 

设置图像源的大小

 

 

 

 

 

cam->wndHsize = 640;

 

cam->wndVsize = 480; //window cut的设置

 

cam->targetHsize = cam->wndHsize;//

目标图像格式的设置,与window图像重叠,全覆盖

 

cam->targetVsize = cam->wndVsize;

旋转没有设置

到目前为止,只是填充了cam的数据,但是camera控制器的源地址寄存器、目的地址寄存器都还没有配置

这两个寄存器的配置依赖于上面初始化的参数

 

update_camera_config(cam, (u32)-1);//这个函数中集成了一个函数,这个函数就是配置两个寄存器的操作

 

}

 

static void update_camera_config (struct s5pc100_camera_device *c, u32 cmdcode)

{

struct s5pc100_camera_device *cam = c;

update_camera_regs(cam);// config the regs directly.封装了下面的两个函数,其实没必要

}

 

static void __inline__ update_camera_regs(struct s5pc100_camera_device * cam)

{

update_source_fmt_regs(cam);

update_target_fmt_regs(cam);

}
复制代码

 

 

 

初始化source寄存器

复制代码
static void __inline__ update_source_fmt_regs(struct s5pc100_camera_device *c)
{
struct s5pc100_camera_device *cam = c;
u32 cfg;
 
cfg = (1<<31)// ITU-R BT.601 YCbCr 8-bit mode
|(0<<30)// CB,Cr value offset cntrol for YCbCr
|(640<<16)// target image width
|(0<<14)// input order is YCbYCr
|(640<<0);// source image height
writel(cfg, cam->reg_base + S5PC100_CISRCFMT);    //0xEE20_0000 + 0000_0000 图像源地址
printk("S5PC100_CIGCFMT = %x\n", readl(cam->reg_base + S5PC100_CISRCFMT));
 
cfg = (1<<15)
|(1<<14)
|(1<<30)
|(1<<29);
 
writel(cfg, cam->reg_base + S5PC100_CIWDOFST);///0xEE20_0000 + 0000_0004 清缓存fifo
cfg = (1<<26)
|(1<<29)
|(1<<16)
|(1<<7)
|(0<<0);
writel(cfg, cam->reg_base + S5PC100_CIGCTRL);///0xEE20_0000 + 0000_0008全局变量控制寄存器,包含了使能IRQ中断等操作
printk("S5PC100_CIGCTRL = %x\n", readl(cam->reg_base + S5PC100_CIGCTRL));
writel(0, cam->reg_base + S5PC100_CIWDOFST2);//0xEE20_0000 + 0000_0014窗口偏移寄存器
printk("OV9650_VGA mode\n");
}
 
复制代码

 


初始化目的寄存器
复制代码
static void __inline__ update_target_fmt_regs(struct s5pc100_camera_device * cam)
{
u32 cfg;
u32 h_shift;
u32 v_shift;
u32 prescaler_v_ratio;
u32 prescaler_h_ratio;
u32 main_v_ratio;
u32 main_h_ratio;
switch (formats[cam->format].pixelformat)
{
case V4L2_PIX_FMT_RGB565:
case V4L2_PIX_FMT_RGB24:
case V4L2_PIX_FMT_YUV420:
case V4L2_PIX_FMT_YUYV:
/* YCbCr 1 plane*/
printk("format V4L2_PIX_FMT_YUYV");
writel(img_buff[0].phy_base, cam->reg_base + S5PC100_CIOYSA1);//
 0xEE20_0000 + 0000_0018 
DMAY1输出开始地址寄存器
            将配置好的DMA物理开始地址赋给上述寄存器                                                                       
/* CIPRTRGFMT. */
cfg = (2 << 29) | (cam->targetHsize << 16)| (cam->targetVsize << 0)|(1<<13)|(1<<14)|(1<<15);
将cam里已经初始化好的大小信息移位,写入对应的位置
writel(cfg, cam->reg_base + S5PC100_CITRGFMT);    //
 0xEE20_0000 + 0000_0048 目标格式寄存器
/* CISCPRERATIO. */
calculate_prescaler_ratio_shift(cam->srcHsize, cam->targetHsize, &prescaler_h_ratio, &h_shift);//将源的横坐标进行压缩,返回 压缩率和移位数
calculate_prescaler_ratio_shift(cam->srcVsize, cam->targetVsize, &prescaler_v_ratio, &v_shift);//将源的纵坐标进行压缩
 
main_h_ratio = (cam->srcHsize << 8) / (cam->targetHsize << h_shift);
main_v_ratio = (cam->srcVsize << 8) / (cam->targetVsize << v_shift);
 
cfg = ((10 - (h_shift + v_shift)) << 28) | (prescaler_h_ratio << 16) | (prescaler_v_ratio << 0);       //移位因子,即共移位多少次
writel(cfg, cam->reg_base + S5PC100_CISCPRERATIO);//
 0xEE20_0000 + 0000_0050缩放比例寄存器,实现了图像的缩放处理
 
cfg = (cam->targetHsize << 16) | (cam->targetVsize << 0);
writel(cfg, cam->reg_base + S5PC100_CISCPREDST); //
 0xEE20_0000 + 0000_0054
最初的目的定位寄存器
 
cfg = (main_h_ratio << 16) | (main_v_ratio << 0);
writel(cfg, cam->reg_base + S5PC100_CISCCTRL); //main-scaler control Reg的配置
 
cfg = cam->targetVsize * cam->targetHsize;        //长*宽,0-27位,满足了
writel(cfg, cam->reg_base + S5PC100_CITAREA);//输出目标区域大小寄存器
 
cfg = (cam->targetVsize << 0) | (cam->targetHsize << 16);
writel(cfg, cam->reg_base + S5PC100_ORGOSIZE); //
0xEE20_0000 + 0000_0184 
DMA图像开始坐标寄存器
break;
 
}
}
下面的函数的意思是:传进来两个参数,一个是源的大小,另个是目的的大小,如果源是目标的64倍以上就错了,否则进行缩放,即源的大小是目标的32-64倍之间,就返回ratio(缩放比例)和shift(2的多少次幂),缩放比例是2的多少次幂,这样做的目的是方便移位,因为移位都是2的倍数
int calculate_prescaler_ratio_shift(unsigned int SrcSize, unsigned int DstSize, unsigned int*ratio,unsigned int  *shift)
{
if(SrcSize>=64*DstSize) {
return -EINVAL;
}
else if(SrcSize>=32*DstSize) {
*ratio=32;
*shift=5;
}
else if(SrcSize>=16*DstSize) {
*ratio=16;
*shift=4;
}
else if(SrcSize>=8*DstSize) {
*ratio=8;
*shift=3;
}
else if(SrcSize>=4*DstSize) {
*ratio=4;
*shift=2;
}
else if(SrcSize>=2*DstSize) {
*ratio=2;
*shift=1;
}
else {
*ratio=1;
*shift=0;
} 
return 0;
}

ov9650摄像头驱动之——linux内核v4l2架构分析3

NO.3 V4L2的API和数据结构

 

V4L2V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。

1、常用的结构体在内核目录include/linux/videodev2.h中定义

   struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS 
   struct v4l2_capability //
视频设备的功能,对应命令
VIDIOC_QUERYCAP 
   struct v4l2_input   //
视频输入信息,对应命令
VIDIOC_ENUMINPUT
   struct v4l2_standard //
视频的制式,比如PALNTSC,对应命令
VIDIOC_ENUMSTD 
   struct v4l2_format    //
帧的格式,对应命令VIDIOC_G_FMTVIDIOC_S_FMT

   struct v4l2_buffer   //驱动
中的一帧图像缓存,对应命令VIDIOC_QUERYBUF

   struct v4l2_crop   //视频信号矩形边框

      v4l2_std_id   //视频制式

2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义

 

 

VIDIOC_REQBUFS //分配内存  

VIDIOC_QUERYBUF //VIDIOC_REQBUFS中分配的数据缓存转换成物理地址

VIDIOC_QUERYCAP //查询驱动功能

VIDIOC_ENUM_FMT //获取当前驱动支持的视频格式

VIDIOC_S_FMT //设置当前驱动的频捕获格式

VIDIOC_G_FMT //读取当前驱动的频捕获格式

VIDIOC_TRY_FMT //验证当前驱动的显示格式

VIDIOC_CROPCAP //查询驱动的修剪能力

VIDIOC_S_CROP //设置视频信号的矩形边框

VIDIOC_G_CROP //读取视频信号的矩形边框

VIDIOC_QBUF //把数据从缓存中读取出来

VIDIOC_DQBUF //把数据放回缓存队列

VIDIOC_STREAMON //开始视频显示函数

VIDIOC_STREAMOFF //结束视频显示函数

VIDIOC_QUERYSTD //检查当前视频设备支持的标准,例如PALNTSC

  3、操作流程

V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。

下面列举出一种操作的流程,供参考。

1)打开设备文件

int fd = open(Devicename,mode);

    Devicename/dev/video0/dev/video1 ……

     ModeO_RDWR [| O_NONBLOCK]

       如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。

2)取得设备的capability

struct v4l2_capability capability

              int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);     

看看设备具有什么功能,比如是否具有视频输入特性。

3)选择视频输入

struct v4l2_input input

……初始化input

int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);     

一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。

4)检测视频支持的制式

复制代码
            v4l2_std_id std;

            do {

                 ret = ioctl(fd, VIDIOC_QUERYSTD, &std);

            } while (ret == -1 && errno == EAGAIN);

            switch (std) {

                case V4L2_STD_NTSC: 

                      //……

         case V4L2_STD_PAL:

             //……

}
复制代码

 

5)设置视频捕获格式

复制代码
struct v4l2_format fmt;

fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;

fmt.fmt.pix.height = height;

fmt.fmt.pix.width = width;

fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

ret = ioctl(fd, VIDIOC_S_FMT, &fmt);

if(ret) {

perror("VIDIOC_S_FMT/n");

close(fd);

return -1;

}
复制代码

 

6)向驱动申请帧缓存

     struct v4l2_requestbuffers  req;

if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {

           return -1;

}

       v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立FIFO,来提高视频采集的效率。

7)获取每个缓存的信息,并mmap到用户空间

复制代码
typedef struct VideoBuffer {

    void   *start;

    size_t  length;

} VideoBuffer;

                                                              

VideoBuffer*       buffers = calloc( req.count, sizeof(*buffers) );

struct v4l2_buffer    buf;

 

for (numBufs = 0; numBufs < req.count; numBufs++) {//映射所有的缓存

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

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    buf.index = numBufs;

    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {//获取到对应index的缓存信息,此处主要利用length信息及offset信息来完成后面的mmap操作。

        return -1;

    }

 

    buffers[numBufs].length = buf.length;

    // 转换成相对地址

    buffers[numBufs].start = mmap(NULL, buf.length,

        PROT_READ | PROT_WRITE,

        MAP_SHARED,

        fd, buf.m.offset);

 

    if (buffers[numBufs].start == MAP_FAILED) {

        return -1;

    }
复制代码

 

 8)开始采集视频

int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE

int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);

 9)取出FIFO缓存中已经采样的帧缓存

struct v4l2_buffer buf;

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

buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory=V4L2_MEMORY_MMAP;

buf.index=0;//此值由下面的ioctl返回

if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1)

{

    return -1;

}

根据返回的buf.index找到对应的mmap映射好的缓存,取出视频数据。

10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集

if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {

    return -1;

}

11)停止视频的采集

int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);

12)关闭视频设备

close(fd);

 

NO.4 V4L2的驱动架构

 

上述流程的各个操作都需要有底层V4L2驱动的支持。内核中有一些非常完善的例子。

比如:linux-2.6.26内核目录/drivers/media/video//zc301/zc301_core.c 中的ZC301视频驱动代码。上面的V4L2操作流程涉及的功能在其中都有实现。

1V4L2驱动注册、注销函数

       Video核心层(drivers/media/video/videodev.c)提供了注册函数

int video_register_device(struct video_device *vfd, int type, int nr)

video_device:  要构建的核心数据结构

Type:  表示设备类型,此设备号的基地址受此变量的影响

Nr:    如果end-base>nr>0 :次设备号=base(基准值,受type影响)+nr

否则:系统自动分配合适的次设备号

       具体驱动只需要构建video_device结构,然后调用注册函数既可。

如:zc301_core.c中的

       err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER,

                          video_nr[dev_nr]);

       Video核心层(drivers/media/video/videodev.c)提供了注销函数

void video_unregister_device(struct video_device *vfd)

 

2struct video_device 的构建

              video_device结构包含了视频设备的属性和操作方法。参见zc301_core.c

复制代码
strcpy(cam->v4ldev->name, "ZC0301[P] PC Camera");

       cam->v4ldev->owner = THIS_MODULE;

       cam->v4ldev->type = VID_TYPE_CAPTURE | VID_TYPE_SCALES;

       cam->v4ldev->fops = &zc0301_fops;

       cam->v4ldev->minor = video_nr[dev_nr];

       cam->v4ldev->release = video_device_release;

       video_set_drvdata(cam->v4ldev, cam);
复制代码

 

       大家发现在这个zc301的驱动中并没有实现struct video_device中的很多操作函数vidioc_querycapvidioc_g_fmt_cap等。主要原因是struct file_operations zc0301_fops中的zc0301_ioctl实现了前面的所有ioctl操作。所以就不需要在struct video_device再实现struct video_device中的那些操作了。

       另一种实现方法如下:

复制代码
static struct video_device camif_dev =

{

       .name             = "s3c2440 camif",

       .type              = VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_SUBCAPTURE,

       .fops              = &camif_fops,

       .minor            = -1,

       .release    = camif_dev_release,

       .vidioc_querycap      = vidioc_querycap,

       .vidioc_enum_fmt_cap  = vidioc_enum_fmt_cap,

       .vidioc_g_fmt_cap     = vidioc_g_fmt_cap,

 .vidioc_s_fmt_cap     = vidioc_s_fmt_cap,

       .vidioc_queryctrl = vidioc_queryctrl,

       .vidioc_g_ctrl = vidioc_g_ctrl,

       .vidioc_s_ctrl = vidioc_s_ctrl,

};

static struct file_operations camif_fops =

{

       .owner           = THIS_MODULE,

       .open             = camif_open,

       .release    = camif_release,

       .read              = camif_read,

       .poll        = camif_poll,

       .ioctl              = video_ioctl2, /* V4L2 ioctl handler */

       .mmap           = camif_mmap,

       .llseek            = no_llseek,

};
复制代码

 

注意video_ioctl2videodev.c中是实现的。video_ioctl2中会根据ioctl不同的cmd

调用video_device中的操作方法。

3Video核心层的实现

       参见内核/drivers/media/videodev.c

1)注册256个视频设备

复制代码
       static int __init videodev_init(void)

{

int ret;

           if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) {

                  return -EIO;

           }

           ret = class_register(&video_class);

……

}
复制代码

 

上面的代码注册了256个视频设备,并注册了video_class类。video_fops为这256个设备共同的操作方法。

2V4L2驱动注册函数的实现

 

复制代码
int video_register_device(struct video_device *vfd, int type, int nr)

{

int i=0;

int base;

int end;

int ret;

       char *name_base;

 

       switch(type) //根据不同的type确定设备名称、次设备号

       {

              case VFL_TYPE_GRABBER:

                     base=MINOR_VFL_TYPE_GRABBER_MIN;

                     end=MINOR_VFL_TYPE_GRABBER_MAX+1;

                     name_base = "video";

                     break;

              case VFL_TYPE_VTX:

                     base=MINOR_VFL_TYPE_VTX_MIN;

                     end=MINOR_VFL_TYPE_VTX_MAX+1;

                     name_base = "vtx";

                     break;

              case VFL_TYPE_VBI:

                     base=MINOR_VFL_TYPE_VBI_MIN;

                     end=MINOR_VFL_TYPE_VBI_MAX+1;

                     name_base = "vbi";

                     break;

              case VFL_TYPE_RADIO:

                     base=MINOR_VFL_TYPE_RADIO_MIN;

                     end=MINOR_VFL_TYPE_RADIO_MAX+1;

                     name_base = "radio";

                     break;

              default:

                     printk(KERN_ERR "%s called with unknown type: %d/n",

                            __func__, type);

                     return -1;

       }

 

       /* 计算出次设备号 */

       mutex_lock(&videodev_lock);

       if (nr >= 0  &&  nr < end-base) {

              /* use the one the driver asked for */

              i = base+nr;

              if (NULL != video_device[i]) {

                     mutex_unlock(&videodev_lock);

                     return -ENFILE;

              }

       } else {

              /* use first free */

              for(i=base;i<end;i++)

                     if (NULL == video_device[i])

                            break;

              if (i == end) {

                     mutex_unlock(&videodev_lock);

                     return -ENFILE;

              }

       }

       video_device[i]=vfd; //保存video_device结构指针到系统的结构数组中,最终的次设备号和i相关。

       vfd->minor=i;

       mutex_unlock(&videodev_lock);

       mutex_init(&vfd->lock);

 

       /* sysfs class */

       memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev));

       if (vfd->dev)

              vfd->class_dev.parent = vfd->dev;

       vfd->class_dev.class       = &video_class;

       vfd->class_dev.devt       = MKDEV(VIDEO_MAJOR, vfd->minor);

       sprintf(vfd->class_dev.bus_id, "%s%d", name_base, i - base);//最后在/dev目录下的名称

       ret = device_register(&vfd->class_dev);//结合udev或mdev可以实现自动在/dev下创建设备节点

       ……

}
复制代码

 

从上面的注册函数中可以看出V4L2驱动的注册事实上只是完成了设备节点的创建,如:/dev/video0。和video_device结构指针的保存。

3)视频驱动的打开过程

当用户空间调用open打开对应的视频文件时,如:

int fd = open(/dev/video0, O_RDWR);

对应/dev/video0的文件操作结构是/drivers/media/videodev.c中定义的video_fops

static const struct file_operations video_fops=

{

       .owner           = THIS_MODULE,

       .llseek            = no_llseek,

       .open             = video_open,

};

奇怪吧,这里只实现了open操作。那么后面的其它操作呢?还是先看看video_open吧。

复制代码
static int video_open(struct inode *inode, struct file *file)

{

       unsigned int minor = iminor(inode);

       int err = 0;

       struct video_device *vfl;

       const struct file_operations *old_fops;

 

       if(minor>=VIDEO_NUM_DEVICES)

              return -ENODEV;

       mutex_lock(&videodev_lock);

       vfl=video_device[minor];

       if(vfl==NULL) {

              mutex_unlock(&videodev_lock);

              request_module("char-major-%d-%d", VIDEO_MAJOR, minor);

              mutex_lock(&videodev_lock);

              vfl=video_device[minor]; //根据次设备号取出video_device结构

              if (vfl==NULL) {

                     mutex_unlock(&videodev_lock);

                     return -ENODEV;

              }

       }

       old_fops = file->f_op;

       file->f_op = fops_get(vfl->fops);//替换此打开文件的file_operation结构。后面的其它针对此文件的操作都由新的结构来负责了。也就是由每个具体的video_device的fops负责。

       if(file->f_op->open)

              err = file->f_op->open(inode,file);

       if (err) {

              fops_put(file->f_op);

              file->f_op = fops_get(old_fops);

       }

……

}


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值