/* * $Id: cx88-mpeg.c,v 1.4 2004/07/30 15:26:01 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 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) 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); #if 0 /* config stuff from DVB */ /* Setup TS portion of chip */ cx_write(TS_GEN_CNTRL, 0x0c); /* write TS length to chip */ cx_write(MO_TS_LNGTH, buf->bpl); #endif #if 0 /* config stuff for from blackbird ... */ cx_write(MO_PINMUX_IO, 0x88); /* enable MPEG parallel IO */ cx_write(TS_F2_CMD_STAT_MM, 0x2900106); /* F2_CMD_STAT_MM defaults + master + memory space */ cx_write(TS_GEN_CNTRL, 0x46); /* punctured clock TS & posedge driven & software reset */ cx_write(MO_TS_LNGTH, MD_TS_LNGHT_VAL); udelay(100); cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */ //cx_write(TS_HW_SOP_CNTRL, 0x2F0BC0); /* mpeg start byte ts: 0x2F0BC0 ? */ cx_write(TS_VALERR_CNTRL, 0x2000); cx_write(TS_GEN_CNTRL, 0x06); /* punctured clock TS & posedge driven */ udelay(100); #endif /* reset counter */ cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET); q->count = 1; /* enable irqs */ cx_set(MO_PCI_INTMSK, 0x00fc04); cx_set(MO_TS_INTMSK, 0x1f0011); /* start dma */ cx_set(MO_DEV_CNTRL2, (1<<5)); cx_set(MO_TS_DMACNTRL, 0x11); 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 | RISC_CNT_INC); 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, }; /* ----------------------------------------------------------- */ /* 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 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: register v4l device, init cx23416 encoder */ } else if (cx88_boards[core->board].dvb) { printk("%s/2: has DVB support\n", core->name); /* todo: register dvb device */ } 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 /* 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); 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: */