/* * $Id: cx88-mpeg.c,v 1.2 2004/07/30 11:14:05 kraxel Exp $ * * Support for the mpeg transport stream transfers * PCI function #2 of the cx2388x. * * (c) 2004 Jelle Foks * (c) 2004 Gerd Knorr * * 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 #include #include #include #include #include "cx88.h" #define PACKET_SIZE 512 // smaller ones seem not to work #define PACKETS_PER_BUFFER 1024 #define BUFFER_SIZE (PACKET_SIZE * PACKETS_PER_BUFFER) /* ------------------------------------------------------------------ */ MODULE_DESCRIPTION("mpeg driver module for cx2388x based TV cards"); MODULE_AUTHOR("Jelle Foks "); MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); MODULE_LICENSE("GPL"); static unsigned int mpegbufs = 8; MODULE_PARM(mpegbufs,"i"); MODULE_PARM_DESC(mpegbufs,"number of mpeg buffers, range 2-32"); static unsigned int mpeg_debug = 0; MODULE_PARM(mpeg_debug,"i"); MODULE_PARM_DESC(mpeg_debug,"enable debug messages [mpeg]"); #define dprintk(level,fmt, arg...) if (mpeg_debug >= level) \ printk(KERN_DEBUG "%s/2: " fmt, dev->core->name , ## arg) static LIST_HEAD(cx8802_devlist); /* ------------------------------------------------------------------ */ int cx8802_start_dma(struct cx8802_dev *dev, struct cx88_dmaqueue *q, struct cx88_buffer *buf) { struct cx88_core *core = dev->core; dprintk(0, "cx8802_start_mpegport_dma %d\n", buf->vb.width); /* setup fifo + format */ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], PACKET_SIZE, buf->risc.dma); //cx_set(VID_CAPTURE_CONTROL,0x0); /* should this??? */ /* enable capture */ /* reset counter */ cx_write(MO_TS_GPCNTRL,0x3); /* general purpose counter for risc program (?) */ q->count = 1; /* enable irqs */ cx_set(MO_PCI_INTMSK, 0x4); /* enable ts_int */ cx_set(MO_TS_INTMSK, 0x1f1101); /* all except the irq2 bit */ /* start dma */ cx_set(MO_DEV_CNTRL2, (1<<5)); /* enable the risc controller */ cx_set(MO_TS_DMACNTRL, 0x11); /* enable TS RISC and FIFO */ return 0; } int cx8802_restart_queue(struct cx8802_dev *dev, struct cx88_dmaqueue *q) { struct cx88_buffer *buf; struct list_head *item; if (list_empty(&q->active)) return 0; buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); dprintk(2,"restart_queue [%p/%d]: restart dma\n", buf, buf->vb.i); cx8802_start_dma(dev, q, buf); list_for_each(item,&q->active) { buf = list_entry(item, struct cx88_buffer, vb.queue); buf->count = q->count++; } mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); return 0; } /* ------------------------------------------------------------------ */ static int mpeg_buf_setup(struct file *file, unsigned int *count, unsigned int *size) { *size = BUFFER_SIZE; if (0 == *count) *count = mpegbufs; if (*count < 2) *count = 2; if (*count > 32) *count = 32; return 0; } static int mpeg_buf_prepare(struct file *file, struct videobuf_buffer *vb, enum v4l2_field field) { struct cx8802_fh *fh = file->private_data; struct cx8802_dev *dev = fh->dev; struct cx88_buffer *buf = (struct cx88_buffer*)vb; int rc; dprintk(1, "%s: %p\n", __FUNCTION__, buf); if (0 != buf->vb.baddr && buf->vb.bsize < BUFFER_SIZE) return -EINVAL; if (STATE_NEEDS_INIT == buf->vb.state) { buf->vb.width = PACKET_SIZE; buf->vb.height = PACKETS_PER_BUFFER; buf->vb.size = BUFFER_SIZE; buf->vb.field = field; if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) goto fail; cx88_risc_buffer(dev->pci, &buf->risc, buf->vb.dma.sglist, 0, UNSET, buf->vb.width, 0, buf->vb.height); } buf->vb.state = STATE_PREPARED; return 0; fail: cx88_free_buffer(dev->pci,buf); return rc; } static void mpeg_buf_queue(struct file *file, struct videobuf_buffer *vb) { struct cx88_buffer *buf = (struct cx88_buffer*)vb; struct cx88_buffer *prev; struct cx8802_fh *fh = file->private_data; struct cx8802_dev *dev = fh->dev; struct cx88_dmaqueue *q = &dev->mpegq; /* add jump to stopper */ buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | 0x10000); buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); if (list_empty(&q->active)) { list_add_tail(&buf->vb.queue,&q->active); cx8802_start_dma(dev, q, buf); buf->vb.state = STATE_ACTIVE; buf->count = q->count++; mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); dprintk(2,"[%p/%d] %s - first active\n", buf, buf->vb.i, __FUNCTION__); } else { prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); list_add_tail(&buf->vb.queue,&q->active); buf->vb.state = STATE_ACTIVE; buf->count = q->count++; prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); dprintk(2,"[%p/%d] %s - append to active\n", buf, buf->vb.i, __FUNCTION__); } } static void mpeg_buf_release(struct file *file, struct videobuf_buffer *vb) { struct cx88_buffer *buf = (struct cx88_buffer*)vb; struct cx8802_fh *fh = file->private_data; struct cx8802_dev *dev = fh->dev; dprintk(1, "%s: %p\n", __FUNCTION__, buf); #if 0 /* FIXME: probably wrong place */ mpegport_api_cmd(fh->dev, IVTV_API_END_CAPTURE, 3, 0, 1, 0, 0x13); #endif cx88_free_buffer(fh->dev->pci, buf); } struct videobuf_queue_ops cx8802_mpeg_qops = { .buf_setup = mpeg_buf_setup, .buf_prepare = mpeg_buf_prepare, .buf_queue = mpeg_buf_queue, .buf_release = mpeg_buf_release, }; /* ------------------------------------------------------------------ */ static int mpeg_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg) { struct cx8802_fh *fh = file->private_data; struct cx8802_dev *dev = fh->dev; if (mpeg_debug > 1) cx88_print_ioctl(dev->core->name,cmd); switch (cmd) { /* --- capture ioctls ---------------------------------------- */ case VIDIOC_ENUM_FMT: { struct v4l2_fmtdesc *f = arg; int index; index = f->index; if (index != 0) return -EINVAL; memset(f,0,sizeof(*f)); f->index = index; strlcpy(f->description, "MPEG TS", sizeof(f->description)); f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; f->pixelformat = V4L2_PIX_FMT_MPEG; return 0; } case VIDIOC_G_FMT: case VIDIOC_S_FMT: case VIDIOC_TRY_FMT: { /* FIXME -- quick'n'dirty for exactly one size ... */ struct v4l2_format *f = arg; memset(f,0,sizeof(*f)); f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; f->fmt.pix.width = 720; f->fmt.pix.height = 576; f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; f->fmt.pix.sizeimage = BUFFER_SIZE; } /* --- streaming capture ------------------------------------- */ case VIDIOC_REQBUFS: return videobuf_reqbufs(file, &fh->mpegq, arg); case VIDIOC_QUERYBUF: return videobuf_querybuf(&fh->mpegq, arg); case VIDIOC_QBUF: return videobuf_qbuf(file, &fh->mpegq, arg); case VIDIOC_DQBUF: return videobuf_dqbuf(file, &fh->mpegq, arg); case VIDIOC_STREAMON: return videobuf_streamon(file, &fh->mpegq); case VIDIOC_STREAMOFF: return videobuf_streamoff(file, &fh->mpegq); default: return -EINVAL; } return 0; } static int mpeg_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { return video_usercopy(inode, file, cmd, arg, mpeg_do_ioctl); } static int mpeg_open(struct inode *inode, struct file *file) { int minor = iminor(inode); struct cx8802_dev *h,*dev = NULL; struct cx8802_fh *fh; struct list_head *list; list_for_each(list,&cx8802_devlist) { h = list_entry(list, struct cx8802_dev, devlist); if (h->mpeg_dev->minor == minor) dev = h; } if (NULL == dev) return -ENODEV; #if 0 /* FIXME */ if (mpegport_initialize_codec(dev) < 0) return -EINVAL; #endif dprintk(1,"open minor=%d\n",minor); /* allocate + initialize per filehandle data */ fh = kmalloc(sizeof(*fh),GFP_KERNEL); if (NULL == fh) return -ENOMEM; memset(fh,0,sizeof(*fh)); file->private_data = fh; fh->dev = dev; videobuf_queue_init(&fh->mpegq, &cx8802_mpeg_qops, dev->pci, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_TOP, sizeof(struct cx88_buffer)); init_MUTEX(&fh->mpegq.lock); return 0; } static int mpeg_release(struct inode *inode, struct file *file) { struct cx8802_fh *fh = file->private_data; #if 0 /* FIXME */ mpegport_api_cmd(fh->dev, IVTV_API_END_CAPTURE, 3, 0, 1, 0, 0x13); #endif /* stop mpeg capture */ if (fh->mpegq.streaming) videobuf_streamoff(file,&fh->mpegq); if (fh->mpegq.reading) videobuf_read_stop(file,&fh->mpegq); file->private_data = NULL; kfree(fh); return 0; } static ssize_t mpeg_read(struct file *file, char *data, size_t count, loff_t *ppos) { struct cx8802_fh *fh = file->private_data; return videobuf_read_stream(file, &fh->mpegq, data, count, ppos, 0); } static unsigned int mpeg_poll(struct file *file, struct poll_table_struct *wait) { struct cx8802_fh *fh = file->private_data; return videobuf_poll_stream(file, &fh->mpegq, wait); } static int mpeg_mmap(struct file *file, struct vm_area_struct * vma) { struct cx8802_fh *fh = file->private_data; return videobuf_mmap_mapper(vma, &fh->mpegq); } static struct file_operations mpeg_fops = { .owner = THIS_MODULE, .open = mpeg_open, .release = mpeg_release, .read = mpeg_read, .poll = mpeg_poll, .mmap = mpeg_mmap, .ioctl = mpeg_ioctl, .llseek = no_llseek, }; static struct video_device cx8802_mpeg_template = { .name = "cx8802", .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_SCALES|VID_TYPE_MPEG_ENCODER, .hardware = 0, .fops = &mpeg_fops, .minor = -1, }; /* ----------------------------------------------------------- */ /* exported stuff */ static void cx8802_timeout(unsigned long data) { struct cx8802_dev *dev = (struct cx8802_dev*)data; struct cx88_core *core = dev->core; struct cx88_dmaqueue *q = &dev->mpegq; struct cx88_buffer *buf; unsigned long flags; dprintk(0, "cx8802_mpegport_timeout\n"); #if 0 /* FIXME */ mpegport_api_cmd(dev, IVTV_API_END_CAPTURE, 3, 0, 1, 0, 0x13); #endif cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); cx_clear(MO_TS_DMACNTRL, 0x11); /* disable TS RISC and FIFO */ spin_lock_irqsave(&dev->slock,flags); while (!list_empty(&q->active)) { buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); list_del(&buf->vb.queue); buf->vb.state = STATE_ERROR; wake_up(&buf->vb.done); printk("%s: [%p/%d] timeout - dma=0x%08lx\n", dev->core->name, buf, buf->vb.i, (unsigned long)buf->risc.dma); } cx8802_restart_queue(dev,q); spin_unlock_irqrestore(&dev->slock,flags); } static void cx8802_mpeg_irq(struct cx8802_dev *dev) { struct cx88_core *core = dev->core; u32 status, mask, count; status = cx_read(MO_TS_INTSTAT); mask = cx_read(MO_TS_INTMSK); if (0 == (status & mask)) return; cx_write(MO_TS_INTSTAT, status); if (mpeg_debug || (status & mask & ~0xff)) cx88_print_irqbits(core->name, "irq mpeg ", cx88_vid_irqs, status, mask); /* risc op code error */ if (status & (1 << 16)) { printk(KERN_WARNING "%s: mpeg risc op code error\n",core->name); cx_clear(MO_TS_DMACNTRL, 0x11); cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); } /* risc1 y */ if (status & 0x01) { spin_lock(&dev->slock); count = cx_read(MO_TS_GPCNT); cx88_wakeup(dev->core, &dev->mpegq, count); spin_unlock(&dev->slock); } /* risc2 y */ if (status & 0x10) { /* I don't know what this does/when it's needed */ spin_lock(&dev->slock); cx8802_restart_queue(dev,&dev->mpegq); spin_unlock(&dev->slock); } } static irqreturn_t cx8802_irq(int irq, void *dev_id, struct pt_regs *regs) { struct cx8802_dev *dev = dev_id; struct cx88_core *core = dev->core; u32 status, mask; int loop, handled = 0; for (loop = 0; loop < 10; loop++) { status = cx_read(MO_PCI_INTSTAT) & (~0x1f | 0x04); mask = cx_read(MO_PCI_INTMSK); if (0 == (status & mask)) goto out; handled = 1; cx_write(MO_PCI_INTSTAT, status); if (status & mask & ~0x1f) cx88_irq(core,status,mask); if (status & 0x04) cx8802_mpeg_irq(dev); }; if (10 == loop) { printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n", core->name); cx_write(MO_PCI_INTMSK,0); } out: return IRQ_RETVAL(handled); } /* ----------------------------------------------------------- */ static void cx8802_unregister_video(struct cx8802_dev *dev) { if (dev->mpeg_dev) { if (-1 != dev->mpeg_dev->minor) video_unregister_device(dev->mpeg_dev); else video_device_release(dev->mpeg_dev); dev->mpeg_dev = NULL; } } static int __devinit cx8802_initdev(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) { struct cx8802_dev *dev; struct cx88_core *core; int err; dev = kmalloc(sizeof(*dev),GFP_KERNEL); if (NULL == dev) return -ENOMEM; memset(dev,0,sizeof(*dev)); /* pci init */ dev->pci = pci_dev; if (pci_enable_device(pci_dev)) { err = -EIO; goto fail_free; } core = cx88_core_get(dev->pci); if (NULL == core) { err = -EINVAL; goto fail_free; } dev->core = core; /* print pci info */ pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); printk(KERN_INFO "%s/2: found at %s, rev: %d, irq: %d, " "latency: %d, mmio: 0x%lx\n", core->name, pci_name(pci_dev), dev->pci_rev, pci_dev->irq, dev->pci_lat,pci_resource_start(pci_dev,0)); pci_set_master(pci_dev); if (!pci_dma_supported(pci_dev,0xffffffff)) { printk("%s/2: Oops: no 32bit PCI DMA ???\n",core->name); err = -EIO; goto fail_core; } /* look what exactly we have ... */ if (cx88_boards[core->board].blackbird) { printk("%s/2: cx23416 based mpeg encoder (blackbird design)\n", core->name); /* todo */ } else if (cx88_boards[core->board].dvb) { printk("%s/2: has DVB support\n", core->name); /* todo */ } else { printk("%s/2: don't what the mpeg port on this card is used for\n" "%s/2: going to ignore it, sorry\n", core->name, core->name); err = -EINVAL; goto fail_core; } /* initialize driver struct */ init_MUTEX(&dev->lock); dev->slock = SPIN_LOCK_UNLOCKED; /* init dma queue */ INIT_LIST_HEAD(&dev->mpegq.active); INIT_LIST_HEAD(&dev->mpegq.queued); dev->mpegq.timeout.function = cx8802_timeout; dev->mpegq.timeout.data = (unsigned long)dev; init_timer(&dev->mpegq.timeout); cx88_risc_stopper(dev->pci,&dev->mpegq.stopper, MO_TS_DMACNTRL,0x11,0x00); #if 0 /* FIXME */ /* initialize hardware */ cx8802_reset(dev); #endif /* get irq */ err = request_irq(pci_dev->irq, cx8802_irq, SA_SHIRQ | SA_INTERRUPT, core->name, dev); if (err < 0) { printk(KERN_ERR "%s: can't get IRQ %d\n", core->name,pci_dev->irq); goto fail_core; } #if 0 /* FIXME */ /* register i2c bus + load i2c helpers */ cx88_card_setup(dev); #endif /* register v4l devices */ dev->mpeg_dev = cx88_vdev_init(core,dev->pci,&cx8802_mpeg_template,"mpeg"); err = video_register_device(dev->mpeg_dev,VFL_TYPE_GRABBER, -1); if (err < 0) { printk(KERN_INFO "%s/2: can't register mpeg device\n", core->name); return err; } printk(KERN_INFO "%s/2: registered device video%d [mpeg]\n", core->name,dev->mpeg_dev->minor & 0x1f); /* everything worked */ list_add_tail(&dev->devlist,&cx8802_devlist); pci_set_drvdata(pci_dev,dev); return 0; fail_core: cx88_core_put(core,dev->pci); fail_free: kfree(dev); return err; } static void __devexit cx8802_finidev(struct pci_dev *pci_dev) { struct cx8802_dev *dev = pci_get_drvdata(pci_dev); #if 0 cx8802_shutdown(dev); #endif pci_disable_device(pci_dev); /* unregister stuff */ free_irq(pci_dev->irq, dev); cx8802_unregister_video(dev); pci_set_drvdata(pci_dev, NULL); /* free memory */ btcx_riscmem_free(dev->pci,&dev->mpegq.stopper); list_del(&dev->devlist); cx88_core_put(dev->core,dev->pci); kfree(dev); } struct pci_device_id cx8802_pci_tbl[] = { { .vendor = 0x14f1, .device = 0x8802, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, },{ /* --- end of list --- */ } }; MODULE_DEVICE_TABLE(pci, cx8802_pci_tbl); static struct pci_driver cx8802_pci_driver = { .name = "cx8802", .id_table = cx8802_pci_tbl, .probe = cx8802_initdev, .remove = cx8802_finidev, #if 0 .suspend = cx8802_suspend, .resume = cx8802_resume, #endif }; static int cx8802_init(void) { printk(KERN_INFO "cx2388x mpeg driver version %d.%d.%d loaded\n", (CX88_VERSION_CODE >> 16) & 0xff, (CX88_VERSION_CODE >> 8) & 0xff, CX88_VERSION_CODE & 0xff); #ifdef SNAPSHOT printk(KERN_INFO "cx2388x: snapshot date %04d-%02d-%02d\n", SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); #endif return pci_module_init(&cx8802_pci_driver); } static void cx8802_fini(void) { pci_unregister_driver(&cx8802_pci_driver); } module_init(cx8802_init); module_exit(cx8802_fini); /* ----------------------------------------------------------- */ /* * Local variables: * c-basic-offset: 8 * End: */