diff options
Diffstat (limited to 'linux/drivers/media/common/saa7146_fops.c')
-rw-r--r-- | linux/drivers/media/common/saa7146_fops.c | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/linux/drivers/media/common/saa7146_fops.c b/linux/drivers/media/common/saa7146_fops.c new file mode 100644 index 000000000..d8202c65f --- /dev/null +++ b/linux/drivers/media/common/saa7146_fops.c @@ -0,0 +1,426 @@ +#include "saa7146.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME saa7146 +#endif + +#define BOARD_CAN_DO_VBI(dev) (dev->revision != 0 && dev->vbi_minor != -1) + +/********************************************************************************/ +/* common dma 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; +} + + +/********************************************************************************/ +/* 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); +} + +/********************************************************************************/ +/* 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)); + + if (down_interruptible(&saa7146_devices_lock)) + return -ERESTARTSYS; + + list_for_each(list,&saa7146_devices) { + h = list_entry(list, struct saa7146_dev, item); + DEB_D(("trying: %p @ major %d,%d\n",h,h->video_minor,h->vbi_minor)); + if (h->video_minor == minor) { + dev = h; + } + if (h->vbi_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; + + /* FIXME: what's this? */ + 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(&saa7146_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)); + + if (down_interruptible(&saa7146_devices_lock)) + return -ERESTARTSYS; + + 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(&saa7146_devices_lock); + + return 0; +} + +int saa7146_video_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg); +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, saa7146_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, +}; + +static struct video_device device_template = +{ + .hardware = VID_HARDWARE_SAA7146, + .fops = &video_fops, + .minor = -1, +}; + +int saa7146_register_device(struct video_device *vid, struct saa7146_dev* dev, char *name, int type) +{ + *vid = device_template; + strncpy(vid->name, name, 32); + vid->priv = dev; + + // fixme: -1 should be an insmod parameter *for the extension* (like "video_nr"); + if (video_register_device(vid,type,-1) < 0) { + ERR(("cannot register vbi v4l2 device. skipping.\n")); + return -1; + } + + if( VFL_TYPE_GRABBER == type ) { + dev->video_minor = vid->minor; + INFO(("%s: registered device video%d [v4l2]\n", dev->name,vid->minor & 0x1f)); + } else { + dev->vbi_minor = vid->minor; + INFO(("%s: registered device vbi%d [v4l2]\n", dev->name,vid->minor & 0x1f)); + } + return 0; +} + +int saa7146_unregister_device(struct video_device *vid, struct saa7146_dev* dev) +{ + if( VFL_TYPE_GRABBER == vid->type ) { + dev->video_minor = -1; + dev->video_irq_done = saa7146_video_uops.irq_done; + } else { + dev->vbi_minor = -1; + dev->vbi_irq_done = saa7146_vbi_uops.irq_done; + } + video_unregister_device(vid); + return 0; +} + +static +int __init saa7146_vv_init_module(void) +{ + return 0; +} + + +static +void __exit saa7146_vv_cleanup_module(void) +{ +} + +module_init(saa7146_vv_init_module); +module_exit(saa7146_vv_cleanup_module); + +EXPORT_SYMBOL_GPL(saa7146_set_hps_source_and_sync); +EXPORT_SYMBOL_GPL(saa7146_register_device); +EXPORT_SYMBOL_GPL(saa7146_unregister_device); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware"); +MODULE_LICENSE("GPL"); + + |