依旧驱动模块的注册过程,视频采集的驱动模块主要分为:videodev_init(位于videodev_init.c),vpfe_init(位于davinci_vpfe.c),tvp5146_i2c_init(该模块在这里不做解析,是实现一款解码的芯片)。
videodev_init(位于videodev_init.c)模块解析
该驱动模块下,主要是完成V4L2核心的驱动的注册。源码如下:
static int __init videodev_init(void) { int ret; printk(KERN_INFO "Linux video capture interface: v1.00\n"); if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) { //注册一个视频驱动,主设备号为81 printk(KERN_WARNING "video_dev: unable to get major %d\n", VIDEO_MAJOR); return -EIO; } ret = class_register(&video_class); if (ret < 0) { unregister_chrdev(VIDEO_MAJOR, VIDEO_NAME); printk(KERN_WARNING "video_dev: class_register failed\n"); return -EIO; } return 0; }
从代码的分析中我们看到,V4L2的架构中,先是注册一个主设备号为81的字符型设备,文件操作指针定义为video_fops。的确在上述的模块中完成的内容不多,这只是V4L2驱动的基本框架,可以再多平台之间移植。因此在这个框架下针对自己的处理器平台需要完成video_device的抽象设备和驱动的注册。也就是说V4L2下面可以注册进入很多的视频抽象设备。
vpfe_init(位于davinci_vpfe.c)驱动模块中主要完成的内容就是完成video_device的注册,当然因为结合了专用的处理器平台在完成设备和驱动和注册的同时还需要完成相关硬件设备需求的初始化。
static int vpfe_init(void) { int i = 0; int fbuf_size; ccdc_frmfmt frame_format; void *mem; int ret = 0; if (device_type == TVP5146) { fbuf_size = VPFE_TVP5146_MAX_FBUF_SIZE; //768*576*2 vpfe_device = vpfe_device_ycbcr; frame_format = vpfe_device.ccdc_params_ycbcr.frm_fmt; } else if (device_type == MT9T001) { fbuf_size = VPFE_MT9T001_MAX_FBUF_SIZE; vpfe_device = vpfe_device_raw; frame_format = vpfe_device.ccdc_params_raw.frm_fmt; } else { return -1; } /* allocate memory at initialization time to guarentee availability *///对默认的3路缓冲区进行内存的申请 for (i = 0; i < VPFE_DEFNUM_FBUFS; i++) { //VPFE_DEFNUM_FBUFS=3 mem = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, get_order(fbuf_size)); //需要的一帧内存大小为768*576*2 if (mem) { unsigned long adr = (unsigned long)mem; //获取32位的内存地址 u32 size = PAGE_SIZE << (get_order(fbuf_size));//get_order(fbuf_size)=8,PAGE_SIZE=4K, while (size > 0) { /* make sure the frame buffers are never swapped out of memory */ SetPageReserved(virt_to_page(adr));//对256页每页内存申请被保留,不被他人使用 adr += PAGE_SIZE; size -= PAGE_SIZE; }//获取每一个物理缓存区的首地址,地址存入fbuffers数组 vpfe_device.fbuffers[i] = (u8 *) mem;//3 } else { while (--i >= 0) { free_reserved_pages((unsigned long) vpfe_device. fbuffers[i], fbuf_size); } printk(KERN_INFO "frame buffer memory allocation failed.\n"); return -ENOMEM; } } if (driver_register(&vpfe_driver) != 0) { printk(KERN_INFO "driver registration failed\n"); return -1; } if (platform_device_register(&_vpfe_device) != 0) { driver_unregister(&vpfe_driver); printk(KERN_INFO "device registration failed\n"); return -1; } ccdc_reset(); //CCDC复位不使能 if (device_type == TVP5146) { ret = tvp5146_ctrl(TVP5146_INIT, NULL); //decoder I2C驱动的初始化 if (ret >= 0) { ret = tvp5146_ctrl(TVP5146_RESET, NULL); /* configure the tvp5146 to default parameters */ ret |= tvp5146_ctrl(TVP5146_CONFIG, &vpfe_device.tvp5146_params); //vpfe_device=vpfe_device_ycbcr } if (ret < 0) { tvp5146_ctrl(TVP5146_CLEANUP, NULL); } } else if (device_type == MT9T001) { /* enable video port in case of raw capture */ ccdc_enable_vport(); vpfe_device.config_dev_fxn = mt9t001_ctrl; ret = vpfe_device.config_dev_fxn(MT9T001_INIT, &vpfe_device.std, &vpfe_device.device_params); } if (ret < 0) { platform_device_unregister(&_vpfe_device); driver_unregister(&vpfe_driver); /* Free memory for all image buffers */ for (i = 0; i < VPFE_DEFNUM_FBUFS; i++) { free_reserved_pages((unsigned long) vpfe_device.fbuffers[i], fbuf_size); } return -1; } /* setup interrupt handling */ /* request VDINT1 if progressive format */ if (frame_format == CCDC_FRMFMT_PROGRESSIVE) { //frame_format=CCDC_FRMFMT_INTERLACED=1 ret = request_irq(IRQ_VDINT1, vdint1_isr, SA_INTERRUPT, "dm644xv4l2", (void *)&vpfe_device); //逐行格式,申请VDINT1中断 if (ret < 0) { platform_device_unregister(&_vpfe_device); driver_unregister(&vpfe_driver); /* Free memory for all image buffers */ for (i = 0; i < VPFE_DEFNUM_FBUFS; i++) { free_reserved_pages((unsigned long) vpfe_device. fbuffers[i], fbuf_size); } return -1; } } ret = request_irq(IRQ_VDINT0, vpfe_isr, SA_INTERRUPT, "dm644xv4l2", (void *)&vpfe_device);//申请VDINT0中断 if (ret < 0) { platform_device_unregister(&_vpfe_device); driver_unregister(&vpfe_driver); /* Free memory for all image buffers */ for (i = 0; i < VPFE_DEFNUM_FBUFS; i++) { free_reserved_pages((unsigned long) vpfe_device.fbuffers[i], fbuf_size); } free_irq(IRQ_VDINT1, &vpfe_device); return -1; } printk(KERN_INFO "DaVinci v4l2 capture driver V1.0 loaded\n"); return 0; } /
在这个初始化的内容中,我们看到做了很多的内容,这里的vpfe_device = vpfe_device_ycbcr;就是上面提到的video_device的实例,也就是DM6446的视频前端VPFE这个视频设备。同时可以发现,在这里进行了连续的内存页式缓存区的申请(主要用于后续的视频采集内存缓存区队列设置的相关内容),完成vpfe_device的初始化。随后通过基于平台设备进行了驱动和设备的注册。其实这个设备和驱动的注册,完全就是为了调用匹配以后的probe函数,进一步完成相关视频设备文件(主设备号为81,次设备号不一)节点的创建(相当于手动在终端完成mknod操作),只有这样才可以在应用层打开设备之后,才能经过系统调来调用主设备号81的文件操作指针video_fops。同时也完成了VPFE前端CCDC的中断申请。下面来看porbe的指针函数。
static int __init vpfe_probe(struct device *device) //传入参数_vpfe_device->dev { struct video_device *vfd; vpfe_obj *vpfe = &vpfe_device; //vpfe_device=vpfe_device_ycbcr vpfe_dev = device; dev_dbg(vpfe_dev, "\nStarting of vpfe_probe..."); /* alloc video device */ if ((vfd = video_device_alloc()) == NULL) { return -ENOMEM; } *vfd = vpfe_video_template; //video_device的类型 vfd->dev = device; vfd->release = video_device_release; snprintf(vfd->name, sizeof(vfd->name), "DM644X_VPFE_DRIVER_V%d.%d.%d", (VPFE_VERSION_CODE >> 16) & 0xff, (VPFE_VERSION_CODE >> 8) & 0xff, (VPFE_VERSION_CODE) & 0xff); vpfe->video_dev = vfd; //vpfe_obj的实例vpfe_device获取video_device实例 vpfe->usrs = 0; vpfe->io_usrs = 0; vpfe->started = FALSE; vpfe->latest_only = TRUE; v4l2_prio_init(&vpfe->prio); init_MUTEX(&vpfe->lock); /* register video device */ dev_dbg(vpfe_dev, "trying to register vpfe device.\n"); dev_dbg(vpfe_dev, "vpfe=%x,vpfe->video_dev=%x\n", (int)vpfe, (int)&vpfe->video_dev); if (video_register_device(vpfe->video_dev, VFL_TYPE_GRABBER, -1) < 0) { //完成视频设备vfd的注册 video_device_release(vpfe->video_dev); vpfe->video_dev = NULL; return -1; } dev_dbg(vpfe_dev, "DM644X vpfe: driver version V%d.%d.%d loaded\n", (VPFE_VERSION_CODE >> 16) & 0xff, (VPFE_VERSION_CODE >> 8) & 0xff, (VPFE_VERSION_CODE) & 0xff); dev_dbg(vpfe_dev, "vpfe: registered device video%d\n", vpfe->video_dev->minor & 0x1f); /* all done */ return 0; }
在这里我们可以看到调用video_register_device完成
int video_register_device(struct video_device *vfd, int type, int nr) { int i=0; int base; int end; char *name_base; switch(type) { case VFL_TYPE_GRABBER: //明是一个图像采集设备?包括摄像头、调谐器 base=0; end=64; name_base = "video"; break; case VFL_TYPE_VTX: //代表视传设备 base=192; end=224; name_base = "vtx"; break; case VFL_TYPE_VBI: //代表的设备是从视频消隐的时间段取得信息的设备。 base=224; end=240; name_base = "vbi"; break; case VFL_TYPE_RADIO: //代表无线电设备 base=64; end=128; name_base = "radio"; break; default: return -1; } /* pick a minor number */ down(&videodev_lock); if (nr >= 0 && nr < end-base) { /* use the one the driver asked for */ i = base+nr; if (NULL != video_device[i]) { up(&videodev_lock); return -ENFILE; } } else { /* use first free */ //自动分配设备号,如果有当前全局的256个vide0_device为空 for(i=base;i<end;i++) if (NULL == video_device[i]) break; if (i == end) { up(&videodev_lock); return -ENFILE; } } video_device[i]=vfd; vfd->minor=i; //次设备号 up(&videodev_lock); sprintf(vfd->devfs_name, "v4l/%s%d", name_base, i - base); //生成设备文件节点v4l/video0 devfs_mk_cdev(MKDEV(VIDEO_MAJOR, vfd->minor), S_IFCHR | S_IRUSR | S_IWUSR, vfd->devfs_name); //对当前设备在dev下自动创建设备文件节点 init_MUTEX(&vfd->lock); /* sysfs class */ memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev)); if (vfd->dev) vfd->class_dev.dev = vfd->dev; vfd->class_dev.class = &video_class; strlcpy(vfd->class_dev.class_id, vfd->devfs_name + 4, BUS_ID_SIZE); class_device_register(&vfd->class_dev); class_device_create_file(&vfd->class_dev, &class_device_attr_name); class_device_create_file(&vfd->class_dev, &class_device_attr_dev); #if 1 /* needed until all drivers are fixed */ if (!vfd->release) printk(KERN_WARNING "videodev: \"%s\" has no release callback. " "Please fix your driver for proper sysfs support, see " "http://lwn.net/Articles/36850/\n", vfd->name); #endif return 0; }
我们可以看到在这个视频设备注册的函数中,首先会判断你的设备是采集,传输还是其他用途,用于确定devfs_mk_cdev中的设备文件节点的名字。最终按其自动创建设备文件节点的方法,最终创建出/v4l/video0,/v4l/video1之类的设备节点,此类设备就如同mknod的手动功能,只是设备的次设备号不同,主设备号都为81。这样就符合一个驱动可以带多个设备。只是在设备open时,会首先根据次设备号来选择对应的设备,然后获取该设备的fops,进行后续的操作ioctl时,转向专用的设备(即我们自己的设备不再是之前注册的V4L2最上层的抽象设备)。
后一博文,我将完成V4L2的ioctl的解析,这块内容是英语词典核心调用所在。在这部分内容里会涉及到对tvp5416的设置,CCDC的控制以及视频缓存区内存的管理等。