[轉載] http://www.360doc.com/content/15/0414/17/16107733_463178662.shtml
dm6446是基於Davinci架構設計的多媒體處理器。在這裏我們分析的Linux源碼是montavista的2.6.10的版本,該源碼中使用的視頻驅動架構爲V4L2的框架。對這個框架而言,網上已經存在大量的分析,但涉及的內容主要都是框架層的封裝以及相關應用層的解析,底層核心的內容很少會涉及。在這裏,我將結合DM6446的視頻處理前端VPFE中的CCDC模塊,完成視頻採集的驅動核心內容的解析,核心源碼位於kernel/driver/media/video下面。
依舊驅動模塊的註冊過程,視頻採集的驅動模塊主要分爲:videodev_init(位於videodev.c),vpfe_init(位於davinci_vpfe.c),tvp5146_i2c_init(該模塊在這裏不做解析,是實現一款解碼的芯片)。
1、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下面可以註冊進入很多的視頻抽象設備。
2、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完成
[plain] view plaincopy
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的控制以及視頻緩存區內存的管理等。