/* saa7146.c - video4linux driver for saa7146-based video hardware Copyright (C) 1998-2002 Michael Hunold With ideas taken from: "device driver for philips saa7134 based TV cards" (c) 2001,02 Gerd Knorr [SuSE Labs] This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "saa7146.h" #if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) #define KBUILD_MODNAME "saa7146" #endif /* global variables */ struct list_head devices; struct semaphore devices_lock; int saa7146_num = 0; /* fixme */ int debug = 255; #ifdef MODULE MODULE_PARM(debug,"i"); MODULE_PARM_DESC(debug, "debug level (default: 0)"); #endif static void dump_registers(struct saa7146_dev* dev) { int i = 0; INFO((" @ %li jiffies:\n",jiffies)); for(i = 0; i <= 0x148; i+=4) { printk("0x%03x: 0x%08x\n",i,saa7146_read(dev,i)); } } /********************************************************************************/ /* common dma and page table functions */ void saa7146_dma_free(struct saa7146_dev *dev,struct saa7146_buf *buf) { DEB_EE(("dev:%p, buf:%p\n",dev,buf)); if (in_interrupt()) BUG(); videobuf_waiton(&buf->vb,0,0); videobuf_dma_pci_unmap(dev->pci, &buf->vb.dma); videobuf_dma_free(&buf->vb.dma); buf->vb.state = STATE_NEEDS_INIT; } #define SAA7146_PGTABLE_SIZE 4096 void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt) { //fm DEB_EE(("pci:%p, pt:%p\n",pci,pt)); if (NULL == pt->cpu) return; pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); pt->cpu = NULL; } int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt) { u32 *cpu; dma_addr_t dma_addr; //fm DEB_EE(("pci:%p, pt:%p\n",pci,pt)); cpu = pci_alloc_consistent(pci, SAA7146_PGTABLE_SIZE, &dma_addr); if (NULL == cpu) { //fm ERR(("pci_alloc_consistent() failed.")); return -ENOMEM; } pt->size = SAA7146_PGTABLE_SIZE; pt->cpu = cpu; pt->dma = dma_addr; return 0; } void saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, struct scatterlist *list, int length ) { u32 *ptr, fill; int i,p; //fm DEB_EE(("pci:%p, pt:%p, sl:%p, len:%d\n",pci,pt,list,length)); /* if we have a user buffer, the first page may not be aligned to a page boundary. */ pt->offset = list->offset; ptr = pt->cpu; for (i = 0; i < length; i++, list++) { for (p = 0; p * 4096 < list->length; p++, ptr++) { *ptr = sg_dma_address(list) - list->offset; } } /* safety; fill the page table up with the last valid page */ fill = *(ptr-1); for(;i<1024;i++) { *ptr++ = fill; } /* ptr = pt->cpu; for(j=0;j<60;j++) { printk("ptr1 %d: 0x%08x\n",j,ptr[j]); } */ } /********************************************************************************/ /* file operations */ static int video_open(struct inode *inode, struct file *file) { unsigned int minor = minor(inode->i_rdev); struct saa7146_dev *h = NULL, *dev = NULL; struct list_head *list; struct saa7146_fh *fh = NULL; int result = 0; enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; DEB_EE(("inode:%p, file:%p, minor:%d\n",inode,file,minor)); down(&devices_lock); list_for_each(list,&devices) { h = list_entry(list, struct saa7146_dev, item); DEB_D(("trying: %p @ major %d,%d\n",h,h->video_dev.minor,h->vbi_dev.minor)); if (h->video_dev.minor == minor) { dev = h; } if (h->vbi_dev.minor == minor) { type = V4L2_BUF_TYPE_VBI_CAPTURE; dev = h; } } DEB_D(("using: %p\n",dev)); if (NULL == dev) { DEB_S(("no such video device.\n")); result = -ENODEV; goto out; } /* check if an extension is registered */ if( NULL == dev->ext ) { DEB_S(("no extension registered for this device.\n")); result = -ENODEV; goto out; } /* allocate per open data */ fh = kmalloc(sizeof(*fh),GFP_KERNEL); if (NULL == fh) { DEB_S(("cannot allocate memory for per open data.\n")); result = -ENOMEM; goto out; } memset(fh,0,sizeof(*fh)); /* fixme: increase some usage counts */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) dev->ext->inc_use(dev); MOD_INC_USE_COUNT; #else if( 0 == try_module_get(dev->ext->module)) { result = -EINVAL; goto out; } #endif file->private_data = fh; fh->dev = dev; fh->type = type; dev->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; dev->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; saa7146_video_uops.open(dev,fh); if( 0 != BOARD_CAN_DO_VBI(dev) ) { saa7146_vbi_uops.open(dev,fh); } result = 0; out: if( fh != 0 && result != 0 ) { kfree(fh); } up(&devices_lock); return result; } static int video_release(struct inode *inode, struct file *file) { struct saa7146_fh *fh = file->private_data; struct saa7146_dev *dev = fh->dev; DEB_EE(("inode:%p, file:%p\n",inode,file)); down(&devices_lock); saa7146_video_uops.release(dev,fh,file); if( 0 != BOARD_CAN_DO_VBI(dev) ) { saa7146_vbi_uops.release(dev,fh,file); } /* fixme: decrease some usage counts */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) dev->ext->dec_use(dev); MOD_DEC_USE_COUNT; #else module_put(dev->ext->module); #endif file->private_data = NULL; kfree(fh); up(&devices_lock); return 0; } static int video_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { /* DEB_EE(("inode:%p, file:%p, cmd:%d, arg:%li\n",inode, file, cmd, arg)); */ return video_usercopy(inode, file, cmd, arg, video_do_ioctl); } static int video_mmap(struct file *file, struct vm_area_struct * vma) { struct saa7146_fh *fh = file->private_data; struct saa7146_dev *dev = fh->dev; struct videobuf_queue *q; switch (fh->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: { DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n",file, vma)); q = &fh->video_q; break; } case V4L2_BUF_TYPE_VBI_CAPTURE: { DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n",file, vma)); q = &fh->vbi_q; break; } default: BUG(); return 0; } return videobuf_mmap_mapper(vma,q); } static unsigned int video_poll(struct file *file, struct poll_table_struct *wait) { struct saa7146_fh *fh = file->private_data; struct saa7146_dev *dev = fh->dev; struct videobuf_buffer *buf = NULL; struct videobuf_queue *q; DEB_EE(("file:%p, poll:%p\n",file, wait)); if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { if( 0 == fh->vbi_q.streaming ) return videobuf_poll_stream(file, &fh->vbi_q, wait); q = &fh->vbi_q; } else { q = &fh->video_q; } if (!list_empty(&q->stream)) buf = list_entry(q->stream.next, struct videobuf_buffer, stream); if (!buf) { return POLLERR; } poll_wait(file, &buf->done, wait); if (buf->state == STATE_DONE || buf->state == STATE_ERROR) { return POLLIN|POLLRDNORM; } return 0; } static ssize_t video_read(struct file *file, char *data, size_t count, loff_t *ppos) { struct saa7146_fh *fh = file->private_data; struct saa7146_dev *dev = fh->dev; switch (fh->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: { DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%d\n",file, data, count)); return saa7146_video_uops.read(file,data,count,ppos); } case V4L2_BUF_TYPE_VBI_CAPTURE: { DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%d\n",file, data, count)); return saa7146_vbi_uops.read(file,data,count,ppos); } break; default: BUG(); return 0; } } static struct file_operations video_fops = { owner: THIS_MODULE, open: video_open, release: video_release, read: video_read, poll: video_poll, mmap: video_mmap, ioctl: video_ioctl, llseek: no_llseek, }; /********************************************************************************/ /* interrupt handler */ static void interrupt_hw(int irq, void *dev_id, struct pt_regs *regs) { struct saa7146_dev *dev = (struct saa7146_dev*)dev_id; u32 isr = 0; /* read out the interrupt status register */ isr = saa7146_read(dev, ISR); /* is this our interrupt? */ if ( 0 == isr ) { /* nope, some other device */ return; } saa7146_write(dev, ISR, isr); if( 0 != (dev->ext->irq_mask & isr )) { if( 0 != dev->ext->irq_func ) { dev->ext->irq_func(dev, &isr); } isr &= ~dev->ext->irq_mask; } if (0 != (isr & (MASK_27))) { DEB_INT(("irq: RPS0.\n")); saa7146_video_uops.irq_done(dev,isr); isr &= ~MASK_27; } if (0 != (isr & (MASK_28))) { u32 mc2 = saa7146_read(dev, MC2); isr &= ~MASK_28; if( 0 != (mc2 & MASK_15)) { DEB_INT(("irq: RPS1 vbi workaround.\n")); wake_up(&dev->vbi_wq); saa7146_write(dev,MC2, MASK_31); return; } DEB_INT(("irq: RPS1.\n")); saa7146_vbi_uops.irq_done(dev,isr); } if( 0 != isr ) { ERR(("warning: interrupt enabled, but not handled properly. (0x%08x)\n",isr)); } } /********************************************************************************/ /* common buffer functions */ int saa7146_buffer_queue(struct saa7146_dev *dev, struct saa7146_dmaqueue *q, struct saa7146_buf *buf) { #if DEBUG_SPINLOCKS BUG_ON(!spin_is_locked(&dev->slock)); #endif DEB_EE(("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf)); if( NULL == q ) { ERR(("internal error: fatal NULL pointer for q.\n")); return 0; } if (NULL == q->curr) { q->curr = buf; DEB_D(("immediately activating buffer %p\n", buf)); buf->activate(dev,buf,NULL); } else { list_add_tail(&buf->vb.queue,&q->queue); buf->vb.state = STATE_QUEUED; DEB_D(("adding buffer %p to queue. (active buffer present)\n", buf)); } return 0; } void saa7146_buffer_finish(struct saa7146_dev *dev, struct saa7146_dmaqueue *q, int state) { #if DEBUG_SPINLOCKS BUG_ON(!spin_is_locked(&dev->slock)); #endif if( NULL == q->curr ) { ERR(("internal error: fatal NULL pointer for q->curr.\n")); return; } DEB_EE(("dev:%p, dmaq:%p, state:%d\n", dev, q, state)); /* finish current buffer */ q->curr->vb.state = state; do_gettimeofday(&q->curr->vb.ts); wake_up(&q->curr->vb.done); q->curr = NULL; } void saa7146_buffer_next(struct saa7146_dev *dev, struct saa7146_dmaqueue *q, int vbi) { struct saa7146_buf *buf,*next = NULL; if( NULL == q ) { ERR(("internal error: fatal NULL pointer for q.\n")); return; } DEB_EE(("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi)); #if DEBUG_SPINLOCKS BUG_ON(!spin_is_locked(&dev->slock)); #endif if (!list_empty(&q->queue)) { /* activate next one from queue */ buf = list_entry(q->queue.next,struct saa7146_buf,vb.queue); list_del(&buf->vb.queue); if (!list_empty(&q->queue)) next = list_entry(q->queue.next,struct saa7146_buf, vb.queue); q->curr = buf; DEB_D(("next buffer: buf:%p, prev:%p, next:%p\n", buf, q->queue.prev,q->queue.next)); buf->activate(dev,buf,next); } else { DEB_D(("no next buffer. stopping.\n")); if( 0 != vbi ) { /* turn off video-dma3 */ saa7146_write(dev,MC1, MASK_20); } else { /* nothing to do -- just prevent next video-dma1 transfer by lowering the protection address */ // fixme: fix this for vflip != 0 saa7146_write(dev, PROT_ADDR1, 0); /* write the address of the rps-program */ saa7146_write(dev, RPS_ADDR0, virt_to_bus(&dev->rps0[ 0])); /* turn on rps */ saa7146_write(dev, MC1, (MASK_12 | MASK_28)); } del_timer(&q->timeout); } } void saa7146_buffer_timeout(unsigned long data) { struct saa7146_dmaqueue *q = (struct saa7146_dmaqueue*)data; struct saa7146_dev *dev = q->dev; unsigned long flags; DEB_EE(("dev:%p, dmaq:%p\n", dev, q)); spin_lock_irqsave(&dev->slock,flags); if (q->curr) { DEB_D(("timeout on %p\n", q->curr)); saa7146_buffer_finish(dev,q,STATE_ERROR); } /* we don't restart the transfer here like other drivers do. when a streaming capture is disabled, the timeout function will be called for the current buffer. if we activate the next buffer now, we mess up our capture logic. if a timeout occurs on another buffer, then something is seriously broken before, so no need to buffer the next capture IMHO... */ /* saa7146_buffer_next(dev,q); */ spin_unlock_irqrestore(&dev->slock,flags); } /*********************************************************************************/ /* extension handling functions */ static struct video_device device_template = { .hardware = VID_HARDWARE_SAA7146, .fops = &video_fops, .minor = -1, }; int saa7146_register_extension(struct saa7146_extension* ext) { struct saa7146_dev *dev = NULL; struct list_head *list = NULL;; DEB_EE(("ext:%p\n",ext)); down(&devices_lock); list_for_each(list,&devices) { int i = 0; int found = 0; dev = list_entry(list, struct saa7146_dev, item); /* check if already handled by extension */ if( 0 != dev->ext ) { continue; } dev->vbi_dev.minor = -1; dev->video_dev.minor = -1; DEB_S(("Trying device %p...\n",dev)); /* first check the subvendor and subdevice ids */ for(i = 0;;i++) { if( 0xffff == ext->devices[i].subvendor && 0xffff == ext->devices[i].subdevice ) { break; } if( ext->devices[i].subvendor == dev->pci->subsystem_vendor && ext->devices[i].subdevice == dev->pci->subsystem_device ) { found = 1; break; } } if( 0 == found ) { DEB_S(("extension %p does not handle this device. skipping.\n",ext)); continue; } if( 0 != ext->preinit(dev) ) { DEB_S(("ext->preinit() failed for %p. skipping.\n",dev)); continue; } if( 0 != ext->use_kernel_i2c ) { /* register new i2c-bus */ if(i2c_add_adapter(dev->i2c_adapter) < 0) { DEB_S(("cannot register i2c-device. skipping.\n")); continue; } } if( 0 != ext->probe(dev, dev->pci->subsystem_vendor, dev->pci->subsystem_device) ) { DEB_D(("ext->probe() failed for %p. skipping device.\n",dev)); if( 0 != ext->use_kernel_i2c ) i2c_del_adapter(dev->i2c_adapter); continue; } dev->ext = ext; if( 0 != ext->attach(dev) ) { DEB_D(("ext->attach() failed for %p. skipping device.\n",dev)); if( 0 != ext->use_kernel_i2c ) i2c_del_adapter(dev->i2c_adapter); dev->ext = NULL; continue; } /* v4l2 initialization stuff */ dev->video_dev = device_template; strncpy(dev->video_dev.name, ext->name, 32); dev->video_dev.priv = dev; // fixme: -1 should be an insmod parameter *for the extension* (like "video_nr"); if (video_register_device(&dev->video_dev,VFL_TYPE_GRABBER,-1) < 0) { ERR(("cannot register capture v4l2 device. skipping.\n")); /* in this case the extension has probably already "done something", but the v4l2-device could not be registered. this is not as bad as it looks - you cannot access the device. we simply call the 'release' function. */ ext->detach(dev); if( 0 != ext->use_kernel_i2c ) i2c_del_adapter(dev->i2c_adapter); dev->ext = NULL; continue; } INFO(("%s: registered device video%d [v4l2],%d\n", dev->name,dev->video_dev.minor & 0x1f,dev->vbi_dev.minor & 0x1f)); /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ if( 0 != BOARD_CAN_DO_VBI(dev)) { dev->vbi_dev = device_template; strncpy(dev->vbi_dev.name, ext->name, 32); dev->vbi_dev.priv = dev; if (video_register_device(&dev->vbi_dev,VFL_TYPE_VBI,-1) < 0) { ERR(("cannot register vbi v4l2 device. skipping.\n")); } INFO(("%s: registered device vbi%d [v4l2]\n", dev->name,dev->vbi_dev.minor & 0x1f)); } } up(&devices_lock); return 0; } int saa7146_unregister_extension(struct saa7146_extension* ext) { struct saa7146_dev *dev = NULL; struct list_head *list = NULL;; DEB_EE(("ext:%p\n",ext)); down(&devices_lock); list_for_each(list,&devices) { dev = list_entry(list, struct saa7146_dev, item); /* check if handled by this extension */ if( ext != dev->ext ) { continue; } if( 0 != ext->detach(dev) ) { DEB_D(("ext->detach() failed. ignoring.\n")); } if( 0 != ext->use_kernel_i2c ) i2c_del_adapter(dev->i2c_adapter); video_unregister_device(&dev->video_dev); if( 0 != BOARD_CAN_DO_VBI(dev)) { video_unregister_device(&dev->vbi_dev); } dev->ext = NULL; /* make sure to erase the minor number... */ dev->video_dev = device_template; // dump_registers(dev); } up(&devices_lock); return 0; } /*********************************************************************************/ /* configuration-functions */ static int config_a_device(struct pci_dev *pci) { unsigned long adr = 0, len = 0; struct saa7146_dev* dev = (struct saa7146_dev*)kmalloc(sizeof(struct saa7146_dev),GFP_KERNEL); if( NULL == dev) { ERR(("out of memory.\n")); return -ENOMEM; } /* clear out mem for sure */ memset(dev, 0x0, sizeof(struct saa7146_dev)); DEB_EE(("pci:%p\n",pci)); if (pci_enable_device(pci)) { ERR(("pci_enable_device() failed.\n")); kfree(dev); return -EIO; } /* enable bus-mastering */ pci_set_master(pci); dev->pci = pci; /* get chip-revision; this is needed to enable bug-fixes */ if( 0 > pci_read_config_dword(dev->pci, PCI_CLASS_REVISION, &dev->revision)) { ERR(("pci_read_config_dword() failed.\n")); kfree(dev); return -ENODEV; } dev->revision &= 0xf; /* remap the memory from virtual to physical adress */ adr = pci_resource_start(pci,0); len = pci_resource_len(pci,0); if (!request_mem_region(pci_resource_start(pci,0), pci_resource_len(pci,0), "saa7146")) { ERR(("request_mem_region() failed.\n")); kfree(dev); return -ENODEV; } dev->mem=ioremap(adr,len); if ( 0 == dev->mem ) { ERR(("ioremap() failed.\n")); release_mem_region(adr,len); kfree(dev); return -ENODEV; } /* we don't do a master reset here anymore, it screws up some boards that don't have an i2c-eeprom for configuration values */ /* saa7146_write(dev, MC1, MASK_31); */ /* disable alle irqs, clear irq-mask */ saa7146_write(dev, ISR, 0xffffffff); /* shut down all dma transfers */ saa7146_write(dev, MC1, 0x00ff0000); /* clear out any rps-signals pending */ saa7146_write(dev, MC2, 0xf8000000); /* request an interrupt for the saa7146 */ if( 0 != request_irq(dev->pci->irq, interrupt_hw, SA_SHIRQ | SA_INTERRUPT, dev->name, (void *)dev)) { ERR(("request_irq() failed.\n")); iounmap(dev->mem); release_mem_region(adr,len); kfree(dev); return -EINVAL; } /* get memory for various stuff */ dev->rps0 = (u32*)kmalloc(SAA7146_RPS_MEM, GFP_KERNEL); if( NULL == dev->rps0 ) goto kmalloc_error_1; memset(dev->rps0, 0x0, SAA7146_RPS_MEM); dev->rps1 = (u32*)kmalloc(SAA7146_RPS_MEM, GFP_KERNEL); if( NULL == dev->rps1 ) goto kmalloc_error_2; memset(dev->rps1, 0x0, SAA7146_RPS_MEM); dev->clipping = (u32*)kmalloc(SAA7146_CLIPPING_MEM, GFP_KERNEL); if( NULL == dev->clipping ) goto kmalloc_error_3; memset(dev->clipping, 0x0, SAA7146_CLIPPING_MEM); dev->i2c_adapter = kmalloc(sizeof(struct i2c_adapter), GFP_KERNEL); if ( NULL == dev->i2c_adapter ) goto kmalloc_error_4; memset(dev->i2c_adapter, 0x0, sizeof(struct i2c_adapter)); dev->i2c_mem = (u32*)kmalloc(SAA7146_I2C_MEM, GFP_KERNEL); if( NULL == dev->i2c_mem ) goto kmalloc_error_5; memset(dev->i2c_mem, 0x00, SAA7146_I2C_MEM); /* i2c-adapter preparations */ saa7146_i2c_adapter_prepare(dev); /* enable i2c-port pins, video-port-pins */ saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); /* the rest + print status message */ /* create a nice device name */ sprintf(&dev->name[0], "saa7146 (%d)",saa7146_num); INFO(("found saa7146 @ mem 0x%08x (revision %d, irq %d) (0x%04x,0x%04x).\n", (unsigned int)dev->mem, dev->revision,dev->pci->irq,dev->pci->subsystem_vendor,dev->pci->subsystem_device)); dev->ext = NULL; pci_set_drvdata(pci,dev); init_MUTEX(&dev->lock); dev->slock = SPIN_LOCK_UNLOCKED; init_MUTEX(&dev->i2c_lock); INIT_LIST_HEAD(&dev->item); list_add_tail(&dev->item,&devices); saa7146_num++; saa7146_video_uops.init(dev); saa7146_vbi_uops.init(dev); dev->module = THIS_MODULE; return 0; kmalloc_error_5: kfree( dev->i2c_adapter ); kmalloc_error_4: kfree( dev->clipping ); kmalloc_error_3: kfree( dev->rps1 ); kmalloc_error_2: kfree( dev->rps0 ); kmalloc_error_1: ERR(("kmalloc() failed.\n")); iounmap(dev->mem); release_mem_region(adr,len); kfree(dev); return -ENOMEM; } static void unconfig_a_device(struct saa7146_dev* dev) { DEB_EE(("dev:%p\n",dev)); /* shut down all video dma transfers */ saa7146_write(dev, MC1, 0x00ff0000); /* disable all irqs, release irq-routine */ saa7146_write(dev, IER, 0); saa7146_write(dev, ISR, 0xffffffff); free_irq(dev->pci->irq, (void *)dev); /* free kernel memory */ kfree(dev->rps0 ); kfree(dev->rps1 ); kfree(dev->clipping); kfree(dev->i2c_mem); kfree(dev->i2c_adapter); iounmap(dev->mem); release_mem_region(pci_resource_start(dev->pci,0), pci_resource_len(dev->pci,0)); list_del(&dev->item); kfree(dev); saa7146_num--; } #ifdef MODULE int __devinit saa7146_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { if( 0 != config_a_device(pdev)) { return -EINVAL; } return 0; } void __devexit saa7146_remove_one(struct pci_dev *pdev) { struct saa7146_dev* dev = (struct saa7146_dev*)pci_get_drvdata(pdev); unconfig_a_device(dev); } struct pci_device_id saa7146_pci_tbl[] __devinitdata = { { PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, PCI_ANY_ID, PCI_ANY_ID, }, { 0,}, }; MODULE_DEVICE_TABLE(pci, saa7146_pci_tbl); struct pci_driver saa7146_driver = { name: "saa7146", id_table: saa7146_pci_tbl, probe: saa7146_init_one, remove: saa7146_remove_one, }; int __init saa7146_init_module(void) { INIT_LIST_HEAD(&devices); init_MUTEX(&devices_lock); return pci_module_init(&saa7146_driver); } void __exit saa7146_cleanup_module(void) { pci_unregister_driver(&saa7146_driver); } module_init(saa7146_init_module); module_exit(saa7146_cleanup_module); EXPORT_SYMBOL_GPL(saa7146_register_extension); EXPORT_SYMBOL_GPL(saa7146_unregister_extension); /* misc functions used by extension modules */ EXPORT_SYMBOL_GPL(saa7146_set_hps_source_and_sync); EXPORT_SYMBOL_GPL(saa7146_pgtable_alloc); EXPORT_SYMBOL_GPL(saa7146_pgtable_free); EXPORT_SYMBOL_GPL(saa7146_pgtable_build_single); EXPORT_SYMBOL_GPL(saa7146_i2c_transfer); MODULE_AUTHOR("Michael Hunold "); MODULE_DESCRIPTION("video4linux driver for saa7146-based video hardware"); MODULE_LICENSE("GPL"); #endif