summaryrefslogtreecommitdiffstats
path: root/drivers/media/pci/b2c2
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /drivers/media/pci/b2c2
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/pci/b2c2')
-rw-r--r--drivers/media/pci/b2c2/Kconfig16
-rw-r--r--drivers/media/pci/b2c2/Makefile9
-rw-r--r--drivers/media/pci/b2c2/flexcop-dma.c175
-rw-r--r--drivers/media/pci/b2c2/flexcop-pci.c440
4 files changed, 640 insertions, 0 deletions
diff --git a/drivers/media/pci/b2c2/Kconfig b/drivers/media/pci/b2c2/Kconfig
new file mode 100644
index 000000000..0a7d1e178
--- /dev/null
+++ b/drivers/media/pci/b2c2/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DVB_B2C2_FLEXCOP_PCI
+ tristate "Technisat/B2C2 Air/Sky/Cable2PC PCI"
+ depends on DVB_CORE && I2C
+ help
+ Support for the Air/Sky/CableStar2 PCI card (DVB/ATSC) by Technisat/B2C2.
+
+ Say Y if you own such a device and want to use it.
+
+config DVB_B2C2_FLEXCOP_PCI_DEBUG
+ bool "Enable debug for the B2C2 FlexCop drivers"
+ depends on DVB_B2C2_FLEXCOP_PCI
+ select DVB_B2C2_FLEXCOP_DEBUG
+ help
+ Say Y if you want to enable the module option to control debug messages
+ of all B2C2 FlexCop drivers.
diff --git a/drivers/media/pci/b2c2/Makefile b/drivers/media/pci/b2c2/Makefile
new file mode 100644
index 000000000..14ed6e441
--- /dev/null
+++ b/drivers/media/pci/b2c2/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+ifneq ($(CONFIG_DVB_B2C2_FLEXCOP_PCI),)
+b2c2-flexcop-pci-objs += flexcop-dma.o
+endif
+
+b2c2-flexcop-pci-objs += flexcop-pci.o
+obj-$(CONFIG_DVB_B2C2_FLEXCOP_PCI) += b2c2-flexcop-pci.o
+
+ccflags-y += -I $(srctree)/drivers/media/common/b2c2/
diff --git a/drivers/media/pci/b2c2/flexcop-dma.c b/drivers/media/pci/b2c2/flexcop-dma.c
new file mode 100644
index 000000000..ba45b378d
--- /dev/null
+++ b/drivers/media/pci/b2c2/flexcop-dma.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
+ * flexcop-dma.c - configuring and controlling the DMA of the FlexCop
+ * see flexcop.c for copyright information
+ */
+#include "flexcop.h"
+
+int flexcop_dma_allocate(struct pci_dev *pdev,
+ struct flexcop_dma *dma, u32 size)
+{
+ u8 *tcpu;
+ dma_addr_t tdma = 0;
+
+ if (size % 2) {
+ err("dma buffersize has to be even.");
+ return -EINVAL;
+ }
+
+ tcpu = pci_alloc_consistent(pdev, size, &tdma);
+ if (tcpu != NULL) {
+ dma->pdev = pdev;
+ dma->cpu_addr0 = tcpu;
+ dma->dma_addr0 = tdma;
+ dma->cpu_addr1 = tcpu + size/2;
+ dma->dma_addr1 = tdma + size/2;
+ dma->size = size/2;
+ return 0;
+ }
+ return -ENOMEM;
+}
+EXPORT_SYMBOL(flexcop_dma_allocate);
+
+void flexcop_dma_free(struct flexcop_dma *dma)
+{
+ pci_free_consistent(dma->pdev, dma->size*2,
+ dma->cpu_addr0, dma->dma_addr0);
+ memset(dma, 0, sizeof(struct flexcop_dma));
+}
+EXPORT_SYMBOL(flexcop_dma_free);
+
+int flexcop_dma_config(struct flexcop_device *fc,
+ struct flexcop_dma *dma,
+ flexcop_dma_index_t dma_idx)
+{
+ flexcop_ibi_value v0x0, v0x4, v0xc;
+
+ v0x0.raw = v0x4.raw = v0xc.raw = 0;
+ v0x0.dma_0x0.dma_address0 = dma->dma_addr0 >> 2;
+ v0xc.dma_0xc.dma_address1 = dma->dma_addr1 >> 2;
+ v0x4.dma_0x4_write.dma_addr_size = dma->size / 4;
+
+ if ((dma_idx & FC_DMA_1) == dma_idx) {
+ fc->write_ibi_reg(fc, dma1_000, v0x0);
+ fc->write_ibi_reg(fc, dma1_004, v0x4);
+ fc->write_ibi_reg(fc, dma1_00c, v0xc);
+ } else if ((dma_idx & FC_DMA_2) == dma_idx) {
+ fc->write_ibi_reg(fc, dma2_010, v0x0);
+ fc->write_ibi_reg(fc, dma2_014, v0x4);
+ fc->write_ibi_reg(fc, dma2_01c, v0xc);
+ } else {
+ err("either DMA1 or DMA2 can be configured within one %s call.",
+ __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(flexcop_dma_config);
+
+/* start the DMA transfers, but not the DMA IRQs */
+int flexcop_dma_xfer_control(struct flexcop_device *fc,
+ flexcop_dma_index_t dma_idx,
+ flexcop_dma_addr_index_t index,
+ int onoff)
+{
+ flexcop_ibi_value v0x0, v0xc;
+ flexcop_ibi_register r0x0, r0xc;
+
+ if ((dma_idx & FC_DMA_1) == dma_idx) {
+ r0x0 = dma1_000;
+ r0xc = dma1_00c;
+ } else if ((dma_idx & FC_DMA_2) == dma_idx) {
+ r0x0 = dma2_010;
+ r0xc = dma2_01c;
+ } else {
+ err("transfer DMA1 or DMA2 can be started within one %s call.",
+ __func__);
+ return -EINVAL;
+ }
+
+ v0x0 = fc->read_ibi_reg(fc, r0x0);
+ v0xc = fc->read_ibi_reg(fc, r0xc);
+
+ deb_rdump("reg: %03x: %x\n", r0x0, v0x0.raw);
+ deb_rdump("reg: %03x: %x\n", r0xc, v0xc.raw);
+
+ if (index & FC_DMA_SUBADDR_0)
+ v0x0.dma_0x0.dma_0start = onoff;
+
+ if (index & FC_DMA_SUBADDR_1)
+ v0xc.dma_0xc.dma_1start = onoff;
+
+ fc->write_ibi_reg(fc, r0x0, v0x0);
+ fc->write_ibi_reg(fc, r0xc, v0xc);
+
+ deb_rdump("reg: %03x: %x\n", r0x0, v0x0.raw);
+ deb_rdump("reg: %03x: %x\n", r0xc, v0xc.raw);
+ return 0;
+}
+EXPORT_SYMBOL(flexcop_dma_xfer_control);
+
+static int flexcop_dma_remap(struct flexcop_device *fc,
+ flexcop_dma_index_t dma_idx,
+ int onoff)
+{
+ flexcop_ibi_register r = (dma_idx & FC_DMA_1) ? dma1_00c : dma2_01c;
+ flexcop_ibi_value v = fc->read_ibi_reg(fc, r);
+
+ deb_info("%s\n", __func__);
+ v.dma_0xc.remap_enable = onoff;
+ fc->write_ibi_reg(fc, r, v);
+ return 0;
+}
+
+int flexcop_dma_control_size_irq(struct flexcop_device *fc,
+ flexcop_dma_index_t no,
+ int onoff)
+{
+ flexcop_ibi_value v = fc->read_ibi_reg(fc, ctrl_208);
+
+ if (no & FC_DMA_1)
+ v.ctrl_208.DMA1_IRQ_Enable_sig = onoff;
+
+ if (no & FC_DMA_2)
+ v.ctrl_208.DMA2_IRQ_Enable_sig = onoff;
+
+ fc->write_ibi_reg(fc, ctrl_208, v);
+ return 0;
+}
+EXPORT_SYMBOL(flexcop_dma_control_size_irq);
+
+int flexcop_dma_control_timer_irq(struct flexcop_device *fc,
+ flexcop_dma_index_t no,
+ int onoff)
+{
+ flexcop_ibi_value v = fc->read_ibi_reg(fc, ctrl_208);
+
+ if (no & FC_DMA_1)
+ v.ctrl_208.DMA1_Timer_Enable_sig = onoff;
+
+ if (no & FC_DMA_2)
+ v.ctrl_208.DMA2_Timer_Enable_sig = onoff;
+
+ fc->write_ibi_reg(fc, ctrl_208, v);
+ return 0;
+}
+EXPORT_SYMBOL(flexcop_dma_control_timer_irq);
+
+/* 1 cycles = 1.97 msec */
+int flexcop_dma_config_timer(struct flexcop_device *fc,
+ flexcop_dma_index_t dma_idx, u8 cycles)
+{
+ flexcop_ibi_register r = (dma_idx & FC_DMA_1) ? dma1_004 : dma2_014;
+ flexcop_ibi_value v = fc->read_ibi_reg(fc, r);
+
+ flexcop_dma_remap(fc, dma_idx, 0);
+
+ deb_info("%s\n", __func__);
+ v.dma_0x4_write.dmatimer = cycles;
+ fc->write_ibi_reg(fc, r, v);
+ return 0;
+}
+EXPORT_SYMBOL(flexcop_dma_config_timer);
+
diff --git a/drivers/media/pci/b2c2/flexcop-pci.c b/drivers/media/pci/b2c2/flexcop-pci.c
new file mode 100644
index 000000000..c9e6c7d66
--- /dev/null
+++ b/drivers/media/pci/b2c2/flexcop-pci.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Linux driver the digital TV devices equipped with B2C2 FlexcopII(b)/III
+ * flexcop-pci.c - covers the PCI part including DMA transfers
+ * see flexcop.c for copyright information
+ */
+
+#define FC_LOG_PREFIX "flexcop-pci"
+#include "flexcop-common.h"
+
+static int enable_pid_filtering = 1;
+module_param(enable_pid_filtering, int, 0444);
+MODULE_PARM_DESC(enable_pid_filtering,
+ "enable hardware pid filtering: supported values: 0 (fullts), 1");
+
+static int irq_chk_intv = 100;
+module_param(irq_chk_intv, int, 0644);
+MODULE_PARM_DESC(irq_chk_intv, "set the interval for IRQ streaming watchdog.");
+
+#ifdef CONFIG_DVB_B2C2_FLEXCOP_DEBUG
+#define dprintk(level,args...) \
+ do { if ((debug & level)) printk(args); } while (0)
+#define DEBSTATUS ""
+#else
+#define dprintk(level,args...)
+#define DEBSTATUS " (debugging is not enabled)"
+#endif
+
+#define deb_info(args...) dprintk(0x01, args)
+#define deb_reg(args...) dprintk(0x02, args)
+#define deb_ts(args...) dprintk(0x04, args)
+#define deb_irq(args...) dprintk(0x08, args)
+#define deb_chk(args...) dprintk(0x10, args)
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug,
+ "set debug level (1=info,2=regs,4=TS,8=irqdma,16=check (|-able))."
+ DEBSTATUS);
+
+#define DRIVER_VERSION "0.1"
+#define DRIVER_NAME "flexcop-pci"
+#define DRIVER_AUTHOR "Patrick Boettcher <patrick.boettcher@posteo.de>"
+
+struct flexcop_pci {
+ struct pci_dev *pdev;
+
+#define FC_PCI_INIT 0x01
+#define FC_PCI_DMA_INIT 0x02
+ int init_state;
+
+ void __iomem *io_mem;
+ u32 irq;
+ /* buffersize (at least for DMA1, need to be % 188 == 0,
+ * this logic is required */
+#define FC_DEFAULT_DMA1_BUFSIZE (1280 * 188)
+#define FC_DEFAULT_DMA2_BUFSIZE (10 * 188)
+ struct flexcop_dma dma[2];
+
+ int active_dma1_addr; /* 0 = addr0 of dma1; 1 = addr1 of dma1 */
+ u32 last_dma1_cur_pos;
+ /* position of the pointer last time the timer/packet irq occurred */
+ int count;
+ int count_prev;
+ int stream_problem;
+
+ spinlock_t irq_lock;
+ unsigned long last_irq;
+
+ struct delayed_work irq_check_work;
+ struct flexcop_device *fc_dev;
+};
+
+static int lastwreg, lastwval, lastrreg, lastrval;
+
+static flexcop_ibi_value flexcop_pci_read_ibi_reg(struct flexcop_device *fc,
+ flexcop_ibi_register r)
+{
+ struct flexcop_pci *fc_pci = fc->bus_specific;
+ flexcop_ibi_value v;
+ v.raw = readl(fc_pci->io_mem + r);
+
+ if (lastrreg != r || lastrval != v.raw) {
+ lastrreg = r; lastrval = v.raw;
+ deb_reg("new rd: %3x: %08x\n", r, v.raw);
+ }
+
+ return v;
+}
+
+static int flexcop_pci_write_ibi_reg(struct flexcop_device *fc,
+ flexcop_ibi_register r, flexcop_ibi_value v)
+{
+ struct flexcop_pci *fc_pci = fc->bus_specific;
+
+ if (lastwreg != r || lastwval != v.raw) {
+ lastwreg = r; lastwval = v.raw;
+ deb_reg("new wr: %3x: %08x\n", r, v.raw);
+ }
+
+ writel(v.raw, fc_pci->io_mem + r);
+ return 0;
+}
+
+static void flexcop_pci_irq_check_work(struct work_struct *work)
+{
+ struct flexcop_pci *fc_pci =
+ container_of(work, struct flexcop_pci, irq_check_work.work);
+ struct flexcop_device *fc = fc_pci->fc_dev;
+
+ if (fc->feedcount) {
+
+ if (fc_pci->count == fc_pci->count_prev) {
+ deb_chk("no IRQ since the last check\n");
+ if (fc_pci->stream_problem++ == 3) {
+ struct dvb_demux_feed *feed;
+ deb_info("flexcop-pci: stream problem, resetting pid filter\n");
+
+ spin_lock_irq(&fc->demux.lock);
+ list_for_each_entry(feed, &fc->demux.feed_list,
+ list_head) {
+ flexcop_pid_feed_control(fc, feed, 0);
+ }
+
+ list_for_each_entry(feed, &fc->demux.feed_list,
+ list_head) {
+ flexcop_pid_feed_control(fc, feed, 1);
+ }
+ spin_unlock_irq(&fc->demux.lock);
+
+ fc_pci->stream_problem = 0;
+ }
+ } else {
+ fc_pci->stream_problem = 0;
+ fc_pci->count_prev = fc_pci->count;
+ }
+ }
+
+ schedule_delayed_work(&fc_pci->irq_check_work,
+ msecs_to_jiffies(irq_chk_intv < 100 ? 100 : irq_chk_intv));
+}
+
+/* When PID filtering is turned on, we use the timer IRQ, because small amounts
+ * of data need to be passed to the user space instantly as well. When PID
+ * filtering is turned off, we use the page-change-IRQ */
+static irqreturn_t flexcop_pci_isr(int irq, void *dev_id)
+{
+ struct flexcop_pci *fc_pci = dev_id;
+ struct flexcop_device *fc = fc_pci->fc_dev;
+ unsigned long flags;
+ flexcop_ibi_value v;
+ irqreturn_t ret = IRQ_HANDLED;
+
+ spin_lock_irqsave(&fc_pci->irq_lock, flags);
+ v = fc->read_ibi_reg(fc, irq_20c);
+
+ /* errors */
+ if (v.irq_20c.Data_receiver_error)
+ deb_chk("data receiver error\n");
+ if (v.irq_20c.Continuity_error_flag)
+ deb_chk("Continuity error flag is set\n");
+ if (v.irq_20c.LLC_SNAP_FLAG_set)
+ deb_chk("LLC_SNAP_FLAG_set is set\n");
+ if (v.irq_20c.Transport_Error)
+ deb_chk("Transport error\n");
+
+ if ((fc_pci->count % 1000) == 0)
+ deb_chk("%d valid irq took place so far\n", fc_pci->count);
+
+ if (v.irq_20c.DMA1_IRQ_Status == 1) {
+ if (fc_pci->active_dma1_addr == 0)
+ flexcop_pass_dmx_packets(fc_pci->fc_dev,
+ fc_pci->dma[0].cpu_addr0,
+ fc_pci->dma[0].size / 188);
+ else
+ flexcop_pass_dmx_packets(fc_pci->fc_dev,
+ fc_pci->dma[0].cpu_addr1,
+ fc_pci->dma[0].size / 188);
+
+ deb_irq("page change to page: %d\n",!fc_pci->active_dma1_addr);
+ fc_pci->active_dma1_addr = !fc_pci->active_dma1_addr;
+ /* for the timer IRQ we only can use buffer dmx feeding, because we don't have
+ * complete TS packets when reading from the DMA memory */
+ } else if (v.irq_20c.DMA1_Timer_Status == 1) {
+ dma_addr_t cur_addr =
+ fc->read_ibi_reg(fc,dma1_008).dma_0x8.dma_cur_addr << 2;
+ u32 cur_pos = cur_addr - fc_pci->dma[0].dma_addr0;
+ if (cur_pos > fc_pci->dma[0].size * 2)
+ goto error;
+
+ deb_irq("%u irq: %08x cur_addr: %llx: cur_pos: %08x, last_cur_pos: %08x ",
+ jiffies_to_usecs(jiffies - fc_pci->last_irq),
+ v.raw, (unsigned long long)cur_addr, cur_pos,
+ fc_pci->last_dma1_cur_pos);
+ fc_pci->last_irq = jiffies;
+
+ /* buffer end was reached, restarted from the beginning
+ * pass the data from last_cur_pos to the buffer end to the demux
+ */
+ if (cur_pos < fc_pci->last_dma1_cur_pos) {
+ deb_irq(" end was reached: passing %d bytes ",
+ (fc_pci->dma[0].size*2 - 1) -
+ fc_pci->last_dma1_cur_pos);
+ flexcop_pass_dmx_data(fc_pci->fc_dev,
+ fc_pci->dma[0].cpu_addr0 +
+ fc_pci->last_dma1_cur_pos,
+ (fc_pci->dma[0].size*2) -
+ fc_pci->last_dma1_cur_pos);
+ fc_pci->last_dma1_cur_pos = 0;
+ }
+
+ if (cur_pos > fc_pci->last_dma1_cur_pos) {
+ deb_irq(" passing %d bytes ",
+ cur_pos - fc_pci->last_dma1_cur_pos);
+ flexcop_pass_dmx_data(fc_pci->fc_dev,
+ fc_pci->dma[0].cpu_addr0 +
+ fc_pci->last_dma1_cur_pos,
+ cur_pos - fc_pci->last_dma1_cur_pos);
+ }
+ deb_irq("\n");
+
+ fc_pci->last_dma1_cur_pos = cur_pos;
+ fc_pci->count++;
+ } else {
+ deb_irq("isr for flexcop called, apparently without reason (%08x)\n",
+ v.raw);
+ ret = IRQ_NONE;
+ }
+
+error:
+ spin_unlock_irqrestore(&fc_pci->irq_lock, flags);
+ return ret;
+}
+
+static int flexcop_pci_stream_control(struct flexcop_device *fc, int onoff)
+{
+ struct flexcop_pci *fc_pci = fc->bus_specific;
+ if (onoff) {
+ flexcop_dma_config(fc, &fc_pci->dma[0], FC_DMA_1);
+ flexcop_dma_config(fc, &fc_pci->dma[1], FC_DMA_2);
+ flexcop_dma_config_timer(fc, FC_DMA_1, 0);
+ flexcop_dma_xfer_control(fc, FC_DMA_1,
+ FC_DMA_SUBADDR_0 | FC_DMA_SUBADDR_1, 1);
+ deb_irq("DMA xfer enabled\n");
+
+ fc_pci->last_dma1_cur_pos = 0;
+ flexcop_dma_control_timer_irq(fc, FC_DMA_1, 1);
+ deb_irq("IRQ enabled\n");
+ fc_pci->count_prev = fc_pci->count;
+ } else {
+ flexcop_dma_control_timer_irq(fc, FC_DMA_1, 0);
+ deb_irq("IRQ disabled\n");
+
+ flexcop_dma_xfer_control(fc, FC_DMA_1,
+ FC_DMA_SUBADDR_0 | FC_DMA_SUBADDR_1, 0);
+ deb_irq("DMA xfer disabled\n");
+ }
+ return 0;
+}
+
+static int flexcop_pci_dma_init(struct flexcop_pci *fc_pci)
+{
+ int ret;
+ ret = flexcop_dma_allocate(fc_pci->pdev, &fc_pci->dma[0],
+ FC_DEFAULT_DMA1_BUFSIZE);
+ if (ret != 0)
+ return ret;
+
+ ret = flexcop_dma_allocate(fc_pci->pdev, &fc_pci->dma[1],
+ FC_DEFAULT_DMA2_BUFSIZE);
+ if (ret != 0) {
+ flexcop_dma_free(&fc_pci->dma[0]);
+ return ret;
+ }
+
+ flexcop_sram_set_dest(fc_pci->fc_dev, FC_SRAM_DEST_MEDIA |
+ FC_SRAM_DEST_NET, FC_SRAM_DEST_TARGET_DMA1);
+ flexcop_sram_set_dest(fc_pci->fc_dev, FC_SRAM_DEST_CAO |
+ FC_SRAM_DEST_CAI, FC_SRAM_DEST_TARGET_DMA2);
+ fc_pci->init_state |= FC_PCI_DMA_INIT;
+ return ret;
+}
+
+static void flexcop_pci_dma_exit(struct flexcop_pci *fc_pci)
+{
+ if (fc_pci->init_state & FC_PCI_DMA_INIT) {
+ flexcop_dma_free(&fc_pci->dma[0]);
+ flexcop_dma_free(&fc_pci->dma[1]);
+ }
+ fc_pci->init_state &= ~FC_PCI_DMA_INIT;
+}
+
+static int flexcop_pci_init(struct flexcop_pci *fc_pci)
+{
+ int ret;
+
+ info("card revision %x", fc_pci->pdev->revision);
+
+ if ((ret = pci_enable_device(fc_pci->pdev)) != 0)
+ return ret;
+ pci_set_master(fc_pci->pdev);
+
+ if ((ret = pci_request_regions(fc_pci->pdev, DRIVER_NAME)) != 0)
+ goto err_pci_disable_device;
+
+ fc_pci->io_mem = pci_iomap(fc_pci->pdev, 0, 0x800);
+
+ if (!fc_pci->io_mem) {
+ err("cannot map io memory\n");
+ ret = -EIO;
+ goto err_pci_release_regions;
+ }
+
+ pci_set_drvdata(fc_pci->pdev, fc_pci);
+ spin_lock_init(&fc_pci->irq_lock);
+ if ((ret = request_irq(fc_pci->pdev->irq, flexcop_pci_isr,
+ IRQF_SHARED, DRIVER_NAME, fc_pci)) != 0)
+ goto err_pci_iounmap;
+
+ fc_pci->init_state |= FC_PCI_INIT;
+ return ret;
+
+err_pci_iounmap:
+ pci_iounmap(fc_pci->pdev, fc_pci->io_mem);
+err_pci_release_regions:
+ pci_release_regions(fc_pci->pdev);
+err_pci_disable_device:
+ pci_disable_device(fc_pci->pdev);
+ return ret;
+}
+
+static void flexcop_pci_exit(struct flexcop_pci *fc_pci)
+{
+ if (fc_pci->init_state & FC_PCI_INIT) {
+ free_irq(fc_pci->pdev->irq, fc_pci);
+ pci_iounmap(fc_pci->pdev, fc_pci->io_mem);
+ pci_release_regions(fc_pci->pdev);
+ pci_disable_device(fc_pci->pdev);
+ }
+ fc_pci->init_state &= ~FC_PCI_INIT;
+}
+
+static int flexcop_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct flexcop_device *fc;
+ struct flexcop_pci *fc_pci;
+ int ret = -ENOMEM;
+
+ if ((fc = flexcop_device_kmalloc(sizeof(struct flexcop_pci))) == NULL) {
+ err("out of memory\n");
+ return -ENOMEM;
+ }
+
+ /* general flexcop init */
+ fc_pci = fc->bus_specific;
+ fc_pci->fc_dev = fc;
+
+ fc->read_ibi_reg = flexcop_pci_read_ibi_reg;
+ fc->write_ibi_reg = flexcop_pci_write_ibi_reg;
+ fc->i2c_request = flexcop_i2c_request;
+ fc->get_mac_addr = flexcop_eeprom_check_mac_addr;
+ fc->stream_control = flexcop_pci_stream_control;
+
+ if (enable_pid_filtering)
+ info("will use the HW PID filter.");
+ else
+ info("will pass the complete TS to the demuxer.");
+
+ fc->pid_filtering = enable_pid_filtering;
+ fc->bus_type = FC_PCI;
+ fc->dev = &pdev->dev;
+ fc->owner = THIS_MODULE;
+
+ /* bus specific part */
+ fc_pci->pdev = pdev;
+ if ((ret = flexcop_pci_init(fc_pci)) != 0)
+ goto err_kfree;
+
+ /* init flexcop */
+ if ((ret = flexcop_device_initialize(fc)) != 0)
+ goto err_pci_exit;
+
+ /* init dma */
+ if ((ret = flexcop_pci_dma_init(fc_pci)) != 0)
+ goto err_fc_exit;
+
+ INIT_DELAYED_WORK(&fc_pci->irq_check_work, flexcop_pci_irq_check_work);
+
+ if (irq_chk_intv > 0)
+ schedule_delayed_work(&fc_pci->irq_check_work,
+ msecs_to_jiffies(irq_chk_intv < 100 ?
+ 100 :
+ irq_chk_intv));
+ return ret;
+
+err_fc_exit:
+ flexcop_device_exit(fc);
+err_pci_exit:
+ flexcop_pci_exit(fc_pci);
+err_kfree:
+ flexcop_device_kfree(fc);
+ return ret;
+}
+
+/* in theory every _exit function should be called exactly two times,
+ * here and in the bail-out-part of the _init-function
+ */
+static void flexcop_pci_remove(struct pci_dev *pdev)
+{
+ struct flexcop_pci *fc_pci = pci_get_drvdata(pdev);
+
+ if (irq_chk_intv > 0)
+ cancel_delayed_work(&fc_pci->irq_check_work);
+
+ flexcop_pci_dma_exit(fc_pci);
+ flexcop_device_exit(fc_pci->fc_dev);
+ flexcop_pci_exit(fc_pci);
+ flexcop_device_kfree(fc_pci->fc_dev);
+}
+
+static const struct pci_device_id flexcop_pci_tbl[] = {
+ { PCI_DEVICE(0x13d0, 0x2103) },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, flexcop_pci_tbl);
+
+static struct pci_driver flexcop_pci_driver = {
+ .name = "b2c2_flexcop_pci",
+ .id_table = flexcop_pci_tbl,
+ .probe = flexcop_pci_probe,
+ .remove = flexcop_pci_remove,
+};
+
+module_pci_driver(flexcop_pci_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_NAME);
+MODULE_LICENSE("GPL");