forked from Minki/linux
363c35fc44
A detailed description from the original patches 2 years ago: "The WinTV-HVR3000 has a single transport bus which is shared between a DVB-T and DVB-S modulator. These patches build on the bus acquisition cx88 work from a few weeks ago to add support for this. So to applications the HVR3000 looks like this: /dev/dvb/adapter0/fe0 (cx24123 DVB-S demod) /dev/dvb/adapter0/fe1 (cx22702 DVB-T demod) Additional boards continue as before, eg: /dev/dvb/adapter1/fe0 (lgdt3302 ATSC demod) The basic change is removing the single instance of the videobuf_dvb in cx8802_dev and saa7134_dev(?) and replacing it with a list and some supporting functions. *NOTE* This branch was taken before v4l-dvb was closed for 2.6.19 so two or three current cx88 patches appear to be reversed by this tree, this will be cleaned up in the near future. The patches missing change the mutex handing to core->lock, fix an enumeration problem." It should be recognised that a number of people have been maintaining this patchset. Significant levels of Kudos to everyone one involved, including but not limited to: Darron Broad Fabio M. Di Nitto Carlo Scarfoglio Hans Werner Without the work of these people, and countless others, my two year old patches would of died on the Mercurial linuxtv.org vine a long time ago. TODO: Revise these patches a little further so that the need for demux1 and dvr0 is optional, not mandatory on the HVR3000. HISTORY (darron): This is the last update to MFE prepared by Hans which is based upon the `scratchpad' diff created by Carlo. All MFE work prior to that point must be attributed to Fabio who ported and maintained Steve's original patch up to that time. Signed-off-by: Steven Toth <stoth@linuxtv.org> Signed-off-by: Darron Broad <darron@kewl.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
923 lines
24 KiB
C
923 lines
24 KiB
C
/*
|
|
*
|
|
* Support for the mpeg transport stream transfers
|
|
* PCI function #2 of the cx2388x.
|
|
*
|
|
* (c) 2004 Jelle Foks <jelle@foks.8m.com>
|
|
* (c) 2004 Chris Pascoe <c.pascoe@itee.uq.edu.au>
|
|
* (c) 2004 Gerd Knorr <kraxel@bytesex.org>
|
|
*
|
|
* 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 <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/interrupt.h>
|
|
#include <asm/delay.h>
|
|
|
|
#include "cx88.h"
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
MODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards");
|
|
MODULE_AUTHOR("Jelle Foks <jelle@foks.8m.com>");
|
|
MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
|
|
MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static unsigned int debug;
|
|
module_param(debug,int,0644);
|
|
MODULE_PARM_DESC(debug,"enable debug messages [mpeg]");
|
|
|
|
#define dprintk(level,fmt, arg...) if (debug >= level) \
|
|
printk(KERN_DEBUG "%s/2-mpeg: " fmt, dev->core->name, ## arg)
|
|
|
|
#define mpeg_dbg(level,fmt, arg...) if (debug >= level) \
|
|
printk(KERN_DEBUG "%s/2-mpeg: " fmt, core->name, ## arg)
|
|
|
|
#if defined(CONFIG_MODULES) && defined(MODULE)
|
|
static void request_module_async(struct work_struct *work)
|
|
{
|
|
struct cx8802_dev *dev=container_of(work, struct cx8802_dev, request_module_wk);
|
|
|
|
if (dev->core->board.mpeg & CX88_MPEG_DVB)
|
|
request_module("cx88-dvb");
|
|
if (dev->core->board.mpeg & CX88_MPEG_BLACKBIRD)
|
|
request_module("cx88-blackbird");
|
|
}
|
|
|
|
static void request_modules(struct cx8802_dev *dev)
|
|
{
|
|
INIT_WORK(&dev->request_module_wk, request_module_async);
|
|
schedule_work(&dev->request_module_wk);
|
|
}
|
|
#else
|
|
#define request_modules(dev)
|
|
#endif /* CONFIG_MODULES */
|
|
|
|
|
|
static LIST_HEAD(cx8802_devlist);
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int cx8802_start_dma(struct cx8802_dev *dev,
|
|
struct cx88_dmaqueue *q,
|
|
struct cx88_buffer *buf)
|
|
{
|
|
struct cx88_core *core = dev->core;
|
|
|
|
dprintk(1, "cx8802_start_dma w: %d, h: %d, f: %d\n",
|
|
buf->vb.width, buf->vb.height, buf->vb.field);
|
|
|
|
/* setup fifo + format */
|
|
cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28],
|
|
dev->ts_packet_size, buf->risc.dma);
|
|
|
|
/* write TS length to chip */
|
|
cx_write(MO_TS_LNGTH, buf->vb.width);
|
|
|
|
/* FIXME: this needs a review.
|
|
* also: move to cx88-blackbird + cx88-dvb source files? */
|
|
|
|
dprintk( 1, "core->active_type_id = 0x%08x\n", core->active_type_id);
|
|
|
|
if ( (core->active_type_id == CX88_MPEG_DVB) &&
|
|
(core->board.mpeg & CX88_MPEG_DVB) ) {
|
|
|
|
dprintk( 1, "cx8802_start_dma doing .dvb\n");
|
|
/* negedge driven & software reset */
|
|
cx_write(TS_GEN_CNTRL, 0x0040 | dev->ts_gen_cntrl);
|
|
udelay(100);
|
|
cx_write(MO_PINMUX_IO, 0x00);
|
|
cx_write(TS_HW_SOP_CNTRL, 0x47<<16|188<<4|0x01);
|
|
switch (core->boardnr) {
|
|
case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q:
|
|
case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T:
|
|
case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD:
|
|
case CX88_BOARD_PCHDTV_HD5500:
|
|
cx_write(TS_SOP_STAT, 1<<13);
|
|
break;
|
|
case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1:
|
|
case CX88_BOARD_HAUPPAUGE_NOVASE2_S1:
|
|
cx_write(MO_PINMUX_IO, 0x88); /* Enable MPEG parallel IO and video signal pins */
|
|
udelay(100);
|
|
break;
|
|
case CX88_BOARD_HAUPPAUGE_HVR1300:
|
|
break;
|
|
case CX88_BOARD_PINNACLE_PCTV_HD_800i:
|
|
/* Enable MPEG parallel IO and video signal pins */
|
|
cx_write(MO_PINMUX_IO, 0x88);
|
|
cx_write(TS_HW_SOP_CNTRL, (0x47 << 16) | (188 << 4));
|
|
dev->ts_gen_cntrl = 5;
|
|
cx_write(TS_SOP_STAT, 0);
|
|
cx_write(TS_VALERR_CNTRL, 0);
|
|
udelay(100);
|
|
break;
|
|
default:
|
|
cx_write(TS_SOP_STAT, 0x00);
|
|
break;
|
|
}
|
|
cx_write(TS_GEN_CNTRL, dev->ts_gen_cntrl);
|
|
udelay(100);
|
|
} else if ( (core->active_type_id == CX88_MPEG_BLACKBIRD) &&
|
|
(core->board.mpeg & CX88_MPEG_BLACKBIRD) ) {
|
|
dprintk( 1, "cx8802_start_dma doing .blackbird\n");
|
|
cx_write(MO_PINMUX_IO, 0x88); /* enable MPEG parallel IO */
|
|
|
|
cx_write(TS_GEN_CNTRL, 0x46); /* punctured clock TS & posedge driven & software reset */
|
|
udelay(100);
|
|
|
|
cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */
|
|
cx_write(TS_VALERR_CNTRL, 0x2000);
|
|
|
|
cx_write(TS_GEN_CNTRL, 0x06); /* punctured clock TS & posedge driven */
|
|
udelay(100);
|
|
} else {
|
|
printk( "%s() Failed. Unsupported value in .mpeg (0x%08x)\n", __func__,
|
|
core->board.mpeg );
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* reset counter */
|
|
cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET);
|
|
q->count = 1;
|
|
|
|
/* enable irqs */
|
|
dprintk( 1, "setting the interrupt mask\n" );
|
|
cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_TSINT);
|
|
cx_set(MO_TS_INTMSK, 0x1f0011);
|
|
|
|
/* start dma */
|
|
cx_set(MO_DEV_CNTRL2, (1<<5));
|
|
cx_set(MO_TS_DMACNTRL, 0x11);
|
|
return 0;
|
|
}
|
|
|
|
static int cx8802_stop_dma(struct cx8802_dev *dev)
|
|
{
|
|
struct cx88_core *core = dev->core;
|
|
dprintk( 1, "cx8802_stop_dma\n" );
|
|
|
|
/* stop dma */
|
|
cx_clear(MO_TS_DMACNTRL, 0x11);
|
|
|
|
/* disable irqs */
|
|
cx_clear(MO_PCI_INTMSK, PCI_INT_TSINT);
|
|
cx_clear(MO_TS_INTMSK, 0x1f0011);
|
|
|
|
/* Reset the controller */
|
|
cx_write(TS_GEN_CNTRL, 0xcd);
|
|
return 0;
|
|
}
|
|
|
|
static int cx8802_restart_queue(struct cx8802_dev *dev,
|
|
struct cx88_dmaqueue *q)
|
|
{
|
|
struct cx88_buffer *buf;
|
|
|
|
dprintk( 1, "cx8802_restart_queue\n" );
|
|
if (list_empty(&q->active))
|
|
{
|
|
struct cx88_buffer *prev;
|
|
prev = NULL;
|
|
|
|
dprintk(1, "cx8802_restart_queue: queue is empty\n" );
|
|
|
|
for (;;) {
|
|
if (list_empty(&q->queued))
|
|
return 0;
|
|
buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue);
|
|
if (NULL == prev) {
|
|
list_del(&buf->vb.queue);
|
|
list_add_tail(&buf->vb.queue,&q->active);
|
|
cx8802_start_dma(dev, q, buf);
|
|
buf->vb.state = VIDEOBUF_ACTIVE;
|
|
buf->count = q->count++;
|
|
mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT);
|
|
dprintk(1,"[%p/%d] restart_queue - first active\n",
|
|
buf,buf->vb.i);
|
|
|
|
} else if (prev->vb.width == buf->vb.width &&
|
|
prev->vb.height == buf->vb.height &&
|
|
prev->fmt == buf->fmt) {
|
|
list_del(&buf->vb.queue);
|
|
list_add_tail(&buf->vb.queue,&q->active);
|
|
buf->vb.state = VIDEOBUF_ACTIVE;
|
|
buf->count = q->count++;
|
|
prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
|
|
dprintk(1,"[%p/%d] restart_queue - move to active\n",
|
|
buf,buf->vb.i);
|
|
} else {
|
|
return 0;
|
|
}
|
|
prev = buf;
|
|
}
|
|
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_entry(buf, &q->active, vb.queue)
|
|
buf->count = q->count++;
|
|
mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT);
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int cx8802_buf_prepare(struct videobuf_queue *q, struct cx8802_dev *dev,
|
|
struct cx88_buffer *buf, enum v4l2_field field)
|
|
{
|
|
int size = dev->ts_packet_size * dev->ts_packet_count;
|
|
struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
|
|
int rc;
|
|
|
|
dprintk(1, "%s: %p\n", __func__, buf);
|
|
if (0 != buf->vb.baddr && buf->vb.bsize < size)
|
|
return -EINVAL;
|
|
|
|
if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
|
|
buf->vb.width = dev->ts_packet_size;
|
|
buf->vb.height = dev->ts_packet_count;
|
|
buf->vb.size = size;
|
|
buf->vb.field = field /*V4L2_FIELD_TOP*/;
|
|
|
|
if (0 != (rc = videobuf_iolock(q,&buf->vb,NULL)))
|
|
goto fail;
|
|
cx88_risc_databuffer(dev->pci, &buf->risc,
|
|
dma->sglist,
|
|
buf->vb.width, buf->vb.height, 0);
|
|
}
|
|
buf->vb.state = VIDEOBUF_PREPARED;
|
|
return 0;
|
|
|
|
fail:
|
|
cx88_free_buffer(q,buf);
|
|
return rc;
|
|
}
|
|
|
|
void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf)
|
|
{
|
|
struct cx88_buffer *prev;
|
|
struct cx88_dmaqueue *cx88q = &dev->mpegq;
|
|
|
|
dprintk( 1, "cx8802_buf_queue\n" );
|
|
/* 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(cx88q->stopper.dma);
|
|
|
|
if (list_empty(&cx88q->active)) {
|
|
dprintk( 1, "queue is empty - first active\n" );
|
|
list_add_tail(&buf->vb.queue,&cx88q->active);
|
|
cx8802_start_dma(dev, cx88q, buf);
|
|
buf->vb.state = VIDEOBUF_ACTIVE;
|
|
buf->count = cx88q->count++;
|
|
mod_timer(&cx88q->timeout, jiffies+BUFFER_TIMEOUT);
|
|
dprintk(1,"[%p/%d] %s - first active\n",
|
|
buf, buf->vb.i, __func__);
|
|
|
|
} else {
|
|
dprintk( 1, "queue is not empty - append to active\n" );
|
|
prev = list_entry(cx88q->active.prev, struct cx88_buffer, vb.queue);
|
|
list_add_tail(&buf->vb.queue,&cx88q->active);
|
|
buf->vb.state = VIDEOBUF_ACTIVE;
|
|
buf->count = cx88q->count++;
|
|
prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
|
|
dprintk( 1, "[%p/%d] %s - append to active\n",
|
|
buf, buf->vb.i, __func__);
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------- */
|
|
|
|
static void do_cancel_buffers(struct cx8802_dev *dev, char *reason, int restart)
|
|
{
|
|
struct cx88_dmaqueue *q = &dev->mpegq;
|
|
struct cx88_buffer *buf;
|
|
unsigned long flags;
|
|
|
|
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 = VIDEOBUF_ERROR;
|
|
wake_up(&buf->vb.done);
|
|
dprintk(1,"[%p/%d] %s - dma=0x%08lx\n",
|
|
buf, buf->vb.i, reason, (unsigned long)buf->risc.dma);
|
|
}
|
|
if (restart)
|
|
{
|
|
dprintk(1, "restarting queue\n" );
|
|
cx8802_restart_queue(dev,q);
|
|
}
|
|
spin_unlock_irqrestore(&dev->slock,flags);
|
|
}
|
|
|
|
void cx8802_cancel_buffers(struct cx8802_dev *dev)
|
|
{
|
|
struct cx88_dmaqueue *q = &dev->mpegq;
|
|
|
|
dprintk( 1, "cx8802_cancel_buffers" );
|
|
del_timer_sync(&q->timeout);
|
|
cx8802_stop_dma(dev);
|
|
do_cancel_buffers(dev,"cancel",0);
|
|
}
|
|
|
|
static void cx8802_timeout(unsigned long data)
|
|
{
|
|
struct cx8802_dev *dev = (struct cx8802_dev*)data;
|
|
|
|
dprintk(1, "%s\n",__func__);
|
|
|
|
if (debug)
|
|
cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]);
|
|
cx8802_stop_dma(dev);
|
|
do_cancel_buffers(dev,"timeout",1);
|
|
}
|
|
|
|
static char *cx88_mpeg_irqs[32] = {
|
|
"ts_risci1", NULL, NULL, NULL,
|
|
"ts_risci2", NULL, NULL, NULL,
|
|
"ts_oflow", NULL, NULL, NULL,
|
|
"ts_sync", NULL, NULL, NULL,
|
|
"opc_err", "par_err", "rip_err", "pci_abort",
|
|
"ts_err?",
|
|
};
|
|
|
|
static void cx8802_mpeg_irq(struct cx8802_dev *dev)
|
|
{
|
|
struct cx88_core *core = dev->core;
|
|
u32 status, mask, count;
|
|
|
|
dprintk( 1, "cx8802_mpeg_irq\n" );
|
|
status = cx_read(MO_TS_INTSTAT);
|
|
mask = cx_read(MO_TS_INTMSK);
|
|
if (0 == (status & mask))
|
|
return;
|
|
|
|
cx_write(MO_TS_INTSTAT, status);
|
|
|
|
if (debug || (status & mask & ~0xff))
|
|
cx88_print_irqbits(core->name, "irq mpeg ",
|
|
cx88_mpeg_irqs, ARRAY_SIZE(cx88_mpeg_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) {
|
|
dprintk( 1, "wake up\n" );
|
|
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) {
|
|
spin_lock(&dev->slock);
|
|
cx8802_restart_queue(dev,&dev->mpegq);
|
|
spin_unlock(&dev->slock);
|
|
}
|
|
|
|
/* other general errors */
|
|
if (status & 0x1f0100) {
|
|
dprintk( 0, "general errors: 0x%08x\n", status & 0x1f0100 );
|
|
spin_lock(&dev->slock);
|
|
cx8802_stop_dma(dev);
|
|
cx8802_restart_queue(dev,&dev->mpegq);
|
|
spin_unlock(&dev->slock);
|
|
}
|
|
}
|
|
|
|
#define MAX_IRQ_LOOP 10
|
|
|
|
static irqreturn_t cx8802_irq(int irq, void *dev_id)
|
|
{
|
|
struct cx8802_dev *dev = dev_id;
|
|
struct cx88_core *core = dev->core;
|
|
u32 status;
|
|
int loop, handled = 0;
|
|
|
|
for (loop = 0; loop < MAX_IRQ_LOOP; loop++) {
|
|
status = cx_read(MO_PCI_INTSTAT) &
|
|
(core->pci_irqmask | PCI_INT_TSINT);
|
|
if (0 == status)
|
|
goto out;
|
|
dprintk( 1, "cx8802_irq\n" );
|
|
dprintk( 1, " loop: %d/%d\n", loop, MAX_IRQ_LOOP );
|
|
dprintk( 1, " status: %d\n", status );
|
|
handled = 1;
|
|
cx_write(MO_PCI_INTSTAT, status);
|
|
|
|
if (status & core->pci_irqmask)
|
|
cx88_core_irq(core,status);
|
|
if (status & PCI_INT_TSINT)
|
|
cx8802_mpeg_irq(dev);
|
|
};
|
|
if (MAX_IRQ_LOOP == loop) {
|
|
dprintk( 0, "clearing mask\n" );
|
|
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 cx8802_init_common(struct cx8802_dev *dev)
|
|
{
|
|
struct cx88_core *core = dev->core;
|
|
int err;
|
|
|
|
/* pci init */
|
|
if (pci_enable_device(dev->pci))
|
|
return -EIO;
|
|
pci_set_master(dev->pci);
|
|
if (!pci_dma_supported(dev->pci,DMA_32BIT_MASK)) {
|
|
printk("%s/2: Oops: no 32bit PCI DMA ???\n",dev->core->name);
|
|
return -EIO;
|
|
}
|
|
|
|
pci_read_config_byte(dev->pci, PCI_CLASS_REVISION, &dev->pci_rev);
|
|
pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER, &dev->pci_lat);
|
|
printk(KERN_INFO "%s/2: found at %s, rev: %d, irq: %d, "
|
|
"latency: %d, mmio: 0x%llx\n", dev->core->name,
|
|
pci_name(dev->pci), dev->pci_rev, dev->pci->irq,
|
|
dev->pci_lat,(unsigned long long)pci_resource_start(dev->pci,0));
|
|
|
|
/* initialize driver struct */
|
|
spin_lock_init(&dev->slock);
|
|
|
|
/* 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);
|
|
|
|
/* get irq */
|
|
err = request_irq(dev->pci->irq, cx8802_irq,
|
|
IRQF_SHARED | IRQF_DISABLED, dev->core->name, dev);
|
|
if (err < 0) {
|
|
printk(KERN_ERR "%s: can't get IRQ %d\n",
|
|
dev->core->name, dev->pci->irq);
|
|
return err;
|
|
}
|
|
cx_set(MO_PCI_INTMSK, core->pci_irqmask);
|
|
|
|
/* everything worked */
|
|
pci_set_drvdata(dev->pci,dev);
|
|
return 0;
|
|
}
|
|
|
|
static void cx8802_fini_common(struct cx8802_dev *dev)
|
|
{
|
|
dprintk( 2, "cx8802_fini_common\n" );
|
|
cx8802_stop_dma(dev);
|
|
pci_disable_device(dev->pci);
|
|
|
|
/* unregister stuff */
|
|
free_irq(dev->pci->irq, dev);
|
|
pci_set_drvdata(dev->pci, NULL);
|
|
|
|
/* free memory */
|
|
btcx_riscmem_free(dev->pci,&dev->mpegq.stopper);
|
|
}
|
|
|
|
/* ----------------------------------------------------------- */
|
|
|
|
static int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state)
|
|
{
|
|
struct cx8802_dev *dev = pci_get_drvdata(pci_dev);
|
|
struct cx88_core *core = dev->core;
|
|
|
|
/* stop mpeg dma */
|
|
spin_lock(&dev->slock);
|
|
if (!list_empty(&dev->mpegq.active)) {
|
|
dprintk( 2, "suspend\n" );
|
|
printk("%s: suspend mpeg\n", core->name);
|
|
cx8802_stop_dma(dev);
|
|
del_timer(&dev->mpegq.timeout);
|
|
}
|
|
spin_unlock(&dev->slock);
|
|
|
|
/* FIXME -- shutdown device */
|
|
cx88_shutdown(dev->core);
|
|
|
|
pci_save_state(pci_dev);
|
|
if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) {
|
|
pci_disable_device(pci_dev);
|
|
dev->state.disabled = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cx8802_resume_common(struct pci_dev *pci_dev)
|
|
{
|
|
struct cx8802_dev *dev = pci_get_drvdata(pci_dev);
|
|
struct cx88_core *core = dev->core;
|
|
int err;
|
|
|
|
if (dev->state.disabled) {
|
|
err=pci_enable_device(pci_dev);
|
|
if (err) {
|
|
printk(KERN_ERR "%s: can't enable device\n",
|
|
dev->core->name);
|
|
return err;
|
|
}
|
|
dev->state.disabled = 0;
|
|
}
|
|
err=pci_set_power_state(pci_dev, PCI_D0);
|
|
if (err) {
|
|
printk(KERN_ERR "%s: can't enable device\n",
|
|
dev->core->name);
|
|
pci_disable_device(pci_dev);
|
|
dev->state.disabled = 1;
|
|
|
|
return err;
|
|
}
|
|
pci_restore_state(pci_dev);
|
|
|
|
/* FIXME: re-initialize hardware */
|
|
cx88_reset(dev->core);
|
|
|
|
/* restart video+vbi capture */
|
|
spin_lock(&dev->slock);
|
|
if (!list_empty(&dev->mpegq.active)) {
|
|
printk("%s: resume mpeg\n", core->name);
|
|
cx8802_restart_queue(dev,&dev->mpegq);
|
|
}
|
|
spin_unlock(&dev->slock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_VIDEO_CX88_BLACKBIRD) || \
|
|
defined(CONFIG_VIDEO_CX88_BLACKBIRD_MODULE)
|
|
struct cx8802_dev * cx8802_get_device(struct inode *inode)
|
|
{
|
|
int minor = iminor(inode);
|
|
struct cx8802_dev *dev;
|
|
|
|
list_for_each_entry(dev, &cx8802_devlist, devlist)
|
|
if (dev->mpeg_dev && dev->mpeg_dev->minor == minor)
|
|
return dev;
|
|
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(cx8802_get_device);
|
|
#endif
|
|
|
|
struct cx8802_driver * cx8802_get_driver(struct cx8802_dev *dev, enum cx88_board_type btype)
|
|
{
|
|
struct cx8802_driver *d;
|
|
|
|
list_for_each_entry(d, &dev->drvlist, drvlist)
|
|
if (d->type_id == btype)
|
|
return d;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Driver asked for hardware access. */
|
|
static int cx8802_request_acquire(struct cx8802_driver *drv)
|
|
{
|
|
struct cx88_core *core = drv->core;
|
|
|
|
/* Fail a request for hardware if the device is busy. */
|
|
if (core->active_type_id != CX88_BOARD_NONE &&
|
|
core->active_type_id != drv->type_id)
|
|
return -EBUSY;
|
|
|
|
core->input = CX88_VMUX_DVB;
|
|
|
|
if (drv->advise_acquire)
|
|
{
|
|
mutex_lock(&drv->core->lock);
|
|
core->active_ref++;
|
|
if (core->active_type_id == CX88_BOARD_NONE) {
|
|
core->active_type_id = drv->type_id;
|
|
drv->advise_acquire(drv);
|
|
}
|
|
mutex_unlock(&drv->core->lock);
|
|
|
|
mpeg_dbg(1,"%s() Post acquire GPIO=%x\n", __func__, cx_read(MO_GP0_IO));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Driver asked to release hardware. */
|
|
static int cx8802_request_release(struct cx8802_driver *drv)
|
|
{
|
|
struct cx88_core *core = drv->core;
|
|
|
|
mutex_lock(&drv->core->lock);
|
|
if (drv->advise_release && --core->active_ref == 0)
|
|
{
|
|
drv->advise_release(drv);
|
|
core->active_type_id = CX88_BOARD_NONE;
|
|
mpeg_dbg(1,"%s() Post release GPIO=%x\n", __func__, cx_read(MO_GP0_IO));
|
|
}
|
|
mutex_unlock(&drv->core->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx8802_check_driver(struct cx8802_driver *drv)
|
|
{
|
|
if (drv == NULL)
|
|
return -ENODEV;
|
|
|
|
if ((drv->type_id != CX88_MPEG_DVB) &&
|
|
(drv->type_id != CX88_MPEG_BLACKBIRD))
|
|
return -EINVAL;
|
|
|
|
if ((drv->hw_access != CX8802_DRVCTL_SHARED) &&
|
|
(drv->hw_access != CX8802_DRVCTL_EXCLUSIVE))
|
|
return -EINVAL;
|
|
|
|
if ((drv->probe == NULL) ||
|
|
(drv->remove == NULL) ||
|
|
(drv->advise_acquire == NULL) ||
|
|
(drv->advise_release == NULL))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cx8802_register_driver(struct cx8802_driver *drv)
|
|
{
|
|
struct cx8802_dev *dev;
|
|
struct cx8802_driver *driver;
|
|
int err, i = 0;
|
|
|
|
printk(KERN_INFO
|
|
"cx88/2: registering cx8802 driver, type: %s access: %s\n",
|
|
drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird",
|
|
drv->hw_access == CX8802_DRVCTL_SHARED ? "shared" : "exclusive");
|
|
|
|
if ((err = cx8802_check_driver(drv)) != 0) {
|
|
printk(KERN_ERR "cx88/2: cx8802_driver is invalid\n");
|
|
return err;
|
|
}
|
|
|
|
list_for_each_entry(dev, &cx8802_devlist, devlist) {
|
|
printk(KERN_INFO
|
|
"%s/2: subsystem: %04x:%04x, board: %s [card=%d]\n",
|
|
dev->core->name, dev->pci->subsystem_vendor,
|
|
dev->pci->subsystem_device, dev->core->board.name,
|
|
dev->core->boardnr);
|
|
|
|
/* Bring up a new struct for each driver instance */
|
|
driver = kzalloc(sizeof(*drv),GFP_KERNEL);
|
|
if (driver == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Snapshot of the driver registration data */
|
|
drv->core = dev->core;
|
|
drv->suspend = cx8802_suspend_common;
|
|
drv->resume = cx8802_resume_common;
|
|
drv->request_acquire = cx8802_request_acquire;
|
|
drv->request_release = cx8802_request_release;
|
|
memcpy(driver, drv, sizeof(*driver));
|
|
|
|
err = drv->probe(driver);
|
|
if (err == 0) {
|
|
i++;
|
|
mutex_lock(&drv->core->lock);
|
|
list_add_tail(&driver->drvlist, &dev->drvlist);
|
|
mutex_unlock(&drv->core->lock);
|
|
} else {
|
|
printk(KERN_ERR
|
|
"%s/2: cx8802 probe failed, err = %d\n",
|
|
dev->core->name, err);
|
|
}
|
|
|
|
}
|
|
|
|
return i ? 0 : -ENODEV;
|
|
}
|
|
|
|
int cx8802_unregister_driver(struct cx8802_driver *drv)
|
|
{
|
|
struct cx8802_dev *dev;
|
|
struct cx8802_driver *d, *dtmp;
|
|
int err = 0;
|
|
|
|
printk(KERN_INFO
|
|
"cx88/2: unregistering cx8802 driver, type: %s access: %s\n",
|
|
drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird",
|
|
drv->hw_access == CX8802_DRVCTL_SHARED ? "shared" : "exclusive");
|
|
|
|
list_for_each_entry(dev, &cx8802_devlist, devlist) {
|
|
printk(KERN_INFO
|
|
"%s/2: subsystem: %04x:%04x, board: %s [card=%d]\n",
|
|
dev->core->name, dev->pci->subsystem_vendor,
|
|
dev->pci->subsystem_device, dev->core->board.name,
|
|
dev->core->boardnr);
|
|
|
|
list_for_each_entry_safe(d, dtmp, &dev->drvlist, drvlist) {
|
|
/* only unregister the correct driver type */
|
|
if (d->type_id != drv->type_id)
|
|
continue;
|
|
|
|
err = d->remove(d);
|
|
if (err == 0) {
|
|
mutex_lock(&drv->core->lock);
|
|
list_del(&d->drvlist);
|
|
mutex_unlock(&drv->core->lock);
|
|
kfree(d);
|
|
} else
|
|
printk(KERN_ERR "%s/2: cx8802 driver remove "
|
|
"failed (%d)\n", dev->core->name, err);
|
|
}
|
|
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* ----------------------------------------------------------- */
|
|
static int __devinit cx8802_probe(struct pci_dev *pci_dev,
|
|
const struct pci_device_id *pci_id)
|
|
{
|
|
struct cx8802_dev *dev;
|
|
struct cx88_core *core;
|
|
struct videobuf_dvb_frontend *demod;
|
|
int err,i;
|
|
|
|
/* general setup */
|
|
core = cx88_core_get(pci_dev);
|
|
if (NULL == core)
|
|
return -EINVAL;
|
|
|
|
printk("%s/2: cx2388x 8802 Driver Manager\n", core->name);
|
|
|
|
err = -ENODEV;
|
|
if (!core->board.mpeg)
|
|
goto fail_core;
|
|
|
|
if (!core->board.num_frontends) {
|
|
printk(KERN_ERR "%s() .num_frontends should be non-zero, err = %d\n", __FUNCTION__, err);
|
|
goto fail_core;
|
|
}
|
|
|
|
err = -ENOMEM;
|
|
dev = kzalloc(sizeof(*dev),GFP_KERNEL);
|
|
if (NULL == dev)
|
|
goto fail_core;
|
|
dev->pci = pci_dev;
|
|
dev->core = core;
|
|
|
|
err = cx8802_init_common(dev);
|
|
if (err != 0)
|
|
goto fail_free;
|
|
|
|
INIT_LIST_HEAD(&dev->drvlist);
|
|
list_add_tail(&dev->devlist,&cx8802_devlist);
|
|
|
|
mutex_init(&dev->frontends.lock);
|
|
INIT_LIST_HEAD(&dev->frontends.frontend.felist);
|
|
|
|
printk(KERN_INFO "%s() allocating %d frontend(s)\n", __FUNCTION__, core->board.num_frontends);
|
|
|
|
for (i = 1; i <= core->board.num_frontends; i++) {
|
|
demod = videobuf_dvb_alloc_frontend(dev, &dev->frontends, i);
|
|
if(demod == NULL) {
|
|
printk(KERN_ERR "%s() failed to alloc\n", __FUNCTION__);
|
|
err = -ENOMEM;
|
|
goto fail_free;
|
|
}
|
|
}
|
|
|
|
/* Maintain a reference so cx88-video can query the 8802 device. */
|
|
core->dvbdev = dev;
|
|
|
|
/* now autoload cx88-dvb or cx88-blackbird */
|
|
request_modules(dev);
|
|
return 0;
|
|
|
|
fail_free:
|
|
kfree(dev);
|
|
fail_core:
|
|
cx88_core_put(core,pci_dev);
|
|
return err;
|
|
}
|
|
|
|
static void __devexit cx8802_remove(struct pci_dev *pci_dev)
|
|
{
|
|
struct cx8802_dev *dev;
|
|
|
|
dev = pci_get_drvdata(pci_dev);
|
|
|
|
dprintk( 1, "%s\n", __func__);
|
|
|
|
if (!list_empty(&dev->drvlist)) {
|
|
struct cx8802_driver *drv, *tmp;
|
|
int err;
|
|
|
|
printk(KERN_WARNING "%s/2: Trying to remove cx8802 driver "
|
|
"while cx8802 sub-drivers still loaded?!\n",
|
|
dev->core->name);
|
|
|
|
list_for_each_entry_safe(drv, tmp, &dev->drvlist, drvlist) {
|
|
err = drv->remove(drv);
|
|
if (err == 0) {
|
|
mutex_lock(&drv->core->lock);
|
|
list_del(&drv->drvlist);
|
|
mutex_unlock(&drv->core->lock);
|
|
} else
|
|
printk(KERN_ERR "%s/2: cx8802 driver remove "
|
|
"failed (%d)\n", dev->core->name, err);
|
|
kfree(drv);
|
|
}
|
|
}
|
|
|
|
/* Destroy any 8802 reference. */
|
|
dev->core->dvbdev = NULL;
|
|
|
|
/* common */
|
|
cx8802_fini_common(dev);
|
|
cx88_core_put(dev->core,dev->pci);
|
|
kfree(dev);
|
|
}
|
|
|
|
static 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 = "cx88-mpeg driver manager",
|
|
.id_table = cx8802_pci_tbl,
|
|
.probe = cx8802_probe,
|
|
.remove = __devexit_p(cx8802_remove),
|
|
};
|
|
|
|
static int cx8802_init(void)
|
|
{
|
|
printk(KERN_INFO "cx88/2: cx2388x MPEG-TS Driver Manager 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_register_driver(&cx8802_pci_driver);
|
|
}
|
|
|
|
static void cx8802_fini(void)
|
|
{
|
|
pci_unregister_driver(&cx8802_pci_driver);
|
|
}
|
|
|
|
module_init(cx8802_init);
|
|
module_exit(cx8802_fini);
|
|
EXPORT_SYMBOL(cx8802_buf_prepare);
|
|
EXPORT_SYMBOL(cx8802_buf_queue);
|
|
EXPORT_SYMBOL(cx8802_cancel_buffers);
|
|
|
|
EXPORT_SYMBOL(cx8802_register_driver);
|
|
EXPORT_SYMBOL(cx8802_unregister_driver);
|
|
EXPORT_SYMBOL(cx8802_get_driver);
|
|
/* ----------------------------------------------------------- */
|
|
/*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* End:
|
|
* kate: eol "unix"; indent-width 3; remove-trailing-space on; replace-trailing-space-save on; tab-width 8; replace-tabs off; space-indent off; mixed-indent off
|
|
*/
|