summaryrefslogtreecommitdiffstats
path: root/drivers/media/pci/cx88
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/pci/cx88')
-rw-r--r--drivers/media/pci/cx88/Kconfig93
-rw-r--r--drivers/media/pci/cx88/Makefile14
-rw-r--r--drivers/media/pci/cx88/cx88-alsa.c1007
-rw-r--r--drivers/media/pci/cx88/cx88-blackbird.c1253
-rw-r--r--drivers/media/pci/cx88/cx88-cards.c3856
-rw-r--r--drivers/media/pci/cx88/cx88-core.c1099
-rw-r--r--drivers/media/pci/cx88/cx88-dsp.c323
-rw-r--r--drivers/media/pci/cx88/cx88-dvb.c1844
-rw-r--r--drivers/media/pci/cx88/cx88-i2c.c173
-rw-r--r--drivers/media/pci/cx88/cx88-input.c651
-rw-r--r--drivers/media/pci/cx88/cx88-mpeg.c807
-rw-r--r--drivers/media/pci/cx88/cx88-reg.h816
-rw-r--r--drivers/media/pci/cx88/cx88-tvaudio.c1046
-rw-r--r--drivers/media/pci/cx88/cx88-vbi.c235
-rw-r--r--drivers/media/pci/cx88/cx88-video.c1635
-rw-r--r--drivers/media/pci/cx88/cx88-vp3054-i2c.c141
-rw-r--r--drivers/media/pci/cx88/cx88-vp3054-i2c.h26
-rw-r--r--drivers/media/pci/cx88/cx88.h731
18 files changed, 15750 insertions, 0 deletions
diff --git a/drivers/media/pci/cx88/Kconfig b/drivers/media/pci/cx88/Kconfig
new file mode 100644
index 000000000..24e1e7c41
--- /dev/null
+++ b/drivers/media/pci/cx88/Kconfig
@@ -0,0 +1,93 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_CX88
+ tristate "Conexant 2388x (bt878 successor) support"
+ depends on VIDEO_DEV && PCI && I2C && RC_CORE
+ select I2C_ALGOBIT
+ select VIDEOBUF2_DMA_SG
+ select VIDEO_TUNER
+ select VIDEO_TVEEPROM
+ select VIDEO_WM8775 if MEDIA_SUBDRV_AUTOSELECT
+ help
+ This is a video4linux driver for Conexant 2388x based
+ TV cards.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cx8800
+
+config VIDEO_CX88_ALSA
+ tristate "Conexant 2388x DMA audio support"
+ depends on VIDEO_CX88 && SND
+ select SND_PCM
+ help
+ This is a video4linux driver for direct (DMA) audio on
+ Conexant 2388x based TV cards using ALSA.
+
+ It only works with boards with function 01 enabled.
+ To check if your board supports, use lspci -n.
+ If supported, you should see 14f1:8801 or 14f1:8811
+ PCI device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cx88-alsa.
+
+config VIDEO_CX88_BLACKBIRD
+ tristate "Blackbird MPEG encoder support (cx2388x + cx23416)"
+ depends on VIDEO_CX88
+ select VIDEO_CX2341X
+ help
+ This adds support for MPEG encoder cards based on the
+ Blackbird reference design, using the Conexant 2388x
+ and 23416 chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cx88-blackbird.
+
+config VIDEO_CX88_DVB
+ tristate "DVB/ATSC Support for cx2388x based TV cards"
+ depends on VIDEO_CX88 && DVB_CORE
+ select VIDEOBUF2_DVB
+ select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_OR51132 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_CX22702 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_NXT200X if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_CX24123 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT
+ select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
+ help
+ This adds support for DVB/ATSC cards based on the
+ Conexant 2388x chip.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cx88-dvb.
+
+config VIDEO_CX88_ENABLE_VP3054
+ bool "VP-3054 Secondary I2C Bus Support"
+ default y
+ depends on VIDEO_CX88_DVB && DVB_MT352
+ help
+ This adds DVB-T support for cards based on the
+ Conexant 2388x chip and the MT352 demodulator,
+ which also require support for the VP-3054
+ Secondary I2C bus, such at DNTV Live! DVB-T Pro.
+
+config VIDEO_CX88_VP3054
+ tristate
+ depends on VIDEO_CX88_DVB && VIDEO_CX88_ENABLE_VP3054
+ default y
+
+config VIDEO_CX88_MPEG
+ tristate
+ depends on VIDEO_CX88_DVB || VIDEO_CX88_BLACKBIRD
+ default y
diff --git a/drivers/media/pci/cx88/Makefile b/drivers/media/pci/cx88/Makefile
new file mode 100644
index 000000000..c2a015869
--- /dev/null
+++ b/drivers/media/pci/cx88/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+cx88xx-objs := cx88-cards.o cx88-core.o cx88-i2c.o cx88-tvaudio.o \
+ cx88-dsp.o cx88-input.o
+cx8800-objs := cx88-video.o cx88-vbi.o
+cx8802-objs := cx88-mpeg.o
+
+obj-$(CONFIG_VIDEO_CX88) += cx88xx.o cx8800.o
+obj-$(CONFIG_VIDEO_CX88_MPEG) += cx8802.o
+obj-$(CONFIG_VIDEO_CX88_ALSA) += cx88-alsa.o
+obj-$(CONFIG_VIDEO_CX88_BLACKBIRD) += cx88-blackbird.o
+obj-$(CONFIG_VIDEO_CX88_DVB) += cx88-dvb.o
+obj-$(CONFIG_VIDEO_CX88_VP3054) += cx88-vp3054-i2c.o
+ccflags-y += -I $(srctree)/drivers/media/tuners
+ccflags-y += -I $(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/pci/cx88/cx88-alsa.c b/drivers/media/pci/cx88/cx88-alsa.c
new file mode 100644
index 000000000..29fb1311e
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-alsa.c
@@ -0,0 +1,1007 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for audio capture
+ * PCI function #1 of the cx2388x.
+ *
+ * (c) 2007 Trent Piepho <xyzzy@speakeasy.org>
+ * (c) 2005,2006 Ricardo Cerqueira <v4l@cerqueira.org>
+ * (c) 2005 Mauro Carvalho Chehab <mchehab@kernel.org>
+ * Based on a dummy cx88 module by Gerd Knorr <kraxel@bytesex.org>
+ * Based on dummy.c by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include "cx88.h"
+#include "cx88-reg.h"
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/vmalloc.h>
+#include <linux/dma-mapping.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <media/i2c/wm8775.h>
+
+#define dprintk(level, fmt, arg...) do { \
+ if (debug + 1 > level) \
+ printk(KERN_DEBUG pr_fmt("%s: alsa: " fmt), \
+ chip->core->name, ##arg); \
+} while (0)
+
+/*
+ * Data type declarations - Can be moded to a header file later
+ */
+
+struct cx88_audio_buffer {
+ unsigned int bpl;
+ struct cx88_riscmem risc;
+ void *vaddr;
+ struct scatterlist *sglist;
+ int sglen;
+ unsigned long nr_pages;
+};
+
+struct cx88_audio_dev {
+ struct cx88_core *core;
+ struct cx88_dmaqueue q;
+
+ /* pci i/o */
+ struct pci_dev *pci;
+
+ /* audio controls */
+ int irq;
+
+ struct snd_card *card;
+
+ spinlock_t reg_lock;
+ atomic_t count;
+
+ unsigned int dma_size;
+ unsigned int period_size;
+ unsigned int num_periods;
+
+ struct cx88_audio_buffer *buf;
+
+ struct snd_pcm_substream *substream;
+};
+
+/*
+ * Module global static vars
+ */
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static const char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable cx88x soundcard. default enabled.");
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for cx88x capture interface(s).");
+
+/*
+ * Module macros
+ */
+
+MODULE_DESCRIPTION("ALSA driver module for cx2388x based TV cards");
+MODULE_AUTHOR("Ricardo Cerqueira");
+MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@kernel.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(CX88_VERSION);
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages");
+
+/*
+ * Module specific functions
+ */
+
+/*
+ * BOARD Specific: Sets audio DMA
+ */
+
+static int _cx88_start_audio_dma(struct cx88_audio_dev *chip)
+{
+ struct cx88_audio_buffer *buf = chip->buf;
+ struct cx88_core *core = chip->core;
+ const struct sram_channel *audio_ch = &cx88_sram_channels[SRAM_CH25];
+
+ /* Make sure RISC/FIFO are off before changing FIFO/RISC settings */
+ cx_clear(MO_AUD_DMACNTRL, 0x11);
+
+ /* setup fifo + format - out channel */
+ cx88_sram_channel_setup(chip->core, audio_ch, buf->bpl, buf->risc.dma);
+
+ /* sets bpl size */
+ cx_write(MO_AUDD_LNGTH, buf->bpl);
+
+ /* reset counter */
+ cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET);
+ atomic_set(&chip->count, 0);
+
+ dprintk(1,
+ "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d byte buffer\n",
+ buf->bpl, cx_read(audio_ch->cmds_start + 8) >> 1,
+ chip->num_periods, buf->bpl * chip->num_periods);
+
+ /* Enables corresponding bits at AUD_INT_STAT */
+ cx_write(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC |
+ AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1);
+
+ /* Clean any pending interrupt bits already set */
+ cx_write(MO_AUD_INTSTAT, ~0);
+
+ /* enable audio irqs */
+ cx_set(MO_PCI_INTMSK, chip->core->pci_irqmask | PCI_INT_AUDINT);
+
+ /* start dma */
+
+ /* Enables Risc Processor */
+ cx_set(MO_DEV_CNTRL2, (1 << 5));
+ /* audio downstream FIFO and RISC enable */
+ cx_set(MO_AUD_DMACNTRL, 0x11);
+
+ if (debug)
+ cx88_sram_channel_dump(chip->core, audio_ch);
+
+ return 0;
+}
+
+/*
+ * BOARD Specific: Resets audio DMA
+ */
+static int _cx88_stop_audio_dma(struct cx88_audio_dev *chip)
+{
+ struct cx88_core *core = chip->core;
+
+ dprintk(1, "Stopping audio DMA\n");
+
+ /* stop dma */
+ cx_clear(MO_AUD_DMACNTRL, 0x11);
+
+ /* disable irqs */
+ cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT);
+ cx_clear(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC |
+ AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1);
+
+ if (debug)
+ cx88_sram_channel_dump(chip->core,
+ &cx88_sram_channels[SRAM_CH25]);
+
+ return 0;
+}
+
+#define MAX_IRQ_LOOP 50
+
+/*
+ * BOARD Specific: IRQ dma bits
+ */
+static const char *cx88_aud_irqs[32] = {
+ "dn_risci1", "up_risci1", "rds_dn_risc1", /* 0-2 */
+ NULL, /* reserved */
+ "dn_risci2", "up_risci2", "rds_dn_risc2", /* 4-6 */
+ NULL, /* reserved */
+ "dnf_of", "upf_uf", "rds_dnf_uf", /* 8-10 */
+ NULL, /* reserved */
+ "dn_sync", "up_sync", "rds_dn_sync", /* 12-14 */
+ NULL, /* reserved */
+ "opc_err", "par_err", "rip_err", /* 16-18 */
+ "pci_abort", "ber_irq", "mchg_irq" /* 19-21 */
+};
+
+/*
+ * BOARD Specific: Threats IRQ audio specific calls
+ */
+static void cx8801_aud_irq(struct cx88_audio_dev *chip)
+{
+ struct cx88_core *core = chip->core;
+ u32 status, mask;
+
+ status = cx_read(MO_AUD_INTSTAT);
+ mask = cx_read(MO_AUD_INTMSK);
+ if (0 == (status & mask))
+ return;
+ cx_write(MO_AUD_INTSTAT, status);
+ if (debug > 1 || (status & mask & ~0xff))
+ cx88_print_irqbits("irq aud",
+ cx88_aud_irqs, ARRAY_SIZE(cx88_aud_irqs),
+ status, mask);
+ /* risc op code error */
+ if (status & AUD_INT_OPC_ERR) {
+ pr_warn("Audio risc op code error\n");
+ cx_clear(MO_AUD_DMACNTRL, 0x11);
+ cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH25]);
+ }
+ if (status & AUD_INT_DN_SYNC) {
+ dprintk(1, "Downstream sync error\n");
+ cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET);
+ return;
+ }
+ /* risc1 downstream */
+ if (status & AUD_INT_DN_RISCI1) {
+ atomic_set(&chip->count, cx_read(MO_AUDD_GPCNT));
+ snd_pcm_period_elapsed(chip->substream);
+ }
+ /* FIXME: Any other status should deserve a special handling? */
+}
+
+/*
+ * BOARD Specific: Handles IRQ calls
+ */
+static irqreturn_t cx8801_irq(int irq, void *dev_id)
+{
+ struct cx88_audio_dev *chip = dev_id;
+ struct cx88_core *core = chip->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_AUDINT);
+ if (status == 0)
+ goto out;
+ dprintk(3, "cx8801_irq loop %d/%d, status %x\n",
+ loop, MAX_IRQ_LOOP, status);
+ handled = 1;
+ cx_write(MO_PCI_INTSTAT, status);
+
+ if (status & core->pci_irqmask)
+ cx88_core_irq(core, status);
+ if (status & PCI_INT_AUDINT)
+ cx8801_aud_irq(chip);
+ }
+
+ if (loop == MAX_IRQ_LOOP) {
+ pr_err("IRQ loop detected, disabling interrupts\n");
+ cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT);
+ }
+
+ out:
+ return IRQ_RETVAL(handled);
+}
+
+static int cx88_alsa_dma_init(struct cx88_audio_dev *chip,
+ unsigned long nr_pages)
+{
+ struct cx88_audio_buffer *buf = chip->buf;
+ struct page *pg;
+ int i;
+
+ buf->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT);
+ if (!buf->vaddr) {
+ dprintk(1, "vmalloc_32(%lu pages) failed\n", nr_pages);
+ return -ENOMEM;
+ }
+
+ dprintk(1, "vmalloc is at addr %p, size=%lu\n",
+ buf->vaddr, nr_pages << PAGE_SHIFT);
+
+ memset(buf->vaddr, 0, nr_pages << PAGE_SHIFT);
+ buf->nr_pages = nr_pages;
+
+ buf->sglist = vzalloc(array_size(sizeof(*buf->sglist), buf->nr_pages));
+ if (!buf->sglist)
+ goto vzalloc_err;
+
+ sg_init_table(buf->sglist, buf->nr_pages);
+ for (i = 0; i < buf->nr_pages; i++) {
+ pg = vmalloc_to_page(buf->vaddr + i * PAGE_SIZE);
+ if (!pg)
+ goto vmalloc_to_page_err;
+ sg_set_page(&buf->sglist[i], pg, PAGE_SIZE, 0);
+ }
+ return 0;
+
+vmalloc_to_page_err:
+ vfree(buf->sglist);
+ buf->sglist = NULL;
+vzalloc_err:
+ vfree(buf->vaddr);
+ buf->vaddr = NULL;
+ return -ENOMEM;
+}
+
+static int cx88_alsa_dma_map(struct cx88_audio_dev *dev)
+{
+ struct cx88_audio_buffer *buf = dev->buf;
+
+ buf->sglen = dma_map_sg(&dev->pci->dev, buf->sglist,
+ buf->nr_pages, DMA_FROM_DEVICE);
+
+ if (buf->sglen == 0) {
+ pr_warn("%s: cx88_alsa_map_sg failed\n", __func__);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int cx88_alsa_dma_unmap(struct cx88_audio_dev *dev)
+{
+ struct cx88_audio_buffer *buf = dev->buf;
+
+ if (!buf->sglen)
+ return 0;
+
+ dma_unmap_sg(&dev->pci->dev, buf->sglist, buf->nr_pages,
+ DMA_FROM_DEVICE);
+ buf->sglen = 0;
+ return 0;
+}
+
+static int cx88_alsa_dma_free(struct cx88_audio_buffer *buf)
+{
+ vfree(buf->sglist);
+ buf->sglist = NULL;
+ vfree(buf->vaddr);
+ buf->vaddr = NULL;
+ return 0;
+}
+
+static int dsp_buffer_free(struct cx88_audio_dev *chip)
+{
+ struct cx88_riscmem *risc = &chip->buf->risc;
+
+ WARN_ON(!chip->dma_size);
+
+ dprintk(2, "Freeing buffer\n");
+ cx88_alsa_dma_unmap(chip);
+ cx88_alsa_dma_free(chip->buf);
+ if (risc->cpu)
+ dma_free_coherent(&chip->pci->dev, risc->size, risc->cpu,
+ risc->dma);
+ kfree(chip->buf);
+
+ chip->buf = NULL;
+
+ return 0;
+}
+
+/*
+ * ALSA PCM Interface
+ */
+
+/*
+ * Digital hardware definition
+ */
+#define DEFAULT_FIFO_SIZE 4096
+static const struct snd_pcm_hardware snd_cx88_digital_hw = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ /*
+ * Analog audio output will be full of clicks and pops if there
+ * are not exactly four lines in the SRAM FIFO buffer.
+ */
+ .period_bytes_min = DEFAULT_FIFO_SIZE / 4,
+ .period_bytes_max = DEFAULT_FIFO_SIZE / 4,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .buffer_bytes_max = (1024 * 1024),
+};
+
+/*
+ * audio pcm capture open callback
+ */
+static int snd_cx88_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ if (!chip) {
+ pr_err("BUG: cx88 can't find device struct. Can't proceed with open\n");
+ return -ENODEV;
+ }
+
+ err = snd_pcm_hw_constraint_pow2(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0)
+ goto _error;
+
+ chip->substream = substream;
+
+ runtime->hw = snd_cx88_digital_hw;
+
+ if (cx88_sram_channels[SRAM_CH25].fifo_size != DEFAULT_FIFO_SIZE) {
+ unsigned int bpl = cx88_sram_channels[SRAM_CH25].fifo_size / 4;
+
+ bpl &= ~7; /* must be multiple of 8 */
+ runtime->hw.period_bytes_min = bpl;
+ runtime->hw.period_bytes_max = bpl;
+ }
+
+ return 0;
+_error:
+ dprintk(1, "Error opening PCM!\n");
+ return err;
+}
+
+/*
+ * audio close callback
+ */
+static int snd_cx88_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/*
+ * hw_params callback
+ */
+static int snd_cx88_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream);
+
+ struct cx88_audio_buffer *buf;
+ int ret;
+
+ if (substream->runtime->dma_area) {
+ dsp_buffer_free(chip);
+ substream->runtime->dma_area = NULL;
+ }
+
+ chip->period_size = params_period_bytes(hw_params);
+ chip->num_periods = params_periods(hw_params);
+ chip->dma_size = chip->period_size * params_periods(hw_params);
+
+ WARN_ON(!chip->dma_size);
+ WARN_ON(chip->num_periods & (chip->num_periods - 1));
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ chip->buf = buf;
+ buf->bpl = chip->period_size;
+
+ ret = cx88_alsa_dma_init(chip,
+ (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT));
+ if (ret < 0)
+ goto error;
+
+ ret = cx88_alsa_dma_map(chip);
+ if (ret < 0)
+ goto error;
+
+ ret = cx88_risc_databuffer(chip->pci, &buf->risc, buf->sglist,
+ chip->period_size, chip->num_periods, 1);
+ if (ret < 0)
+ goto error;
+
+ /* Loop back to start of program */
+ buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC);
+ buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+
+ substream->runtime->dma_area = chip->buf->vaddr;
+ substream->runtime->dma_bytes = chip->dma_size;
+ substream->runtime->dma_addr = 0;
+ return 0;
+
+error:
+ kfree(buf);
+ return ret;
+}
+
+/*
+ * hw free callback
+ */
+static int snd_cx88_hw_free(struct snd_pcm_substream *substream)
+{
+ struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream);
+
+ if (substream->runtime->dma_area) {
+ dsp_buffer_free(chip);
+ substream->runtime->dma_area = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * prepare callback
+ */
+static int snd_cx88_prepare(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/*
+ * trigger callback
+ */
+static int snd_cx88_card_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream);
+ int err;
+
+ /* Local interrupts are already disabled by ALSA */
+ spin_lock(&chip->reg_lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ err = _cx88_start_audio_dma(chip);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ err = _cx88_stop_audio_dma(chip);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&chip->reg_lock);
+
+ return err;
+}
+
+/*
+ * pointer callback
+ */
+static snd_pcm_uframes_t snd_cx88_pointer(struct snd_pcm_substream *substream)
+{
+ struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u16 count;
+
+ count = atomic_read(&chip->count);
+
+// dprintk(2, "%s - count %d (+%u), period %d, frame %lu\n", __func__,
+// count, new, count & (runtime->periods-1),
+// runtime->period_size * (count & (runtime->periods-1)));
+ return runtime->period_size * (count & (runtime->periods - 1));
+}
+
+/*
+ * page callback (needed for mmap)
+ */
+static struct page *snd_cx88_page(struct snd_pcm_substream *substream,
+ unsigned long offset)
+{
+ void *pageptr = substream->runtime->dma_area + offset;
+
+ return vmalloc_to_page(pageptr);
+}
+
+/*
+ * operators
+ */
+static const struct snd_pcm_ops snd_cx88_pcm_ops = {
+ .open = snd_cx88_pcm_open,
+ .close = snd_cx88_close,
+ .hw_params = snd_cx88_hw_params,
+ .hw_free = snd_cx88_hw_free,
+ .prepare = snd_cx88_prepare,
+ .trigger = snd_cx88_card_trigger,
+ .pointer = snd_cx88_pointer,
+ .page = snd_cx88_page,
+};
+
+/*
+ * create a PCM device
+ */
+static int snd_cx88_pcm(struct cx88_audio_dev *chip, int device,
+ const char *name)
+{
+ int err;
+ struct snd_pcm *pcm;
+
+ err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm);
+ if (err < 0)
+ return err;
+ pcm->private_data = chip;
+ strscpy(pcm->name, name, sizeof(pcm->name));
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cx88_pcm_ops);
+
+ return 0;
+}
+
+/*
+ * CONTROL INTERFACE
+ */
+static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *info)
+{
+ info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ info->count = 2;
+ info->value.integer.min = 0;
+ info->value.integer.max = 0x3f;
+
+ return 0;
+}
+
+static int snd_cx88_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
+ struct cx88_core *core = chip->core;
+ int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f),
+ bal = cx_read(AUD_BAL_CTL);
+
+ value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol;
+ vol -= (bal & 0x3f);
+ value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol;
+
+ return 0;
+}
+
+static void snd_cx88_wm8775_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
+ struct cx88_core *core = chip->core;
+ u16 left = value->value.integer.value[0];
+ u16 right = value->value.integer.value[1];
+ int v, b;
+
+ /* Pass volume & balance onto any WM8775 */
+ if (left >= right) {
+ v = left << 10;
+ b = left ? (0x8000 * right) / left : 0x8000;
+ } else {
+ v = right << 10;
+ b = right ? 0xffff - (0x8000 * left) / right : 0x8000;
+ }
+ wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v);
+ wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b);
+}
+
+/* OK - TODO: test it */
+static int snd_cx88_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
+ struct cx88_core *core = chip->core;
+ int left, right, v, b;
+ int changed = 0;
+ u32 old;
+
+ if (core->sd_wm8775)
+ snd_cx88_wm8775_volume_put(kcontrol, value);
+
+ left = value->value.integer.value[0] & 0x3f;
+ right = value->value.integer.value[1] & 0x3f;
+ b = right - left;
+ if (b < 0) {
+ v = 0x3f - left;
+ b = (-b) | 0x40;
+ } else {
+ v = 0x3f - right;
+ }
+ /* Do we really know this will always be called with IRQs on? */
+ spin_lock_irq(&chip->reg_lock);
+ old = cx_read(AUD_VOL_CTL);
+ if (v != (old & 0x3f)) {
+ cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v);
+ changed = 1;
+ }
+ if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) {
+ cx_write(AUD_BAL_CTL, b);
+ changed = 1;
+ }
+ spin_unlock_irq(&chip->reg_lock);
+
+ return changed;
+}
+
+static const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0);
+
+static const struct snd_kcontrol_new snd_cx88_volume = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .name = "Analog-TV Volume",
+ .info = snd_cx88_volume_info,
+ .get = snd_cx88_volume_get,
+ .put = snd_cx88_volume_put,
+ .tlv.p = snd_cx88_db_scale,
+};
+
+static int snd_cx88_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
+ struct cx88_core *core = chip->core;
+ u32 bit = kcontrol->private_value;
+
+ value->value.integer.value[0] = !(cx_read(AUD_VOL_CTL) & bit);
+ return 0;
+}
+
+static int snd_cx88_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
+ struct cx88_core *core = chip->core;
+ u32 bit = kcontrol->private_value;
+ int ret = 0;
+ u32 vol;
+
+ spin_lock_irq(&chip->reg_lock);
+ vol = cx_read(AUD_VOL_CTL);
+ if (value->value.integer.value[0] != !(vol & bit)) {
+ vol ^= bit;
+ cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, vol);
+ /* Pass mute onto any WM8775 */
+ if (core->sd_wm8775 && ((1 << 6) == bit))
+ wm8775_s_ctrl(core,
+ V4L2_CID_AUDIO_MUTE, 0 != (vol & bit));
+ ret = 1;
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ return ret;
+}
+
+static const struct snd_kcontrol_new snd_cx88_dac_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Audio-Out Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = snd_cx88_switch_get,
+ .put = snd_cx88_switch_put,
+ .private_value = (1 << 8),
+};
+
+static const struct snd_kcontrol_new snd_cx88_source_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog-TV Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = snd_cx88_switch_get,
+ .put = snd_cx88_switch_put,
+ .private_value = (1 << 6),
+};
+
+static int snd_cx88_alc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
+ struct cx88_core *core = chip->core;
+ s32 val;
+
+ val = wm8775_g_ctrl(core, V4L2_CID_AUDIO_LOUDNESS);
+ value->value.integer.value[0] = val ? 1 : 0;
+ return 0;
+}
+
+static int snd_cx88_alc_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
+ struct cx88_core *core = chip->core;
+
+ wm8775_s_ctrl(core, V4L2_CID_AUDIO_LOUDNESS,
+ value->value.integer.value[0] != 0);
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_cx88_alc_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line-In ALC Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = snd_cx88_alc_get,
+ .put = snd_cx88_alc_put,
+};
+
+/*
+ * Basic Flow for Sound Devices
+ */
+
+/*
+ * PCI ID Table - 14f1:8801 and 14f1:8811 means function 1: Audio
+ * Only boards with eeprom and byte 1 at eeprom=1 have it
+ */
+
+static const struct pci_device_id cx88_audio_pci_tbl[] = {
+ {0x14f1, 0x8801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {0x14f1, 0x8811, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {0, }
+};
+MODULE_DEVICE_TABLE(pci, cx88_audio_pci_tbl);
+
+/*
+ * Chip-specific destructor
+ */
+
+static int snd_cx88_free(struct cx88_audio_dev *chip)
+{
+ if (chip->irq >= 0)
+ free_irq(chip->irq, chip);
+
+ cx88_core_put(chip->core, chip->pci);
+
+ pci_disable_device(chip->pci);
+ return 0;
+}
+
+/*
+ * Component Destructor
+ */
+static void snd_cx88_dev_free(struct snd_card *card)
+{
+ struct cx88_audio_dev *chip = card->private_data;
+
+ snd_cx88_free(chip);
+}
+
+/*
+ * Alsa Constructor - Component probe
+ */
+
+static int devno;
+static int snd_cx88_create(struct snd_card *card, struct pci_dev *pci,
+ struct cx88_audio_dev **rchip,
+ struct cx88_core **core_ptr)
+{
+ struct cx88_audio_dev *chip;
+ struct cx88_core *core;
+ int err;
+ unsigned char pci_lat;
+
+ *rchip = NULL;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ pci_set_master(pci);
+
+ chip = card->private_data;
+
+ core = cx88_core_get(pci);
+ if (!core) {
+ err = -EINVAL;
+ return err;
+ }
+
+ err = dma_set_mask(&pci->dev, DMA_BIT_MASK(32));
+ if (err) {
+ dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n", core->name);
+ cx88_core_put(core, pci);
+ return err;
+ }
+
+ /* pci init */
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+ spin_lock_init(&chip->reg_lock);
+
+ chip->core = core;
+
+ /* get irq */
+ err = request_irq(chip->pci->irq, cx8801_irq,
+ IRQF_SHARED, chip->core->name, chip);
+ if (err < 0) {
+ dprintk(0, "%s: can't get IRQ %d\n",
+ chip->core->name, chip->pci->irq);
+ return err;
+ }
+
+ /* print pci info */
+ pci_read_config_byte(pci, PCI_LATENCY_TIMER, &pci_lat);
+
+ dprintk(1,
+ "ALSA %s/%i: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n",
+ core->name, devno,
+ pci_name(pci), pci->revision, pci->irq,
+ pci_lat, (unsigned long long)pci_resource_start(pci, 0));
+
+ chip->irq = pci->irq;
+ synchronize_irq(chip->irq);
+
+ *rchip = chip;
+ *core_ptr = core;
+
+ return 0;
+}
+
+static int cx88_audio_initdev(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ struct snd_card *card;
+ struct cx88_audio_dev *chip;
+ struct cx88_core *core = NULL;
+ int err;
+
+ if (devno >= SNDRV_CARDS)
+ return (-ENODEV);
+
+ if (!enable[devno]) {
+ ++devno;
+ return (-ENOENT);
+ }
+
+ err = snd_card_new(&pci->dev, index[devno], id[devno], THIS_MODULE,
+ sizeof(struct cx88_audio_dev), &card);
+ if (err < 0)
+ return err;
+
+ card->private_free = snd_cx88_dev_free;
+
+ err = snd_cx88_create(card, pci, &chip, &core);
+ if (err < 0)
+ goto error;
+
+ err = snd_cx88_pcm(chip, 0, "CX88 Digital");
+ if (err < 0)
+ goto error;
+
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_volume, chip));
+ if (err < 0)
+ goto error;
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_dac_switch, chip));
+ if (err < 0)
+ goto error;
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_source_switch, chip));
+ if (err < 0)
+ goto error;
+
+ /* If there's a wm8775 then add a Line-In ALC switch */
+ if (core->sd_wm8775) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_alc_switch, chip));
+ if (err < 0)
+ goto error;
+ }
+
+ strscpy(card->driver, "CX88x", sizeof(card->driver));
+ sprintf(card->shortname, "Conexant CX%x", pci->device);
+ sprintf(card->longname, "%s at %#llx",
+ card->shortname,
+ (unsigned long long)pci_resource_start(pci, 0));
+ strscpy(card->mixername, "CX88", sizeof(card->mixername));
+
+ dprintk(0, "%s/%i: ALSA support for cx2388x boards\n",
+ card->driver, devno);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto error;
+ pci_set_drvdata(pci, card);
+
+ devno++;
+ return 0;
+
+error:
+ snd_card_free(card);
+ return err;
+}
+
+/*
+ * ALSA destructor
+ */
+static void cx88_audio_finidev(struct pci_dev *pci)
+{
+ struct snd_card *card = pci_get_drvdata(pci);
+
+ snd_card_free(card);
+
+ devno--;
+}
+
+/*
+ * PCI driver definition
+ */
+
+static struct pci_driver cx88_audio_pci_driver = {
+ .name = "cx88_audio",
+ .id_table = cx88_audio_pci_tbl,
+ .probe = cx88_audio_initdev,
+ .remove = cx88_audio_finidev,
+};
+
+module_pci_driver(cx88_audio_pci_driver);
diff --git a/drivers/media/pci/cx88/cx88-blackbird.c b/drivers/media/pci/cx88/cx88-blackbird.c
new file mode 100644
index 000000000..c1b41a928
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-blackbird.c
@@ -0,0 +1,1253 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for a cx23416 mpeg encoder via cx2388x host port.
+ * "blackbird" reference design.
+ *
+ * (c) 2004 Jelle Foks <jelle@foks.us>
+ * (c) 2004 Gerd Knorr <kraxel@bytesex.org>
+ *
+ * (c) 2005-2006 Mauro Carvalho Chehab <mchehab@kernel.org>
+ * - video_ioctl2 conversion
+ *
+ * Includes parts from the ivtv driver <http://sourceforge.net/projects/ivtv/>
+ */
+
+#include "cx88.h"
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/drv-intf/cx2341x.h>
+
+MODULE_DESCRIPTION("driver for cx2388x/cx23416 based mpeg encoder cards");
+MODULE_AUTHOR("Jelle Foks <jelle@foks.us>, Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(CX88_VERSION);
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages [blackbird]");
+
+#define dprintk(level, fmt, arg...) do { \
+ if (debug + 1 > level) \
+ printk(KERN_DEBUG pr_fmt("%s: blackbird:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+/* ------------------------------------------------------------------ */
+
+#define BLACKBIRD_FIRM_IMAGE_SIZE 376836
+
+/* defines below are from ivtv-driver.h */
+
+#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF
+
+/* Firmware API commands */
+#define IVTV_API_STD_TIMEOUT 500
+
+enum blackbird_capture_type {
+ BLACKBIRD_MPEG_CAPTURE,
+ BLACKBIRD_RAW_CAPTURE,
+ BLACKBIRD_RAW_PASSTHRU_CAPTURE
+};
+
+enum blackbird_capture_bits {
+ BLACKBIRD_RAW_BITS_NONE = 0x00,
+ BLACKBIRD_RAW_BITS_YUV_CAPTURE = 0x01,
+ BLACKBIRD_RAW_BITS_PCM_CAPTURE = 0x02,
+ BLACKBIRD_RAW_BITS_VBI_CAPTURE = 0x04,
+ BLACKBIRD_RAW_BITS_PASSTHRU_CAPTURE = 0x08,
+ BLACKBIRD_RAW_BITS_TO_HOST_CAPTURE = 0x10
+};
+
+enum blackbird_capture_end {
+ BLACKBIRD_END_AT_GOP, /* stop at the end of gop, generate irq */
+ BLACKBIRD_END_NOW, /* stop immediately, no irq */
+};
+
+enum blackbird_framerate {
+ BLACKBIRD_FRAMERATE_NTSC_30, /* NTSC: 30fps */
+ BLACKBIRD_FRAMERATE_PAL_25 /* PAL: 25fps */
+};
+
+enum blackbird_stream_port {
+ BLACKBIRD_OUTPUT_PORT_MEMORY,
+ BLACKBIRD_OUTPUT_PORT_STREAMING,
+ BLACKBIRD_OUTPUT_PORT_SERIAL
+};
+
+enum blackbird_data_xfer_status {
+ BLACKBIRD_MORE_BUFFERS_FOLLOW,
+ BLACKBIRD_LAST_BUFFER,
+};
+
+enum blackbird_picture_mask {
+ BLACKBIRD_PICTURE_MASK_NONE,
+ BLACKBIRD_PICTURE_MASK_I_FRAMES,
+ BLACKBIRD_PICTURE_MASK_I_P_FRAMES = 0x3,
+ BLACKBIRD_PICTURE_MASK_ALL_FRAMES = 0x7,
+};
+
+enum blackbird_vbi_mode_bits {
+ BLACKBIRD_VBI_BITS_SLICED,
+ BLACKBIRD_VBI_BITS_RAW,
+};
+
+enum blackbird_vbi_insertion_bits {
+ BLACKBIRD_VBI_BITS_INSERT_IN_XTENSION_USR_DATA,
+ BLACKBIRD_VBI_BITS_INSERT_IN_PRIVATE_PACKETS = 0x1 << 1,
+ BLACKBIRD_VBI_BITS_SEPARATE_STREAM = 0x2 << 1,
+ BLACKBIRD_VBI_BITS_SEPARATE_STREAM_USR_DATA = 0x4 << 1,
+ BLACKBIRD_VBI_BITS_SEPARATE_STREAM_PRV_DATA = 0x5 << 1,
+};
+
+enum blackbird_dma_unit {
+ BLACKBIRD_DMA_BYTES,
+ BLACKBIRD_DMA_FRAMES,
+};
+
+enum blackbird_dma_transfer_status_bits {
+ BLACKBIRD_DMA_TRANSFER_BITS_DONE = 0x01,
+ BLACKBIRD_DMA_TRANSFER_BITS_ERROR = 0x04,
+ BLACKBIRD_DMA_TRANSFER_BITS_LL_ERROR = 0x10,
+};
+
+enum blackbird_pause {
+ BLACKBIRD_PAUSE_ENCODING,
+ BLACKBIRD_RESUME_ENCODING,
+};
+
+enum blackbird_copyright {
+ BLACKBIRD_COPYRIGHT_OFF,
+ BLACKBIRD_COPYRIGHT_ON,
+};
+
+enum blackbird_notification_type {
+ BLACKBIRD_NOTIFICATION_REFRESH,
+};
+
+enum blackbird_notification_status {
+ BLACKBIRD_NOTIFICATION_OFF,
+ BLACKBIRD_NOTIFICATION_ON,
+};
+
+enum blackbird_notification_mailbox {
+ BLACKBIRD_NOTIFICATION_NO_MAILBOX = -1,
+};
+
+enum blackbird_field1_lines {
+ BLACKBIRD_FIELD1_SAA7114 = 0x00EF, /* 239 */
+ BLACKBIRD_FIELD1_SAA7115 = 0x00F0, /* 240 */
+ BLACKBIRD_FIELD1_MICRONAS = 0x0105, /* 261 */
+};
+
+enum blackbird_field2_lines {
+ BLACKBIRD_FIELD2_SAA7114 = 0x00EF, /* 239 */
+ BLACKBIRD_FIELD2_SAA7115 = 0x00F0, /* 240 */
+ BLACKBIRD_FIELD2_MICRONAS = 0x0106, /* 262 */
+};
+
+enum blackbird_custom_data_type {
+ BLACKBIRD_CUSTOM_EXTENSION_USR_DATA,
+ BLACKBIRD_CUSTOM_PRIVATE_PACKET,
+};
+
+enum blackbird_mute {
+ BLACKBIRD_UNMUTE,
+ BLACKBIRD_MUTE,
+};
+
+enum blackbird_mute_video_mask {
+ BLACKBIRD_MUTE_VIDEO_V_MASK = 0x0000FF00,
+ BLACKBIRD_MUTE_VIDEO_U_MASK = 0x00FF0000,
+ BLACKBIRD_MUTE_VIDEO_Y_MASK = 0xFF000000,
+};
+
+enum blackbird_mute_video_shift {
+ BLACKBIRD_MUTE_VIDEO_V_SHIFT = 8,
+ BLACKBIRD_MUTE_VIDEO_U_SHIFT = 16,
+ BLACKBIRD_MUTE_VIDEO_Y_SHIFT = 24,
+};
+
+/* Registers */
+#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8 /*| IVTV_REG_OFFSET*/)
+#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC /*| IVTV_REG_OFFSET*/)
+#define IVTV_REG_SPU (0x9050 /*| IVTV_REG_OFFSET*/)
+#define IVTV_REG_HW_BLOCKS (0x9054 /*| IVTV_REG_OFFSET*/)
+#define IVTV_REG_VPU (0x9058 /*| IVTV_REG_OFFSET*/)
+#define IVTV_REG_APU (0xA064 /*| IVTV_REG_OFFSET*/)
+
+/* ------------------------------------------------------------------ */
+
+static void host_setup(struct cx88_core *core)
+{
+ /* toggle reset of the host */
+ cx_write(MO_GPHST_SOFT_RST, 1);
+ udelay(100);
+ cx_write(MO_GPHST_SOFT_RST, 0);
+ udelay(100);
+
+ /* host port setup */
+ cx_write(MO_GPHST_WSC, 0x44444444U);
+ cx_write(MO_GPHST_XFR, 0);
+ cx_write(MO_GPHST_WDTH, 15);
+ cx_write(MO_GPHST_HDSHK, 0);
+ cx_write(MO_GPHST_MUX16, 0x44448888U);
+ cx_write(MO_GPHST_MODE, 0);
+}
+
+/* ------------------------------------------------------------------ */
+
+#define P1_MDATA0 0x390000
+#define P1_MDATA1 0x390001
+#define P1_MDATA2 0x390002
+#define P1_MDATA3 0x390003
+#define P1_MADDR2 0x390004
+#define P1_MADDR1 0x390005
+#define P1_MADDR0 0x390006
+#define P1_RDATA0 0x390008
+#define P1_RDATA1 0x390009
+#define P1_RDATA2 0x39000A
+#define P1_RDATA3 0x39000B
+#define P1_RADDR0 0x39000C
+#define P1_RADDR1 0x39000D
+#define P1_RRDWR 0x39000E
+
+static int wait_ready_gpio0_bit1(struct cx88_core *core, u32 state)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(1);
+ u32 gpio0, need;
+
+ need = state ? 2 : 0;
+ for (;;) {
+ gpio0 = cx_read(MO_GP0_IO) & 2;
+ if (need == gpio0)
+ return 0;
+ if (time_after(jiffies, timeout))
+ return -1;
+ udelay(1);
+ }
+}
+
+static int memory_write(struct cx88_core *core, u32 address, u32 value)
+{
+ /* Warning: address is dword address (4 bytes) */
+ cx_writeb(P1_MDATA0, (unsigned int)value);
+ cx_writeb(P1_MDATA1, (unsigned int)(value >> 8));
+ cx_writeb(P1_MDATA2, (unsigned int)(value >> 16));
+ cx_writeb(P1_MDATA3, (unsigned int)(value >> 24));
+ cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) | 0x40);
+ cx_writeb(P1_MADDR1, (unsigned int)(address >> 8));
+ cx_writeb(P1_MADDR0, (unsigned int)address);
+ cx_read(P1_MDATA0);
+ cx_read(P1_MADDR0);
+
+ return wait_ready_gpio0_bit1(core, 1);
+}
+
+static int memory_read(struct cx88_core *core, u32 address, u32 *value)
+{
+ int retval;
+ u32 val;
+
+ /* Warning: address is dword address (4 bytes) */
+ cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) & ~0xC0);
+ cx_writeb(P1_MADDR1, (unsigned int)(address >> 8));
+ cx_writeb(P1_MADDR0, (unsigned int)address);
+ cx_read(P1_MADDR0);
+
+ retval = wait_ready_gpio0_bit1(core, 1);
+
+ cx_writeb(P1_MDATA3, 0);
+ val = (unsigned char)cx_read(P1_MDATA3) << 24;
+ cx_writeb(P1_MDATA2, 0);
+ val |= (unsigned char)cx_read(P1_MDATA2) << 16;
+ cx_writeb(P1_MDATA1, 0);
+ val |= (unsigned char)cx_read(P1_MDATA1) << 8;
+ cx_writeb(P1_MDATA0, 0);
+ val |= (unsigned char)cx_read(P1_MDATA0);
+
+ *value = val;
+ return retval;
+}
+
+static int register_write(struct cx88_core *core, u32 address, u32 value)
+{
+ cx_writeb(P1_RDATA0, (unsigned int)value);
+ cx_writeb(P1_RDATA1, (unsigned int)(value >> 8));
+ cx_writeb(P1_RDATA2, (unsigned int)(value >> 16));
+ cx_writeb(P1_RDATA3, (unsigned int)(value >> 24));
+ cx_writeb(P1_RADDR0, (unsigned int)address);
+ cx_writeb(P1_RADDR1, (unsigned int)(address >> 8));
+ cx_writeb(P1_RRDWR, 1);
+ cx_read(P1_RDATA0);
+ cx_read(P1_RADDR0);
+
+ return wait_ready_gpio0_bit1(core, 1);
+}
+
+static int register_read(struct cx88_core *core, u32 address, u32 *value)
+{
+ int retval;
+ u32 val;
+
+ cx_writeb(P1_RADDR0, (unsigned int)address);
+ cx_writeb(P1_RADDR1, (unsigned int)(address >> 8));
+ cx_writeb(P1_RRDWR, 0);
+ cx_read(P1_RADDR0);
+
+ retval = wait_ready_gpio0_bit1(core, 1);
+ val = (unsigned char)cx_read(P1_RDATA0);
+ val |= (unsigned char)cx_read(P1_RDATA1) << 8;
+ val |= (unsigned char)cx_read(P1_RDATA2) << 16;
+ val |= (unsigned char)cx_read(P1_RDATA3) << 24;
+
+ *value = val;
+ return retval;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int blackbird_mbox_func(void *priv, u32 command, int in,
+ int out, u32 data[CX2341X_MBOX_MAX_DATA])
+{
+ struct cx8802_dev *dev = priv;
+ unsigned long timeout;
+ u32 value, flag, retval;
+ int i;
+
+ dprintk(1, "%s: 0x%X\n", __func__, command);
+
+ /*
+ * this may not be 100% safe if we can't read any memory location
+ * without side effects
+ */
+ memory_read(dev->core, dev->mailbox - 4, &value);
+ if (value != 0x12345678) {
+ dprintk(0,
+ "Firmware and/or mailbox pointer not initialized or corrupted\n");
+ return -EIO;
+ }
+
+ memory_read(dev->core, dev->mailbox, &flag);
+ if (flag) {
+ dprintk(0, "ERROR: Mailbox appears to be in use (%x)\n", flag);
+ return -EIO;
+ }
+
+ flag |= 1; /* tell 'em we're working on it */
+ memory_write(dev->core, dev->mailbox, flag);
+
+ /* write command + args + fill remaining with zeros */
+ memory_write(dev->core, dev->mailbox + 1, command); /* command code */
+ /* timeout */
+ memory_write(dev->core, dev->mailbox + 3, IVTV_API_STD_TIMEOUT);
+ for (i = 0; i < in; i++) {
+ memory_write(dev->core, dev->mailbox + 4 + i, data[i]);
+ dprintk(1, "API Input %d = %d\n", i, data[i]);
+ }
+ for (; i < CX2341X_MBOX_MAX_DATA; i++)
+ memory_write(dev->core, dev->mailbox + 4 + i, 0);
+
+ flag |= 3; /* tell 'em we're done writing */
+ memory_write(dev->core, dev->mailbox, flag);
+
+ /* wait for firmware to handle the API command */
+ timeout = jiffies + msecs_to_jiffies(1000);
+ for (;;) {
+ memory_read(dev->core, dev->mailbox, &flag);
+ if (0 != (flag & 4))
+ break;
+ if (time_after(jiffies, timeout)) {
+ dprintk(0, "ERROR: API Mailbox timeout %x\n", command);
+ return -EIO;
+ }
+ udelay(10);
+ }
+
+ /* read output values */
+ for (i = 0; i < out; i++) {
+ memory_read(dev->core, dev->mailbox + 4 + i, data + i);
+ dprintk(1, "API Output %d = %d\n", i, data[i]);
+ }
+
+ memory_read(dev->core, dev->mailbox + 2, &retval);
+ dprintk(1, "API result = %d\n", retval);
+
+ flag = 0;
+ memory_write(dev->core, dev->mailbox, flag);
+ return retval;
+}
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * We don't need to call the API often, so using just one mailbox
+ * will probably suffice
+ */
+static int blackbird_api_cmd(struct cx8802_dev *dev, u32 command,
+ u32 inputcnt, u32 outputcnt, ...)
+{
+ u32 data[CX2341X_MBOX_MAX_DATA];
+ va_list vargs;
+ int i, err;
+
+ va_start(vargs, outputcnt);
+
+ for (i = 0; i < inputcnt; i++)
+ data[i] = va_arg(vargs, int);
+
+ err = blackbird_mbox_func(dev, command, inputcnt, outputcnt, data);
+ for (i = 0; i < outputcnt; i++) {
+ int *vptr = va_arg(vargs, int *);
+ *vptr = data[i];
+ }
+ va_end(vargs);
+ return err;
+}
+
+static int blackbird_find_mailbox(struct cx8802_dev *dev)
+{
+ u32 signature[4] = {0x12345678, 0x34567812, 0x56781234, 0x78123456};
+ int signaturecnt = 0;
+ u32 value;
+ int i;
+
+ for (i = 0; i < BLACKBIRD_FIRM_IMAGE_SIZE; i++) {
+ memory_read(dev->core, i, &value);
+ if (value == signature[signaturecnt])
+ signaturecnt++;
+ else
+ signaturecnt = 0;
+ if (signaturecnt == 4) {
+ dprintk(1, "Mailbox signature found\n");
+ return i + 1;
+ }
+ }
+ dprintk(0, "Mailbox signature values not found!\n");
+ return -EIO;
+}
+
+static int blackbird_load_firmware(struct cx8802_dev *dev)
+{
+ static const unsigned char magic[8] = {
+ 0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa
+ };
+ const struct firmware *firmware;
+ int i, retval = 0;
+ u32 value = 0;
+ u32 checksum = 0;
+ __le32 *dataptr;
+
+ retval = register_write(dev->core, IVTV_REG_VPU, 0xFFFFFFED);
+ retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS,
+ IVTV_CMD_HW_BLOCKS_RST);
+ retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_REFRESH,
+ 0x80000640);
+ retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_PRECHARGE,
+ 0x1A);
+ usleep_range(10000, 20000);
+ retval |= register_write(dev->core, IVTV_REG_APU, 0);
+
+ if (retval < 0)
+ dprintk(0, "Error with register_write\n");
+
+ retval = request_firmware(&firmware, CX2341X_FIRM_ENC_FILENAME,
+ &dev->pci->dev);
+
+ if (retval != 0) {
+ pr_err("Hotplug firmware request failed (%s).\n",
+ CX2341X_FIRM_ENC_FILENAME);
+ pr_err("Please fix your hotplug setup, the board will not work without firmware loaded!\n");
+ return -EIO;
+ }
+
+ if (firmware->size != BLACKBIRD_FIRM_IMAGE_SIZE) {
+ pr_err("Firmware size mismatch (have %zd, expected %d)\n",
+ firmware->size, BLACKBIRD_FIRM_IMAGE_SIZE);
+ release_firmware(firmware);
+ return -EINVAL;
+ }
+
+ if (memcmp(firmware->data, magic, 8) != 0) {
+ pr_err("Firmware magic mismatch, wrong file?\n");
+ release_firmware(firmware);
+ return -EINVAL;
+ }
+
+ /* transfer to the chip */
+ dprintk(1, "Loading firmware ...\n");
+ dataptr = (__le32 *)firmware->data;
+ for (i = 0; i < (firmware->size >> 2); i++) {
+ value = le32_to_cpu(*dataptr);
+ checksum += ~value;
+ memory_write(dev->core, i, value);
+ dataptr++;
+ }
+
+ /* read back to verify with the checksum */
+ for (i--; i >= 0; i--) {
+ memory_read(dev->core, i, &value);
+ checksum -= ~value;
+ }
+ release_firmware(firmware);
+ if (checksum) {
+ pr_err("Firmware load might have failed (checksum mismatch).\n");
+ return -EIO;
+ }
+ dprintk(0, "Firmware upload successful.\n");
+
+ retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS,
+ IVTV_CMD_HW_BLOCKS_RST);
+ retval |= register_read(dev->core, IVTV_REG_SPU, &value);
+ retval |= register_write(dev->core, IVTV_REG_SPU, value & 0xFFFFFFFE);
+ usleep_range(10000, 20000);
+
+ retval |= register_read(dev->core, IVTV_REG_VPU, &value);
+ retval |= register_write(dev->core, IVTV_REG_VPU, value & 0xFFFFFFE8);
+
+ if (retval < 0)
+ dprintk(0, "Error with register_write\n");
+ return 0;
+}
+
+/*
+ * Settings used by the windows tv app for PVR2000:
+ * =================================================================================================================
+ * Profile | Codec | Resolution | CBR/VBR | Video Qlty | V. Bitrate | Frmrate | Audio Codec | A. Bitrate | A. Mode
+ * -----------------------------------------------------------------------------------------------------------------
+ * MPEG-1 | MPEG1 | 352x288PAL | (CBR) | 1000:Optimal | 2000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo
+ * MPEG-2 | MPEG2 | 720x576PAL | VBR | 600 :Good | 4000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo
+ * VCD | MPEG1 | 352x288PAL | (CBR) | 1000:Optimal | 1150 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo
+ * DVD | MPEG2 | 720x576PAL | VBR | 600 :Good | 6000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo
+ * DB* DVD | MPEG2 | 720x576PAL | CBR | 600 :Good | 6000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo
+ * =================================================================================================================
+ * [*] DB: "DirectBurn"
+ */
+
+static void blackbird_codec_settings(struct cx8802_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+
+ /* assign frame size */
+ blackbird_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0,
+ core->height, core->width);
+
+ dev->cxhdl.width = core->width;
+ dev->cxhdl.height = core->height;
+ cx2341x_handler_set_50hz(&dev->cxhdl,
+ dev->core->tvnorm & V4L2_STD_625_50);
+ cx2341x_handler_setup(&dev->cxhdl);
+}
+
+static int blackbird_initialize_codec(struct cx8802_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+ int version;
+ int retval;
+
+ dprintk(1, "Initialize codec\n");
+ retval = blackbird_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */
+ if (retval < 0) {
+ /* ping was not successful, reset and upload firmware */
+ cx_write(MO_SRST_IO, 0); /* SYS_RSTO=0 */
+ cx_write(MO_SRST_IO, 1); /* SYS_RSTO=1 */
+ retval = blackbird_load_firmware(dev);
+ if (retval < 0)
+ return retval;
+
+ retval = blackbird_find_mailbox(dev);
+ if (retval < 0)
+ return -1;
+
+ dev->mailbox = retval;
+
+ /* ping */
+ retval = blackbird_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0);
+ if (retval < 0) {
+ dprintk(0, "ERROR: Firmware ping failed!\n");
+ return -1;
+ }
+
+ retval = blackbird_api_cmd(dev, CX2341X_ENC_GET_VERSION,
+ 0, 1, &version);
+ if (retval < 0) {
+ dprintk(0,
+ "ERROR: Firmware get encoder version failed!\n");
+ return -1;
+ }
+ dprintk(0, "Firmware version is 0x%08x\n", version);
+ }
+
+ cx_write(MO_PINMUX_IO, 0x88); /* 656-8bit IO and enable MPEG parallel IO */
+ cx_clear(MO_INPUT_FORMAT, 0x100); /* chroma subcarrier lock to normal? */
+ cx_write(MO_VBOS_CONTROL, 0x84A00); /* no 656 mode, 8-bit pixels, disable VBI */
+ cx_clear(MO_OUTPUT_FORMAT, 0x0008); /* Normal Y-limits to let the mpeg encoder sync */
+
+ blackbird_codec_settings(dev);
+
+ blackbird_api_cmd(dev, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, 0,
+ BLACKBIRD_FIELD1_SAA7115, BLACKBIRD_FIELD2_SAA7115);
+
+ blackbird_api_cmd(dev, CX2341X_ENC_SET_PLACEHOLDER, 12, 0,
+ BLACKBIRD_CUSTOM_EXTENSION_USR_DATA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+ return 0;
+}
+
+static int blackbird_start_codec(struct cx8802_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+ /* start capturing to the host interface */
+ u32 reg;
+
+ int i;
+ int lastchange = -1;
+ int lastval = 0;
+
+ for (i = 0; (i < 10) && (i < (lastchange + 4)); i++) {
+ reg = cx_read(AUD_STATUS);
+
+ dprintk(1, "AUD_STATUS:%dL: 0x%x\n", i, reg);
+ if ((reg & 0x0F) != lastval) {
+ lastval = reg & 0x0F;
+ lastchange = i;
+ }
+ msleep(100);
+ }
+
+ /* unmute audio source */
+ cx_clear(AUD_VOL_CTL, (1 << 6));
+
+ blackbird_api_cmd(dev, CX2341X_ENC_REFRESH_INPUT, 0, 0);
+
+ /* initialize the video input */
+ blackbird_api_cmd(dev, CX2341X_ENC_INITIALIZE_INPUT, 0, 0);
+
+ cx2341x_handler_set_busy(&dev->cxhdl, 1);
+
+ /* start capturing to the host interface */
+ blackbird_api_cmd(dev, CX2341X_ENC_START_CAPTURE, 2, 0,
+ BLACKBIRD_MPEG_CAPTURE, BLACKBIRD_RAW_BITS_NONE);
+
+ return 0;
+}
+
+static int blackbird_stop_codec(struct cx8802_dev *dev)
+{
+ blackbird_api_cmd(dev, CX2341X_ENC_STOP_CAPTURE, 3, 0,
+ BLACKBIRD_END_NOW,
+ BLACKBIRD_MPEG_CAPTURE,
+ BLACKBIRD_RAW_BITS_NONE);
+
+ cx2341x_handler_set_busy(&dev->cxhdl, 0);
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct cx8802_dev *dev = q->drv_priv;
+
+ *num_planes = 1;
+ dev->ts_packet_size = 188 * 4;
+ dev->ts_packet_count = 32;
+ sizes[0] = dev->ts_packet_size * dev->ts_packet_count;
+ return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8802_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+
+ return cx8802_buf_prepare(vb->vb2_queue, dev, buf);
+}
+
+static void buffer_finish(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8802_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct cx88_riscmem *risc = &buf->risc;
+
+ if (risc->cpu)
+ dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu,
+ risc->dma);
+ memset(risc, 0, sizeof(*risc));
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8802_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+
+ cx8802_buf_queue(dev, buf);
+}
+
+static int start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct cx8802_dev *dev = q->drv_priv;
+ struct cx88_dmaqueue *dmaq = &dev->mpegq;
+ struct cx8802_driver *drv;
+ struct cx88_buffer *buf;
+ unsigned long flags;
+ int err;
+
+ /* Make sure we can acquire the hardware */
+ drv = cx8802_get_driver(dev, CX88_MPEG_BLACKBIRD);
+ if (!drv) {
+ dprintk(1, "%s: blackbird driver is not loaded\n", __func__);
+ err = -ENODEV;
+ goto fail;
+ }
+
+ err = drv->request_acquire(drv);
+ if (err != 0) {
+ dprintk(1, "%s: Unable to acquire hardware, %d\n", __func__,
+ err);
+ goto fail;
+ }
+
+ if (blackbird_initialize_codec(dev) < 0) {
+ drv->request_release(drv);
+ err = -EINVAL;
+ goto fail;
+ }
+
+ err = blackbird_start_codec(dev);
+ if (err == 0) {
+ buf = list_entry(dmaq->active.next, struct cx88_buffer, list);
+ cx8802_start_dma(dev, dmaq, buf);
+ return 0;
+ }
+
+fail:
+ spin_lock_irqsave(&dev->slock, flags);
+ while (!list_empty(&dmaq->active)) {
+ struct cx88_buffer *buf = list_entry(dmaq->active.next,
+ struct cx88_buffer, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+ return err;
+}
+
+static void stop_streaming(struct vb2_queue *q)
+{
+ struct cx8802_dev *dev = q->drv_priv;
+ struct cx88_dmaqueue *dmaq = &dev->mpegq;
+ struct cx8802_driver *drv = NULL;
+ unsigned long flags;
+
+ cx8802_cancel_buffers(dev);
+ blackbird_stop_codec(dev);
+
+ /* Make sure we release the hardware */
+ drv = cx8802_get_driver(dev, CX88_MPEG_BLACKBIRD);
+ WARN_ON(!drv);
+ if (drv)
+ drv->request_release(drv);
+
+ spin_lock_irqsave(&dev->slock, flags);
+ while (!list_empty(&dmaq->active)) {
+ struct cx88_buffer *buf = list_entry(dmaq->active.next,
+ struct cx88_buffer, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static const struct vb2_ops blackbird_qops = {
+ .queue_setup = queue_setup,
+ .buf_prepare = buffer_prepare,
+ .buf_finish = buffer_finish,
+ .buf_queue = buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+};
+
+/* ------------------------------------------------------------------ */
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ strscpy(cap->driver, "cx88_blackbird", sizeof(cap->driver));
+ return cx88_querycap(file, core, cap);
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index != 0)
+ return -EINVAL;
+
+ f->pixelformat = V4L2_PIX_FMT_MPEG;
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
+ f->fmt.pix.bytesperline = 0;
+ f->fmt.pix.sizeimage = dev->ts_packet_size * dev->ts_packet_count;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ f->fmt.pix.width = core->width;
+ f->fmt.pix.height = core->height;
+ f->fmt.pix.field = core->field;
+ return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ unsigned int maxw, maxh;
+ enum v4l2_field field;
+
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
+ f->fmt.pix.bytesperline = 0;
+ f->fmt.pix.sizeimage = dev->ts_packet_size * dev->ts_packet_count;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ maxw = norm_maxw(core->tvnorm);
+ maxh = norm_maxh(core->tvnorm);
+
+ field = f->fmt.pix.field;
+
+ switch (field) {
+ case V4L2_FIELD_TOP:
+ case V4L2_FIELD_BOTTOM:
+ case V4L2_FIELD_INTERLACED:
+ case V4L2_FIELD_SEQ_BT:
+ case V4L2_FIELD_SEQ_TB:
+ break;
+ default:
+ field = (f->fmt.pix.height > maxh / 2)
+ ? V4L2_FIELD_INTERLACED
+ : V4L2_FIELD_BOTTOM;
+ break;
+ }
+ if (V4L2_FIELD_HAS_T_OR_B(field))
+ maxh /= 2;
+
+ v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
+ &f->fmt.pix.height, 32, maxh, 0, 0);
+ f->fmt.pix.field = field;
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (vb2_is_busy(&dev->vb2_mpegq))
+ return -EBUSY;
+ if (core->v4ldev && (vb2_is_busy(&core->v4ldev->vb2_vidq) ||
+ vb2_is_busy(&core->v4ldev->vb2_vbiq)))
+ return -EBUSY;
+ vidioc_try_fmt_vid_cap(file, priv, f);
+ core->width = f->fmt.pix.width;
+ core->height = f->fmt.pix.height;
+ core->field = f->fmt.pix.field;
+ cx88_set_scale(core, f->fmt.pix.width, f->fmt.pix.height,
+ f->fmt.pix.field);
+ blackbird_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0,
+ f->fmt.pix.height, f->fmt.pix.width);
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *f)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ bool streaming;
+
+ if (unlikely(core->board.tuner_type == UNSET))
+ return -EINVAL;
+ if (unlikely(f->tuner != 0))
+ return -EINVAL;
+ streaming = vb2_start_streaming_called(&dev->vb2_mpegq);
+ if (streaming)
+ blackbird_stop_codec(dev);
+
+ cx88_set_freq(core, f);
+ blackbird_initialize_codec(dev);
+ cx88_set_scale(core, core->width, core->height, core->field);
+ if (streaming)
+ blackbird_start_codec(dev);
+ return 0;
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ char name[32 + 2];
+
+ snprintf(name, sizeof(name), "%s/2", core->name);
+ call_all(core, core, log_status);
+ v4l2_ctrl_handler_log_status(&dev->cxhdl.hdl, name);
+ return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ return cx88_enum_input(core, i);
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (unlikely(core->board.tuner_type == UNSET))
+ return -EINVAL;
+ if (unlikely(f->tuner != 0))
+ return -EINVAL;
+
+ f->frequency = core->freq;
+ call_all(core, tuner, g_frequency, f);
+
+ return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ *i = core->input;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (i >= 4)
+ return -EINVAL;
+ if (!INPUT(i).type)
+ return -EINVAL;
+
+ cx88_newstation(core);
+ cx88_video_mux(core, i);
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *t)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ u32 reg;
+
+ if (unlikely(core->board.tuner_type == UNSET))
+ return -EINVAL;
+ if (t->index != 0)
+ return -EINVAL;
+
+ strscpy(t->name, "Television", sizeof(t->name));
+ t->capability = V4L2_TUNER_CAP_NORM;
+ t->rangehigh = 0xffffffffUL;
+ call_all(core, tuner, g_tuner, t);
+
+ cx88_get_stereo(core, t);
+ reg = cx_read(MO_DEVICE_STATUS);
+ t->signal = (reg & (1 << 5)) ? 0xffff : 0x0000;
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *t)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (core->board.tuner_type == UNSET)
+ return -EINVAL;
+ if (t->index != 0)
+ return -EINVAL;
+
+ cx88_set_stereo(core, t->audmode, 1);
+ return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ *tvnorm = core->tvnorm;
+ return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id)
+{
+ struct cx8802_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ return cx88_set_tvnorm(core, id);
+}
+
+static const struct v4l2_file_operations mpeg_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops mpeg_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_log_status = vidioc_log_status,
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_std = vidioc_g_std,
+ .vidioc_s_std = vidioc_s_std,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct video_device cx8802_mpeg_template = {
+ .name = "cx8802",
+ .fops = &mpeg_fops,
+ .ioctl_ops = &mpeg_ioctl_ops,
+ .tvnorms = CX88_NORMS,
+};
+
+/* ------------------------------------------------------------------ */
+
+/* The CX8802 MPEG API will call this when we can use the hardware */
+static int cx8802_blackbird_advise_acquire(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ int err = 0;
+
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ /*
+ * By default, core setup will leave the cx22702 out of reset,
+ * on the bus.
+ * We left the hardware on power up with the cx22702 active.
+ * We're being given access to re-arrange the GPIOs.
+ * Take the bus off the cx22702 and put the cx23416 on it.
+ */
+ /* Toggle reset on cx22702 leaving i2c active */
+ cx_set(MO_GP0_IO, 0x00000080);
+ udelay(1000);
+ cx_clear(MO_GP0_IO, 0x00000080);
+ udelay(50);
+ cx_set(MO_GP0_IO, 0x00000080);
+ udelay(1000);
+ /* tri-state the cx22702 pins */
+ cx_set(MO_GP0_IO, 0x00000004);
+ udelay(1000);
+ break;
+ default:
+ err = -ENODEV;
+ }
+ return err;
+}
+
+/* The CX8802 MPEG API will call this when we need to release the hardware */
+static int cx8802_blackbird_advise_release(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ int err = 0;
+
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ /* Exit leaving the cx23416 on the bus */
+ break;
+ default:
+ err = -ENODEV;
+ }
+ return err;
+}
+
+static void blackbird_unregister_video(struct cx8802_dev *dev)
+{
+ video_unregister_device(&dev->mpeg_dev);
+}
+
+static int blackbird_register_video(struct cx8802_dev *dev)
+{
+ int err;
+
+ cx88_vdev_init(dev->core, dev->pci, &dev->mpeg_dev,
+ &cx8802_mpeg_template, "mpeg");
+ dev->mpeg_dev.ctrl_handler = &dev->cxhdl.hdl;
+ video_set_drvdata(&dev->mpeg_dev, dev);
+ dev->mpeg_dev.queue = &dev->vb2_mpegq;
+ dev->mpeg_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
+ V4L2_CAP_VIDEO_CAPTURE;
+ if (dev->core->board.tuner_type != UNSET)
+ dev->mpeg_dev.device_caps |= V4L2_CAP_TUNER;
+ err = video_register_device(&dev->mpeg_dev, VFL_TYPE_VIDEO, -1);
+ if (err < 0) {
+ pr_info("can't register mpeg device\n");
+ return err;
+ }
+ pr_info("registered device %s [mpeg]\n",
+ video_device_node_name(&dev->mpeg_dev));
+ return 0;
+}
+
+/* ----------------------------------------------------------- */
+
+static int cx8802_blackbird_probe(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ struct cx8802_dev *dev = core->dvbdev;
+ struct vb2_queue *q;
+ int err;
+
+ dprintk(1, "%s\n", __func__);
+ dprintk(1, " ->being probed by Card=%d Name=%s, PCI %02x:%02x\n",
+ core->boardnr,
+ core->name,
+ core->pci_bus,
+ core->pci_slot);
+
+ err = -ENODEV;
+ if (!(core->board.mpeg & CX88_MPEG_BLACKBIRD))
+ goto fail_core;
+
+ dev->cxhdl.port = CX2341X_PORT_STREAMING;
+ dev->cxhdl.width = core->width;
+ dev->cxhdl.height = core->height;
+ dev->cxhdl.func = blackbird_mbox_func;
+ dev->cxhdl.priv = dev;
+ err = cx2341x_handler_init(&dev->cxhdl, 36);
+ if (err)
+ goto fail_core;
+ v4l2_ctrl_add_handler(&dev->cxhdl.hdl, &core->video_hdl, NULL, false);
+
+ /* blackbird stuff */
+ pr_info("cx23416 based mpeg encoder (blackbird reference design)\n");
+ host_setup(dev->core);
+
+ blackbird_initialize_codec(dev);
+
+ /* initial device configuration: needed ? */
+// init_controls(core);
+ cx88_set_tvnorm(core, core->tvnorm);
+ cx88_video_mux(core, 0);
+ cx2341x_handler_set_50hz(&dev->cxhdl, core->height == 576);
+ cx2341x_handler_setup(&dev->cxhdl);
+
+ q = &dev->vb2_mpegq;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
+ q->gfp_flags = GFP_DMA32;
+ q->min_buffers_needed = 2;
+ q->drv_priv = dev;
+ q->buf_struct_size = sizeof(struct cx88_buffer);
+ q->ops = &blackbird_qops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &core->lock;
+ q->dev = &dev->pci->dev;
+
+ err = vb2_queue_init(q);
+ if (err < 0)
+ goto fail_core;
+
+ blackbird_register_video(dev);
+
+ return 0;
+
+fail_core:
+ return err;
+}
+
+static int cx8802_blackbird_remove(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ struct cx8802_dev *dev = core->dvbdev;
+
+ /* blackbird */
+ blackbird_unregister_video(drv->core->dvbdev);
+ v4l2_ctrl_handler_free(&dev->cxhdl.hdl);
+
+ return 0;
+}
+
+static struct cx8802_driver cx8802_blackbird_driver = {
+ .type_id = CX88_MPEG_BLACKBIRD,
+ .hw_access = CX8802_DRVCTL_SHARED,
+ .probe = cx8802_blackbird_probe,
+ .remove = cx8802_blackbird_remove,
+ .advise_acquire = cx8802_blackbird_advise_acquire,
+ .advise_release = cx8802_blackbird_advise_release,
+};
+
+static int __init blackbird_init(void)
+{
+ pr_info("cx2388x blackbird driver version %s loaded\n",
+ CX88_VERSION);
+ return cx8802_register_driver(&cx8802_blackbird_driver);
+}
+
+static void __exit blackbird_fini(void)
+{
+ cx8802_unregister_driver(&cx8802_blackbird_driver);
+}
+
+module_init(blackbird_init);
+module_exit(blackbird_fini);
diff --git a/drivers/media/pci/cx88/cx88-cards.c b/drivers/media/pci/cx88/cx88-cards.c
new file mode 100644
index 000000000..f01e48c23
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-cards.c
@@ -0,0 +1,3856 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * device driver for Conexant 2388x based TV cards
+ * card-specific stuff.
+ *
+ * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
+ */
+
+#include "cx88.h"
+#include "tea5767.h"
+#include "xc4000.h"
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+static unsigned int tuner[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
+static unsigned int radio[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
+static unsigned int card[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
+
+module_param_array(tuner, int, NULL, 0444);
+module_param_array(radio, int, NULL, 0444);
+module_param_array(card, int, NULL, 0444);
+
+MODULE_PARM_DESC(tuner, "tuner type");
+MODULE_PARM_DESC(radio, "radio tuner type");
+MODULE_PARM_DESC(card, "card type");
+
+static unsigned int latency = UNSET;
+module_param(latency, int, 0444);
+MODULE_PARM_DESC(latency, "pci latency timer");
+
+static int disable_ir;
+module_param(disable_ir, int, 0444);
+MODULE_PARM_DESC(disable_ir, "Disable IR support");
+
+#define dprintk(level, fmt, arg...) do { \
+ if (cx88_core_debug >= level) \
+ printk(KERN_DEBUG pr_fmt("%s: core:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+/* ------------------------------------------------------------------ */
+/* board config info */
+
+/* If radio_type !=UNSET, radio_addr should be specified
+ */
+
+static const struct cx88_board cx88_boards[] = {
+ [CX88_BOARD_UNKNOWN] = {
+ .name = "UNKNOWN/GENERIC",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE2,
+ .vmux = 1,
+ }, {
+ .type = CX88_VMUX_COMPOSITE3,
+ .vmux = 2,
+ }, {
+ .type = CX88_VMUX_COMPOSITE4,
+ .vmux = 3,
+ } },
+ },
+ [CX88_BOARD_HAUPPAUGE] = {
+ .name = "Hauppauge WinTV 34xxx models",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xff00, // internal decoder
+ }, {
+ .type = CX88_VMUX_DEBUG,
+ .vmux = 0,
+ .gpio0 = 0xff01, // mono from tuner chip
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0xff02,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0xff02,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xff01,
+ },
+ },
+ [CX88_BOARD_GDI] = {
+ .name = "GDI Black Gold",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ } },
+ },
+ [CX88_BOARD_PIXELVIEW] = {
+ .name = "PixelView",
+ .tuner_type = TUNER_PHILIPS_PAL,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xff00, // internal decoder
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xff10,
+ },
+ },
+ [CX88_BOARD_ATI_WONDER_PRO] = {
+ .name = "ATI TV Wonder Pro",
+ .tuner_type = TUNER_PHILIPS_4IN1,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x03ff,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x03fe,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x03fe,
+ } },
+ },
+ [CX88_BOARD_WINFAST2000XP_EXPERT] = {
+ .name = "Leadtek Winfast 2000XP Expert",
+ .tuner_type = TUNER_PHILIPS_4IN1,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00F5e700,
+ .gpio1 = 0x00003004,
+ .gpio2 = 0x00F5e700,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00F5c700,
+ .gpio1 = 0x00003004,
+ .gpio2 = 0x00F5c700,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00F5c700,
+ .gpio1 = 0x00003004,
+ .gpio2 = 0x00F5c700,
+ .gpio3 = 0x02000000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x00F5d700,
+ .gpio1 = 0x00003004,
+ .gpio2 = 0x00F5d700,
+ .gpio3 = 0x02000000,
+ },
+ },
+ [CX88_BOARD_AVERTV_STUDIO_303] = {
+ .name = "AverTV Studio 303 (M126)",
+ .tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio1 = 0xe09f,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio1 = 0xe05f,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio1 = 0xe05f,
+ } },
+ .radio = {
+ .gpio1 = 0xe0df,
+ .type = CX88_RADIO,
+ },
+ },
+ [CX88_BOARD_MSI_TVANYWHERE_MASTER] = {
+ // added gpio values thanks to Michal
+ // values for PAL from DScaler
+ .name = "MSI TV-@nywhere Master",
+ .tuner_type = TUNER_MT2032,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER_NTSC,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x000040bf,
+ .gpio1 = 0x000080c0,
+ .gpio2 = 0x0000ff40,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000040bf,
+ .gpio1 = 0x000080c0,
+ .gpio2 = 0x0000ff40,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000040bf,
+ .gpio1 = 0x000080c0,
+ .gpio2 = 0x0000ff40,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .vmux = 3,
+ .gpio0 = 0x000040bf,
+ .gpio1 = 0x000080c0,
+ .gpio2 = 0x0000ff20,
+ },
+ },
+ [CX88_BOARD_WINFAST_DV2000] = {
+ .name = "Leadtek Winfast DV2000",
+ .tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0035e700,
+ .gpio1 = 0x00003004,
+ .gpio2 = 0x0035e700,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0035c700,
+ .gpio1 = 0x00003004,
+ .gpio2 = 0x0035c700,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0035c700,
+ .gpio1 = 0x0035c700,
+ .gpio2 = 0x02000000,
+ .gpio3 = 0x02000000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0035d700,
+ .gpio1 = 0x00007004,
+ .gpio2 = 0x0035d700,
+ .gpio3 = 0x02000000,
+ },
+ },
+ [CX88_BOARD_LEADTEK_PVR2000] = {
+ // gpio values for PAL version from regspy by DScaler
+ .name = "Leadtek PVR 2000",
+ .tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0000bde2,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0000bde6,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0000bde6,
+ .audioroute = 1,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0000bd62,
+ .audioroute = 1,
+ },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_IODATA_GVVCP3PCI] = {
+ .name = "IODATA GV-VCP3/PCI",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE2,
+ .vmux = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ } },
+ },
+ [CX88_BOARD_PROLINK_PLAYTVPVR] = {
+ .name = "Prolink PlayTV PVR",
+ .tuner_type = TUNER_PHILIPS_FM1236_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xbff0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0xbff3,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0xbff3,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xbff0,
+ },
+ },
+ [CX88_BOARD_ASUS_PVR_416] = {
+ .name = "ASUS PVR-416",
+ .tuner_type = TUNER_PHILIPS_FM1236_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0000fde6,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0000fde6, // 0x0000fda6 L,R RCA audio in?
+ .audioroute = 1,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0000fde2,
+ },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_MSI_TVANYWHERE] = {
+ .name = "MSI TV-@nywhere",
+ .tuner_type = TUNER_MT2032,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00000fbf,
+ .gpio2 = 0x0000fc08,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00000fbf,
+ .gpio2 = 0x0000fc68,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00000fbf,
+ .gpio2 = 0x0000fc68,
+ } },
+ },
+ [CX88_BOARD_KWORLD_DVB_T] = {
+ .name = "KWorld/VStream XPert DVB-T",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0700,
+ .gpio2 = 0x0101,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0700,
+ .gpio2 = 0x0101,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1] = {
+ .name = "DViCO FusionHDTV DVB-T1",
+ .tuner_type = UNSET, /* No analog tuner */
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000027df,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000027df,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_KWORLD_LTV883] = {
+ .name = "KWorld LTV883RF",
+ .tuner_type = TUNER_TNF_8831BGFF,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x07f8,
+ }, {
+ .type = CX88_VMUX_DEBUG,
+ .vmux = 0,
+ .gpio0 = 0x07f9, // mono from tuner chip
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000007fa,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000007fa,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x000007f8,
+ },
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q] = {
+ .name = "DViCO FusionHDTV 3 Gold-Q",
+ .tuner_type = TUNER_MICROTUNE_4042FI5,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ /*
+ * GPIO[0] resets DT3302 DTV receiver
+ * 0 - reset asserted
+ * 1 - normal operation
+ * GPIO[1] mutes analog audio output connector
+ * 0 - enable selected source
+ * 1 - mute
+ * GPIO[2] selects source for analog audio output connector
+ * 0 - analog audio input connector on tab
+ * 1 - analog DAC output from CX23881 chip
+ * GPIO[3] selects RF input connector on tuner module
+ * 0 - RF connector labeled CABLE
+ * 1 - RF connector labeled ANT
+ * GPIO[4] selects high RF for QAM256 mode
+ * 0 - normal RF
+ * 1 - high RF
+ */
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0f0d,
+ }, {
+ .type = CX88_VMUX_CABLE,
+ .vmux = 0,
+ .gpio0 = 0x0f05,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0f00,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0f00,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_DVB_T1] = {
+ .name = "Hauppauge Nova-T DVB-T",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_CONEXANT_DVB_T1] = {
+ .name = "Conexant DVB-T reference design",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PROVIDEO_PV259] = {
+ .name = "Provideo PV259",
+ .tuner_type = TUNER_PHILIPS_FQ1216ME,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .audioroute = 1,
+ } },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS] = {
+ .name = "DViCO FusionHDTV DVB-T Plus",
+ .tuner_type = UNSET, /* No analog tuner */
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000027df,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000027df,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DNTV_LIVE_DVB_T] = {
+ .name = "digitalnow DNTV Live! DVB-T",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00000700,
+ .gpio2 = 0x00000101,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00000700,
+ .gpio2 = 0x00000101,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PCHDTV_HD3000] = {
+ .name = "pcHDTV HD3000 HDTV",
+ .tuner_type = TUNER_THOMSON_DTT761X,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ /* GPIO[2] = audio source for analog audio out connector
+ * 0 = analog audio input connector
+ * 1 = CX88 audio DACs
+ *
+ * GPIO[7] = input to CX88's audio/chroma ADC
+ * 0 = FM 10.7 MHz IF
+ * 1 = Sound 4.5 MHz IF
+ *
+ * GPIO[1,5,6] = Oren 51132 pins 27,35,28 respectively
+ *
+ * GPIO[16] = Remote control input
+ */
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00008484,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00008400,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00008400,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x00008404,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_ROSLYN] = {
+ // entry added by Kaustubh D. Bhalerao <bhalerao.1@osu.edu>
+ // GPIO values obtained from regspy, courtesy Sean Covel
+ .name = "Hauppauge WinTV 28xxx (Roslyn) models",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xed1a,
+ .gpio2 = 0x00ff,
+ }, {
+ .type = CX88_VMUX_DEBUG,
+ .vmux = 0,
+ .gpio0 = 0xff01,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0xff02,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0xed92,
+ .gpio2 = 0x00ff,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xed96,
+ .gpio2 = 0x00ff,
+ },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_DIGITALLOGIC_MEC] = {
+ .name = "Digital-Logic MICROSPACE Entertainment Center (MEC)",
+ .tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00009d80,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00009d76,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00009d76,
+ .audioroute = 1,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x00009d00,
+ .audioroute = 1,
+ },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_IODATA_GVBCTV7E] = {
+ .name = "IODATA GV/BCTV7E",
+ .tuner_type = TUNER_PHILIPS_FQ1286,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 1,
+ .gpio1 = 0x0000e03f,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 2,
+ .gpio1 = 0x0000e07f,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 3,
+ .gpio1 = 0x0000e07f,
+ } }
+ },
+ [CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO] = {
+ .name = "PixelView PlayTV Ultra Pro (Stereo)",
+ /* May be also TUNER_YMEC_TVF_5533MF for NTSC/M or PAL/M */
+ .tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ /*
+ * Some variants use a tda9874 and so need the
+ * tvaudio module.
+ */
+ .audio_chip = CX88_AUDIO_TVAUDIO,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xbf61, /* internal decoder */
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0xbf63,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0xbf63,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xbf60,
+ },
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T] = {
+ .name = "DViCO FusionHDTV 3 Gold-T",
+ .tuner_type = TUNER_THOMSON_DTT761X,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x97ed,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x97e9,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x97e9,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_ADSTECH_DVB_T_PCI] = {
+ .name = "ADS Tech Instant TV DVB-T PCI",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0700,
+ .gpio2 = 0x0101,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0700,
+ .gpio2 = 0x0101,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1] = {
+ .name = "TerraTec Cinergy 1400 DVB-T",
+ .tuner_type = UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 2,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD] = {
+ .name = "DViCO FusionHDTV 5 Gold",
+ .tuner_type = TUNER_LG_TDVS_H06XF, /* TDVS-H062F */
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x87fd,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x87f9,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x87f9,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_AVERMEDIA_ULTRATV_MC_550] = {
+ .name = "AverMedia UltraTV Media Center PCI 550",
+ .tuner_type = TUNER_PHILIPS_FM1236_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 0,
+ .gpio0 = 0x0000cd73,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 1,
+ .gpio0 = 0x0000cd73,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 3,
+ .gpio0 = 0x0000cdb3,
+ .audioroute = 1,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .vmux = 2,
+ .gpio0 = 0x0000cdf3,
+ .audioroute = 1,
+ },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_KWORLD_VSTREAM_EXPERT_DVD] = {
+ /* Alexander Wold <awold@bigfoot.com> */
+ .name = "Kworld V-Stream Xpert DVD",
+ .tuner_type = UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x03000000,
+ .gpio1 = 0x01000000,
+ .gpio2 = 0x02000000,
+ .gpio3 = 0x00100000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x03000000,
+ .gpio1 = 0x01000000,
+ .gpio2 = 0x02000000,
+ .gpio3 = 0x00100000,
+ } },
+ },
+ [CX88_BOARD_ATI_HDTVWONDER] = {
+ .name = "ATI HDTV Wonder",
+ .tuner_type = TUNER_PHILIPS_TUV1236D,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00000ff7,
+ .gpio1 = 0x000000ff,
+ .gpio2 = 0x00000001,
+ .gpio3 = 0x00000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00000ffe,
+ .gpio1 = 0x000000ff,
+ .gpio2 = 0x00000001,
+ .gpio3 = 0x00000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00000ffe,
+ .gpio1 = 0x000000ff,
+ .gpio2 = 0x00000001,
+ .gpio3 = 0x00000000,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_WINFAST_DTV1000] = {
+ .name = "WinFast DTV1000-T",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_AVERTV_303] = {
+ .name = "AVerTV 303 (M126)",
+ .tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00ff,
+ .gpio1 = 0xe09f,
+ .gpio2 = 0x0010,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00ff,
+ .gpio1 = 0xe05f,
+ .gpio2 = 0x0010,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00ff,
+ .gpio1 = 0xe05f,
+ .gpio2 = 0x0010,
+ .gpio3 = 0x0000,
+ } },
+ },
+ [CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1] = {
+ .name = "Hauppauge Nova-S-Plus DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .audio_chip = CX88_AUDIO_WM8775,
+ .i2sinputcntl = 2,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ /* 2: Line-In */
+ .audioroute = 2,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ /* 2: Line-In */
+ .audioroute = 2,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ /* 2: Line-In */
+ .audioroute = 2,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_NOVASE2_S1] = {
+ .name = "Hauppauge Nova-SE2 DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_KWORLD_DVBS_100] = {
+ .name = "KWorld DVB-S 100",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .audio_chip = CX88_AUDIO_WM8775,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ /* 2: Line-In */
+ .audioroute = 2,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ /* 2: Line-In */
+ .audioroute = 2,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ /* 2: Line-In */
+ .audioroute = 2,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_HVR1100] = {
+ .name = "Hauppauge WinTV-HVR1100 DVB-T/Hybrid",
+ .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ } },
+ /* fixme: Add radio support */
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_HVR1100LP] = {
+ .name = "Hauppauge WinTV-HVR1100 DVB-T/Hybrid (Low Profile)",
+ .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ } },
+ /* fixme: Add radio support */
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DNTV_LIVE_DVB_T_PRO] = {
+ .name = "digitalnow DNTV Live! DVB-T Pro",
+ .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE |
+ TDA9887_PORT2_ACTIVE,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xf80808,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0xf80808,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0xf80808,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xf80808,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_KWORLD_DVB_T_CX22702] = {
+ /* Kworld V-stream Xpert DVB-T with Thomson tuner */
+ /* DTT 7579 Conexant CX22702-19 Conexant CX2388x */
+ /* Manenti Marco <marco_manenti@colman.it> */
+ .name = "KWorld/VStream XPert DVB-T with cx22702",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0700,
+ .gpio2 = 0x0101,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0700,
+ .gpio2 = 0x0101,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL] = {
+ .name = "DViCO FusionHDTV DVB-T Dual Digital",
+ .tuner_type = UNSET, /* No analog tuner */
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000067df,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000067df,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT] = {
+ .name = "KWorld HardwareMpegTV XPert",
+ .tuner_type = TUNER_PHILIPS_TDA8290,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x3de2,
+ .gpio2 = 0x00ff,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x3de6,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x3de6,
+ .audioroute = 1,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x3de6,
+ .gpio2 = 0x00ff,
+ },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID] = {
+ .name = "DViCO FusionHDTV DVB-T Hybrid",
+ .tuner_type = TUNER_THOMSON_FE6600,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0000a75f,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0000a75b,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0000a75b,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PCHDTV_HD5500] = {
+ .name = "pcHDTV HD5500 HDTV",
+ .tuner_type = TUNER_LG_TDVS_H06XF, /* TDVS-H064F */
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x87fd,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x87f9,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x87f9,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_KWORLD_MCE200_DELUXE] = {
+ /*
+ * FIXME: tested TV input only, disabled composite,
+ * svideo and radio until they can be tested also.
+ */
+ .name = "Kworld MCE 200 Deluxe",
+ .tuner_type = TUNER_TENA_9533_DI,
+ .radio_type = UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0000BDE6
+ } },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_PIXELVIEW_PLAYTV_P7000] = {
+ /* FIXME: SVideo, Composite and FM inputs are untested */
+ .name = "PixelView PlayTV P7000",
+ .tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE |
+ TDA9887_PORT2_ACTIVE,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x5da6,
+ } },
+ .mpeg = CX88_MPEG_BLACKBIRD,
+ },
+ [CX88_BOARD_NPGTECH_REALTV_TOP10FM] = {
+ .name = "NPG Tech Real TV FM Top 10",
+ .tuner_type = TUNER_TNF_5335MF, /* Actually a TNF9535 */
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0788,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x078b,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x078b,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x074a,
+ },
+ },
+ [CX88_BOARD_WINFAST_DTV2000H] = {
+ .name = "WinFast DTV2000 H",
+ .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00017304,
+ .gpio1 = 0x00008203,
+ .gpio2 = 0x00017304,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0001d701,
+ .gpio1 = 0x0000b207,
+ .gpio2 = 0x0001d701,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE2,
+ .vmux = 2,
+ .gpio0 = 0x0001d503,
+ .gpio1 = 0x0000b207,
+ .gpio2 = 0x0001d503,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 3,
+ .gpio0 = 0x0001d701,
+ .gpio1 = 0x0000b207,
+ .gpio2 = 0x0001d701,
+ .gpio3 = 0x02000000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x00015702,
+ .gpio1 = 0x0000f207,
+ .gpio2 = 0x00015702,
+ .gpio3 = 0x02000000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_WINFAST_DTV2000H_J] = {
+ .name = "WinFast DTV2000 H rev. J",
+ .tuner_type = TUNER_PHILIPS_FMD1216MEX_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00017300,
+ .gpio1 = 0x00008207,
+ .gpio2 = 0x00000000,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00018300,
+ .gpio1 = 0x0000f207,
+ .gpio2 = 0x00017304,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00018301,
+ .gpio1 = 0x0000f207,
+ .gpio2 = 0x00017304,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00018301,
+ .gpio1 = 0x0000f207,
+ .gpio2 = 0x00017304,
+ .gpio3 = 0x02000000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x00015702,
+ .gpio1 = 0x0000f207,
+ .gpio2 = 0x00015702,
+ .gpio3 = 0x02000000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_GENIATECH_DVBS] = {
+ .name = "Geniatech DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_HVR3000] = {
+ .name = "Hauppauge WinTV-HVR3000 TriMode Analog/DVB-S/DVB-T",
+ .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .audio_chip = CX88_AUDIO_WM8775,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x84bf,
+ /* 1: TV Audio / FM Mono */
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x84bf,
+ /* 2: Line-In */
+ .audioroute = 2,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x84bf,
+ /* 2: Line-In */
+ .audioroute = 2,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x84bf,
+ /* 4: FM Stereo (untested) */
+ .audioroute = 8,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ .num_frontends = 2,
+ },
+ [CX88_BOARD_NORWOOD_MICRO] = {
+ .name = "Norwood Micro TV Tuner",
+ .tuner_type = TUNER_TNF_5335MF,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0709,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x070b,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x070b,
+ } },
+ },
+ [CX88_BOARD_TE_DTV_250_OEM_SWANN] = {
+ .name = "Shenzhen Tungsten Ages Tech TE-DTV-250 / Swann OEM",
+ .tuner_type = TUNER_LG_PAL_NEW_TAPC,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x003fffff,
+ .gpio1 = 0x00e00000,
+ .gpio2 = 0x003fffff,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x003fffff,
+ .gpio1 = 0x00e00000,
+ .gpio2 = 0x003fffff,
+ .gpio3 = 0x02000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x003fffff,
+ .gpio1 = 0x00e00000,
+ .gpio2 = 0x003fffff,
+ .gpio3 = 0x02000000,
+ } },
+ },
+ [CX88_BOARD_HAUPPAUGE_HVR1300] = {
+ .name = "Hauppauge WinTV-HVR1300 DVB-T/Hybrid MPEG Encoder",
+ .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .audio_chip = CX88_AUDIO_WM8775,
+ /*
+ * gpio0 as reported by Mike Crash <mike AT mikecrash.com>
+ */
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xef88,
+ /* 1: TV Audio / FM Mono */
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0xef88,
+ /* 2: Line-In */
+ .audioroute = 2,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0xef88,
+ /* 2: Line-In */
+ .audioroute = 2,
+ } },
+ .mpeg = CX88_MPEG_DVB | CX88_MPEG_BLACKBIRD,
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xef88,
+ /* 4: FM Stereo (untested) */
+ .audioroute = 8,
+ },
+ },
+ [CX88_BOARD_SAMSUNG_SMT_7020] = {
+ .name = "Samsung SMT 7020 DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_ADSTECH_PTV_390] = {
+ .name = "ADS Tech Instant Video PCI",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DEBUG,
+ .vmux = 3,
+ .gpio0 = 0x04ff,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x07fa,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x07fa,
+ } },
+ },
+ [CX88_BOARD_PINNACLE_PCTV_HD_800i] = {
+ .name = "Pinnacle PCTV HD 800i",
+ .tuner_type = TUNER_XC5000,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x04fb,
+ .gpio1 = 0x10ff,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x04fb,
+ .gpio1 = 0x10ef,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x04fb,
+ .gpio1 = 0x10ef,
+ .audioroute = 1,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO] = {
+ .name = "DViCO FusionHDTV 5 PCI nano",
+ /* xc3008 tuner, digital only for now */
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x000027df, /* Unconfirmed */
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000027df, /* Unconfirmed */
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000027df, /* Unconfirmed */
+ .audioroute = 1,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PINNACLE_HYBRID_PCTV] = {
+ .name = "Pinnacle Hybrid PCTV",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .radio_type = UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x004ff,
+ .gpio1 = 0x010ff,
+ .gpio2 = 0x00001,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x004fb,
+ .gpio1 = 0x010ef,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x004fb,
+ .gpio1 = 0x010ef,
+ .audioroute = 1,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x004ff,
+ .gpio1 = 0x010ff,
+ .gpio2 = 0x0ff,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ /* Terry Wu <terrywu2009@gmail.com> */
+ /* TV Audio : set GPIO 2, 18, 19 value to 0, 1, 0 */
+ /* FM Audio : set GPIO 2, 18, 19 value to 0, 0, 0 */
+ /* Line-in Audio : set GPIO 2, 18, 19 value to 0, 1, 1 */
+ /* Mute Audio : set GPIO 2 value to 1 */
+ [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL] = {
+ .name = "Leadtek TV2000 XP Global",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .radio_type = UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C04, /* pin 18 = 1, pin 19 = 0 */
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */
+ .gpio3 = 0x0000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C00, /* pin 18 = 0, pin 19 = 0 */
+ .gpio3 = 0x0000,
+ },
+ },
+ [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36] = {
+ .name = "Leadtek TV2000 XP Global (SC4100)",
+ .tuner_type = TUNER_XC4000,
+ .tuner_addr = 0x61,
+ .radio_type = UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C04, /* pin 18 = 1, pin 19 = 0 */
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */
+ .gpio3 = 0x0000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x0000,
+ .gpio2 = 0x0C00, /* pin 18 = 0, pin 19 = 0 */
+ .gpio3 = 0x0000,
+ },
+ },
+ [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43] = {
+ .name = "Leadtek TV2000 XP Global (XC4100)",
+ .tuner_type = TUNER_XC4000,
+ .tuner_addr = 0x61,
+ .radio_type = UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6040, /* pin 14 = 1, pin 13 = 0 */
+ .gpio2 = 0x0000,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6060, /* pin 14 = 1, pin 13 = 1 */
+ .gpio2 = 0x0000,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6060, /* pin 14 = 1, pin 13 = 1 */
+ .gpio2 = 0x0000,
+ .gpio3 = 0x0000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6000, /* pin 14 = 1, pin 13 = 0 */
+ .gpio2 = 0x0000,
+ .gpio3 = 0x0000,
+ },
+ },
+ [CX88_BOARD_POWERCOLOR_REAL_ANGEL] = {
+ /* Long names may confuse LIRC. */
+ .name = "PowerColor RA330",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .input = { {
+ /*
+ * Due to the way the cx88 driver is written,
+ * there is no way to deactivate audio pass-
+ * through without this entry. Furthermore, if
+ * the TV mux entry is first, you get audio
+ * from the tuner on boot for a little while.
+ */
+ .type = CX88_VMUX_DEBUG,
+ .vmux = 3,
+ .gpio0 = 0x00ff,
+ .gpio1 = 0xf39d,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00ff,
+ .gpio1 = 0xf35d,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00ff,
+ .gpio1 = 0xf37d,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000ff,
+ .gpio1 = 0x0f37d,
+ .gpio3 = 0x00000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x000ff,
+ .gpio1 = 0x0f35d,
+ .gpio3 = 0x00000,
+ },
+ },
+ [CX88_BOARD_GENIATECH_X8000_MT] = {
+ /* Also PowerColor Real Angel 330 and Geniatech X800 OEM */
+ .name = "Geniatech X8000-MT DVBT",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x00000000,
+ .gpio1 = 0x00e3e341,
+ .gpio2 = 0x00000000,
+ .gpio3 = 0x00000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x00000000,
+ .gpio1 = 0x00e3e361,
+ .gpio2 = 0x00000000,
+ .gpio3 = 0x00000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x00000000,
+ .gpio1 = 0x00e3e361,
+ .gpio2 = 0x00000000,
+ .gpio3 = 0x00000000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x00000000,
+ .gpio1 = 0x00e3e341,
+ .gpio2 = 0x00000000,
+ .gpio3 = 0x00000000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_NOTONLYTV_LV3H] = {
+ .name = "NotOnlyTV LV3H",
+ .tuner_type = TUNER_XC2028,
+ .radio_type = UNSET,
+ .tuner_addr = 0x61,
+ .radio_addr = ADDR_UNSET,
+ /* if gpio1:bit9 is enabled, DVB-T won't work */
+
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0000,
+ .gpio1 = 0xa141,
+ .gpio2 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0000,
+ .gpio1 = 0xa161,
+ .gpio2 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0000,
+ .gpio1 = 0xa161,
+ .gpio2 = 0x0000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0000,
+ .gpio1 = 0xa141,
+ .gpio2 = 0x0000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO] = {
+ .name = "DViCO FusionHDTV DVB-T PRO",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .radio_type = UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000067df,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000067df,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD] = {
+ .name = "DViCO FusionHDTV 7 Gold",
+ .tuner_type = TUNER_XC5000,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x10df,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x16d9,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x16d9,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PROLINK_PV_8000GT] = {
+ .name = "Prolink Pixelview MPEG 8000GT",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0ff,
+ .gpio2 = 0x0cfb,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio2 = 0x0cfb,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio2 = 0x0cfb,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio2 = 0x0cfb,
+ },
+ },
+ [CX88_BOARD_PROLINK_PV_GLOBAL_XTREME] = {
+ .name = "Prolink Pixelview Global Extreme",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x04fb,
+ .gpio1 = 0x04080,
+ .gpio2 = 0x0cf7,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x04fb,
+ .gpio1 = 0x04080,
+ .gpio2 = 0x0cfb,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x04fb,
+ .gpio1 = 0x04080,
+ .gpio2 = 0x0cfb,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x04ff,
+ .gpio1 = 0x04080,
+ .gpio2 = 0x0cf7,
+ },
+ },
+ /*
+ * Both radio, analog and ATSC work with this board.
+ * However, for analog to work, s5h1409 gate should be open,
+ * otherwise, tuner-xc3028 won't be detected.
+ * A proper fix require using the newer i2c methods to add
+ * tuner-xc3028 without doing an i2c probe.
+ */
+ [CX88_BOARD_KWORLD_ATSC_120] = {
+ .name = "Kworld PlusTV HD PCI 120 (ATSC 120)",
+ .tuner_type = TUNER_XC2028,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x000000ff,
+ .gpio1 = 0x0000f35d,
+ .gpio2 = 0x00000000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x000000ff,
+ .gpio1 = 0x0000f37e,
+ .gpio2 = 0x00000000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x000000ff,
+ .gpio1 = 0x0000f37e,
+ .gpio2 = 0x00000000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x000000ff,
+ .gpio1 = 0x0000f35d,
+ .gpio2 = 0x00000000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_HVR4000] = {
+ .name = "Hauppauge WinTV-HVR4000 DVB-S/S2/T/Hybrid",
+ .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .tda9887_conf = TDA9887_PRESENT,
+ .audio_chip = CX88_AUDIO_WM8775,
+ /*
+ * GPIO0 (WINTV2000)
+ *
+ * Analogue SAT DVB-T
+ * Antenna 0xc4bf 0xc4bb
+ * Composite 0xc4bf 0xc4bb
+ * S-Video 0xc4bf 0xc4bb
+ * Composite1 0xc4ff 0xc4fb
+ * S-Video1 0xc4ff 0xc4fb
+ *
+ * BIT VALUE FUNCTION GP{x}_IO
+ * 0 1 I:?
+ * 1 1 I:?
+ * 2 1 O:MPEG PORT 0=DVB-T 1=DVB-S
+ * 3 1 I:?
+ * 4 1 I:?
+ * 5 1 I:?
+ * 6 0 O:INPUT SELECTOR 0=INTERNAL 1=EXPANSION
+ * 7 1 O:DVB-T DEMOD RESET LOW
+ *
+ * BIT VALUE FUNCTION GP{x}_OE
+ * 8 0 I
+ * 9 0 I
+ * a 1 O
+ * b 0 I
+ * c 0 I
+ * d 0 I
+ * e 1 O
+ * f 1 O
+ *
+ * WM8775 ADC
+ *
+ * 1: TV Audio / FM Mono
+ * 2: Line-In
+ * 3: Line-In Expansion
+ * 4: FM Stereo
+ */
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0xc4bf,
+ /* 1: TV Audio / FM Mono */
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0xc4bf,
+ /* 2: Line-In */
+ .audioroute = 2,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0xc4bf,
+ /* 2: Line-In */
+ .audioroute = 2,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0xc4bf,
+ /* 4: FM Stereo */
+ .audioroute = 8,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ .num_frontends = 2,
+ },
+ [CX88_BOARD_HAUPPAUGE_HVR4000LITE] = {
+ .name = "Hauppauge WinTV-HVR4000(Lite) DVB-S/S2",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TEVII_S420] = {
+ .name = "TeVii S420 DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TEVII_S460] = {
+ .name = "TeVii S460 DVB-S/S2",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TEVII_S464] = {
+ .name = "TeVii S464 DVB-S/S2",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_OMICOM_SS4_PCI] = {
+ .name = "Omicom SS4 DVB-S/S2 PCI",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TBS_8910] = {
+ .name = "TBS 8910 DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TBS_8920] = {
+ .name = "TBS 8920 DVB-S/S2",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ .gpio0 = 0x8080,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PROF_6200] = {
+ .name = "Prof 6200 DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PROF_7300] = {
+ .name = "PROF 7300 DVB-S/S2",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_SATTRADE_ST4200] = {
+ .name = "SATTRADE ST4200 DVB-S/S2",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII] = {
+ .name = "Terratec Cinergy HT PCI MKII",
+ .tuner_type = TUNER_XC2028,
+ .tuner_addr = 0x61,
+ .radio_type = UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x004ff,
+ .gpio1 = 0x010ff,
+ .gpio2 = 0x00001,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x004fb,
+ .gpio1 = 0x010ef,
+ .audioroute = 1,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x004fb,
+ .gpio1 = 0x010ef,
+ .audioroute = 1,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x004ff,
+ .gpio1 = 0x010ff,
+ .gpio2 = 0x0ff,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_HAUPPAUGE_IRONLY] = {
+ .name = "Hauppauge WinTV-IR Only",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ },
+ [CX88_BOARD_WINFAST_DTV1800H] = {
+ .name = "Leadtek WinFast DTV1800 Hybrid",
+ .tuner_type = TUNER_XC2028,
+ .radio_type = UNSET,
+ .tuner_addr = 0x61,
+ .radio_addr = ADDR_UNSET,
+ /*
+ * GPIO setting
+ *
+ * 2: mute (0=off,1=on)
+ * 12: tuner reset pin
+ * 13: audio source (0=tuner audio,1=line in)
+ * 14: FM (0=on,1=off ???)
+ */
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6040, /* pin 13 = 0, pin 14 = 1 */
+ .gpio2 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */
+ .gpio2 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */
+ .gpio2 = 0x0000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6000, /* pin 13 = 0, pin 14 = 0 */
+ .gpio2 = 0x0000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_WINFAST_DTV1800H_XC4000] = {
+ .name = "Leadtek WinFast DTV1800 H (XC4000)",
+ .tuner_type = TUNER_XC4000,
+ .radio_type = UNSET,
+ .tuner_addr = 0x61,
+ .radio_addr = ADDR_UNSET,
+ /*
+ * GPIO setting
+ *
+ * 2: mute (0=off,1=on)
+ * 12: tuner reset pin
+ * 13: audio source (0=tuner audio,1=line in)
+ * 14: FM (0=on,1=off ???)
+ */
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6040, /* pin 13 = 0, pin 14 = 1 */
+ .gpio2 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */
+ .gpio2 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */
+ .gpio2 = 0x0000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0400, /* pin 2 = 0 */
+ .gpio1 = 0x6000, /* pin 13 = 0, pin 14 = 0 */
+ .gpio2 = 0x0000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_WINFAST_DTV2000H_PLUS] = {
+ .name = "Leadtek WinFast DTV2000 H PLUS",
+ .tuner_type = TUNER_XC4000,
+ .radio_type = UNSET,
+ .tuner_addr = 0x61,
+ .radio_addr = ADDR_UNSET,
+ /*
+ * GPIO
+ * 2: 1: mute audio
+ * 12: 0: reset XC4000
+ * 13: 1: audio input is line in (0: tuner)
+ * 14: 0: FM radio
+ * 16: 0: RF input is cable
+ */
+ .input = { {
+ .type = CX88_VMUX_TELEVISION,
+ .vmux = 0,
+ .gpio0 = 0x0403,
+ .gpio1 = 0xF0D7,
+ .gpio2 = 0x0101,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_CABLE,
+ .vmux = 0,
+ .gpio0 = 0x0403,
+ .gpio1 = 0xF0D7,
+ .gpio2 = 0x0100,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_COMPOSITE1,
+ .vmux = 1,
+ .gpio0 = 0x0403, /* was 0x0407 */
+ .gpio1 = 0xF0F7,
+ .gpio2 = 0x0101,
+ .gpio3 = 0x0000,
+ }, {
+ .type = CX88_VMUX_SVIDEO,
+ .vmux = 2,
+ .gpio0 = 0x0403, /* was 0x0407 */
+ .gpio1 = 0xF0F7,
+ .gpio2 = 0x0101,
+ .gpio3 = 0x0000,
+ } },
+ .radio = {
+ .type = CX88_RADIO,
+ .gpio0 = 0x0403,
+ .gpio1 = 0xF097,
+ .gpio2 = 0x0100,
+ .gpio3 = 0x0000,
+ },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_PROF_7301] = {
+ .name = "Prof 7301 DVB-S/S2",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+ [CX88_BOARD_TWINHAN_VP1027_DVBS] = {
+ .name = "Twinhan VP-1027 DVB-S",
+ .tuner_type = UNSET,
+ .radio_type = UNSET,
+ .tuner_addr = ADDR_UNSET,
+ .radio_addr = ADDR_UNSET,
+ .input = { {
+ .type = CX88_VMUX_DVB,
+ .vmux = 0,
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
+};
+
+/* ------------------------------------------------------------------ */
+/* PCI subsystem IDs */
+
+static const struct cx88_subid cx88_subids[] = {
+ {
+ .subvendor = 0x0070,
+ .subdevice = 0x3400,
+ .card = CX88_BOARD_HAUPPAUGE,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x3401,
+ .card = CX88_BOARD_HAUPPAUGE,
+ }, {
+ .subvendor = 0x14c7,
+ .subdevice = 0x0106,
+ .card = CX88_BOARD_GDI,
+ }, {
+ .subvendor = 0x14c7,
+ .subdevice = 0x0107, /* with mpeg encoder */
+ .card = CX88_BOARD_GDI,
+ }, {
+ .subvendor = PCI_VENDOR_ID_ATI,
+ .subdevice = 0x00f8,
+ .card = CX88_BOARD_ATI_WONDER_PRO,
+ }, {
+ .subvendor = PCI_VENDOR_ID_ATI,
+ .subdevice = 0x00f9,
+ .card = CX88_BOARD_ATI_WONDER_PRO,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6611,
+ .card = CX88_BOARD_WINFAST2000XP_EXPERT,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6613, /* NTSC */
+ .card = CX88_BOARD_WINFAST2000XP_EXPERT,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6620,
+ .card = CX88_BOARD_WINFAST_DV2000,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x663b,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x663c,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ .subvendor = 0x1461,
+ .subdevice = 0x000b,
+ .card = CX88_BOARD_AVERTV_STUDIO_303,
+ }, {
+ .subvendor = 0x1462,
+ .subdevice = 0x8606,
+ .card = CX88_BOARD_MSI_TVANYWHERE_MASTER,
+ }, {
+ .subvendor = 0x10fc,
+ .subdevice = 0xd003,
+ .card = CX88_BOARD_IODATA_GVVCP3PCI,
+ }, {
+ .subvendor = 0x1043,
+ .subdevice = 0x4823, /* with mpeg encoder */
+ .card = CX88_BOARD_ASUS_PVR_416,
+ }, {
+ .subvendor = 0x17de,
+ .subdevice = 0x08a6,
+ .card = CX88_BOARD_KWORLD_DVB_T,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xd810,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xd820,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb00,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9002,
+ .card = CX88_BOARD_HAUPPAUGE_DVB_T1,
+ }, {
+ .subvendor = 0x14f1,
+ .subdevice = 0x0187,
+ .card = CX88_BOARD_CONEXANT_DVB_T1,
+ }, {
+ .subvendor = 0x1540,
+ .subdevice = 0x2580,
+ .card = CX88_BOARD_PROVIDEO_PV259,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb10,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS,
+ }, {
+ .subvendor = 0x1554,
+ .subdevice = 0x4811,
+ .card = CX88_BOARD_PIXELVIEW,
+ }, {
+ .subvendor = 0x7063,
+ .subdevice = 0x3000, /* HD-3000 card */
+ .card = CX88_BOARD_PCHDTV_HD3000,
+ }, {
+ .subvendor = 0x17de,
+ .subdevice = 0xa8a6,
+ .card = CX88_BOARD_DNTV_LIVE_DVB_T,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x2801,
+ .card = CX88_BOARD_HAUPPAUGE_ROSLYN,
+ }, {
+ .subvendor = 0x14f1,
+ .subdevice = 0x0342,
+ .card = CX88_BOARD_DIGITALLOGIC_MEC,
+ }, {
+ .subvendor = 0x10fc,
+ .subdevice = 0xd035,
+ .card = CX88_BOARD_IODATA_GVBCTV7E,
+ }, {
+ .subvendor = 0x1421,
+ .subdevice = 0x0334,
+ .card = CX88_BOARD_ADSTECH_DVB_T_PCI,
+ }, {
+ .subvendor = 0x153b,
+ .subdevice = 0x1166,
+ .card = CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xd500,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD,
+ }, {
+ .subvendor = 0x1461,
+ .subdevice = 0x8011,
+ .card = CX88_BOARD_AVERMEDIA_ULTRATV_MC_550,
+ }, {
+ .subvendor = PCI_VENDOR_ID_ATI,
+ .subdevice = 0xa101,
+ .card = CX88_BOARD_ATI_HDTVWONDER,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x665f,
+ .card = CX88_BOARD_WINFAST_DTV1000,
+ }, {
+ .subvendor = 0x1461,
+ .subdevice = 0x000a,
+ .card = CX88_BOARD_AVERTV_303,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9200,
+ .card = CX88_BOARD_HAUPPAUGE_NOVASE2_S1,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9201,
+ .card = CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9202,
+ .card = CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1,
+ }, {
+ .subvendor = 0x17de,
+ .subdevice = 0x08b2,
+ .card = CX88_BOARD_KWORLD_DVBS_100,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9400,
+ .card = CX88_BOARD_HAUPPAUGE_HVR1100,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9402,
+ .card = CX88_BOARD_HAUPPAUGE_HVR1100,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9800,
+ .card = CX88_BOARD_HAUPPAUGE_HVR1100LP,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9802,
+ .card = CX88_BOARD_HAUPPAUGE_HVR1100LP,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9001,
+ .card = CX88_BOARD_HAUPPAUGE_DVB_T1,
+ }, {
+ .subvendor = 0x1822,
+ .subdevice = 0x0025,
+ .card = CX88_BOARD_DNTV_LIVE_DVB_T_PRO,
+ }, {
+ .subvendor = 0x17de,
+ .subdevice = 0x08a1,
+ .card = CX88_BOARD_KWORLD_DVB_T_CX22702,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb50,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb54,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL,
+ /* Re-branded DViCO: DigitalNow DVB-T Dual */
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb11,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS,
+ /* Re-branded DViCO: UltraView DVB-T Plus */
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb30,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO,
+ }, {
+ .subvendor = 0x17de,
+ .subdevice = 0x0840,
+ .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT,
+ }, {
+ .subvendor = 0x1421,
+ .subdevice = 0x0305,
+ .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb40,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdb44,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID,
+ }, {
+ .subvendor = 0x7063,
+ .subdevice = 0x5500,
+ .card = CX88_BOARD_PCHDTV_HD5500,
+ }, {
+ .subvendor = 0x17de,
+ .subdevice = 0x0841,
+ .card = CX88_BOARD_KWORLD_MCE200_DELUXE,
+ }, {
+ .subvendor = 0x1822,
+ .subdevice = 0x0019,
+ .card = CX88_BOARD_DNTV_LIVE_DVB_T_PRO,
+ }, {
+ .subvendor = 0x1554,
+ .subdevice = 0x4813,
+ .card = CX88_BOARD_PIXELVIEW_PLAYTV_P7000,
+ }, {
+ .subvendor = 0x14f1,
+ .subdevice = 0x0842,
+ .card = CX88_BOARD_NPGTECH_REALTV_TOP10FM,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x665e,
+ .card = CX88_BOARD_WINFAST_DTV2000H,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6f2b,
+ .card = CX88_BOARD_WINFAST_DTV2000H_J,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xd800, /* FusionHDTV 3 Gold (original revision) */
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q,
+ }, {
+ .subvendor = 0x14f1,
+ .subdevice = 0x0084,
+ .card = CX88_BOARD_GENIATECH_DVBS,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x1404,
+ .card = CX88_BOARD_HAUPPAUGE_HVR3000,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdc00,
+ .card = CX88_BOARD_SAMSUNG_SMT_7020,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xdccd,
+ .card = CX88_BOARD_SAMSUNG_SMT_7020,
+ }, {
+ .subvendor = 0x1461,
+ .subdevice = 0xc111, /* AverMedia M150-D */
+ /* This board is known to work with the ASUS PVR416 config */
+ .card = CX88_BOARD_ASUS_PVR_416,
+ }, {
+ .subvendor = 0xc180,
+ .subdevice = 0xc980,
+ .card = CX88_BOARD_TE_DTV_250_OEM_SWANN,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9600,
+ .card = CX88_BOARD_HAUPPAUGE_HVR1300,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9601,
+ .card = CX88_BOARD_HAUPPAUGE_HVR1300,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9602,
+ .card = CX88_BOARD_HAUPPAUGE_HVR1300,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6632,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ .subvendor = 0x12ab,
+ .subdevice = 0x2300, /* Club3D Zap TV2100 */
+ .card = CX88_BOARD_KWORLD_DVB_T_CX22702,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9000,
+ .card = CX88_BOARD_HAUPPAUGE_DVB_T1,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x1400,
+ .card = CX88_BOARD_HAUPPAUGE_HVR3000,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x1401,
+ .card = CX88_BOARD_HAUPPAUGE_HVR3000,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x1402,
+ .card = CX88_BOARD_HAUPPAUGE_HVR3000,
+ }, {
+ .subvendor = 0x1421,
+ .subdevice = 0x0341, /* ADS Tech InstantTV DVB-S */
+ .card = CX88_BOARD_KWORLD_DVBS_100,
+ }, {
+ .subvendor = 0x1421,
+ .subdevice = 0x0390,
+ .card = CX88_BOARD_ADSTECH_PTV_390,
+ }, {
+ .subvendor = 0x11bd,
+ .subdevice = 0x0051,
+ .card = CX88_BOARD_PINNACLE_PCTV_HD_800i,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xd530,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO,
+ }, {
+ .subvendor = 0x12ab,
+ .subdevice = 0x1788,
+ .card = CX88_BOARD_PINNACLE_HYBRID_PCTV,
+ }, {
+ .subvendor = 0x14f1,
+ .subdevice = 0xea3d,
+ .card = CX88_BOARD_POWERCOLOR_REAL_ANGEL,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6f18,
+ .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL,
+ }, {
+ /* Also NotOnlyTV LV3H (version 1.11 is silkscreened on the board) */
+ .subvendor = 0x14f1,
+ .subdevice = 0x8852,
+ .card = CX88_BOARD_GENIATECH_X8000_MT,
+ }, {
+ .subvendor = 0x18ac,
+ .subdevice = 0xd610,
+ .card = CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD,
+ }, {
+ .subvendor = 0x1554,
+ .subdevice = 0x4935,
+ .card = CX88_BOARD_PROLINK_PV_8000GT,
+ }, {
+ .subvendor = 0x1554,
+ .subdevice = 0x4976,
+ .card = CX88_BOARD_PROLINK_PV_GLOBAL_XTREME,
+ }, {
+ .subvendor = 0x17de,
+ .subdevice = 0x08c1,
+ .card = CX88_BOARD_KWORLD_ATSC_120,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x6900,
+ .card = CX88_BOARD_HAUPPAUGE_HVR4000,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x6904,
+ .card = CX88_BOARD_HAUPPAUGE_HVR4000,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x6902,
+ .card = CX88_BOARD_HAUPPAUGE_HVR4000,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x6905,
+ .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x6906,
+ .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE,
+ }, {
+ .subvendor = 0xd420,
+ .subdevice = 0x9022,
+ .card = CX88_BOARD_TEVII_S420,
+ }, {
+ .subvendor = 0xd460,
+ .subdevice = 0x9022,
+ .card = CX88_BOARD_TEVII_S460,
+ }, {
+ .subvendor = 0xd464,
+ .subdevice = 0x9022,
+ .card = CX88_BOARD_TEVII_S464,
+ }, {
+ .subvendor = 0xA044,
+ .subdevice = 0x2011,
+ .card = CX88_BOARD_OMICOM_SS4_PCI,
+ }, {
+ .subvendor = 0x8910,
+ .subdevice = 0x8888,
+ .card = CX88_BOARD_TBS_8910,
+ }, {
+ .subvendor = 0x8920,
+ .subdevice = 0x8888,
+ .card = CX88_BOARD_TBS_8920,
+ }, {
+ .subvendor = 0xb022,
+ .subdevice = 0x3022,
+ .card = CX88_BOARD_PROF_6200,
+ }, {
+ .subvendor = 0xB033,
+ .subdevice = 0x3033,
+ .card = CX88_BOARD_PROF_7300,
+ }, {
+ .subvendor = 0xb200,
+ .subdevice = 0x4200,
+ .card = CX88_BOARD_SATTRADE_ST4200,
+ }, {
+ .subvendor = 0x153b,
+ .subdevice = 0x1177,
+ .card = CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII,
+ }, {
+ .subvendor = 0x0070,
+ .subdevice = 0x9290,
+ .card = CX88_BOARD_HAUPPAUGE_IRONLY,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6654,
+ .card = CX88_BOARD_WINFAST_DTV1800H,
+ }, {
+ /* WinFast DTV1800 H with XC4000 tuner */
+ .subvendor = 0x107d,
+ .subdevice = 0x6f38,
+ .card = CX88_BOARD_WINFAST_DTV1800H_XC4000,
+ }, {
+ .subvendor = 0x107d,
+ .subdevice = 0x6f42,
+ .card = CX88_BOARD_WINFAST_DTV2000H_PLUS,
+ }, {
+ /* PVR2000 PAL Model [107d:6630] */
+ .subvendor = 0x107d,
+ .subdevice = 0x6630,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ /* PVR2000 PAL Model [107d:6638] */
+ .subvendor = 0x107d,
+ .subdevice = 0x6638,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ /* PVR2000 NTSC Model [107d:6631] */
+ .subvendor = 0x107d,
+ .subdevice = 0x6631,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ /* PVR2000 NTSC Model [107d:6637] */
+ .subvendor = 0x107d,
+ .subdevice = 0x6637,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ /* PVR2000 NTSC Model [107d:663d] */
+ .subvendor = 0x107d,
+ .subdevice = 0x663d,
+ .card = CX88_BOARD_LEADTEK_PVR2000,
+ }, {
+ /* DV2000 NTSC Model [107d:6621] */
+ .subvendor = 0x107d,
+ .subdevice = 0x6621,
+ .card = CX88_BOARD_WINFAST_DV2000,
+ }, {
+ /* TV2000 XP Global [107d:6618] */
+ .subvendor = 0x107d,
+ .subdevice = 0x6618,
+ .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL,
+ }, {
+ /* TV2000 XP Global [107d:6618] */
+ .subvendor = 0x107d,
+ .subdevice = 0x6619,
+ .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL,
+ }, {
+ /* WinFast TV2000 XP Global with XC4000 tuner */
+ .subvendor = 0x107d,
+ .subdevice = 0x6f36,
+ .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36,
+ }, {
+ /* WinFast TV2000 XP Global with XC4000 tuner and different GPIOs */
+ .subvendor = 0x107d,
+ .subdevice = 0x6f43,
+ .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43,
+ }, {
+ .subvendor = 0xb034,
+ .subdevice = 0x3034,
+ .card = CX88_BOARD_PROF_7301,
+ }, {
+ .subvendor = 0x1822,
+ .subdevice = 0x0023,
+ .card = CX88_BOARD_TWINHAN_VP1027_DVBS,
+ },
+};
+
+/*
+ * some leadtek specific stuff
+ */
+static void leadtek_eeprom(struct cx88_core *core, u8 *eeprom_data)
+{
+ if (eeprom_data[4] != 0x7d ||
+ eeprom_data[5] != 0x10 ||
+ eeprom_data[7] != 0x66) {
+ pr_warn("Leadtek eeprom invalid.\n");
+ return;
+ }
+
+ /* Terry Wu <terrywu2009@gmail.com> */
+ switch (eeprom_data[6]) {
+ case 0x13: /* SSID 6613 for TV2000 XP Expert NTSC Model */
+ case 0x21: /* SSID 6621 for DV2000 NTSC Model */
+ case 0x31: /* SSID 6631 for PVR2000 NTSC Model */
+ case 0x37: /* SSID 6637 for PVR2000 NTSC Model */
+ case 0x3d: /* SSID 6637 for PVR2000 NTSC Model */
+ core->board.tuner_type = TUNER_PHILIPS_FM1236_MK3;
+ break;
+ default:
+ core->board.tuner_type = TUNER_PHILIPS_FM1216ME_MK3;
+ break;
+ }
+
+ pr_info("Leadtek Winfast 2000XP Expert config: tuner=%d, eeprom[0]=0x%02x\n",
+ core->board.tuner_type, eeprom_data[0]);
+}
+
+static void hauppauge_eeprom(struct cx88_core *core, u8 *eeprom_data)
+{
+ struct tveeprom tv;
+
+ tveeprom_hauppauge_analog(&tv, eeprom_data);
+ core->board.tuner_type = tv.tuner_type;
+ core->tuner_formats = tv.tuner_formats;
+ core->board.radio.type = tv.has_radio ? CX88_RADIO : 0;
+ core->model = tv.model;
+
+ /* Make sure we support the board model */
+ switch (tv.model) {
+ case 14009: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in) */
+ case 14019: /* WinTV-HVR3000 (Retail, IR Blaster, b/panel video, 3.5mm audio in) */
+ case 14029: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge) */
+ case 14109: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - low profile) */
+ case 14129: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge - LP) */
+ case 14559: /* WinTV-HVR3000 (OEM, no IR, b/panel video, 3.5mm audio in) */
+ case 14569: /* WinTV-HVR3000 (OEM, no IR, no back panel video) */
+ case 14659: /* WinTV-HVR3000 (OEM, no IR, b/panel video, RCA audio in - Low profile) */
+ case 14669: /* WinTV-HVR3000 (OEM, no IR, no b/panel video - Low profile) */
+ case 28552: /* WinTV-PVR 'Roslyn' (No IR) */
+ case 34519: /* WinTV-PCI-FM */
+ case 69009:
+ /* WinTV-HVR4000 (DVBS/S2/T, Video and IR, back panel inputs) */
+ case 69100: /* WinTV-HVR4000LITE (DVBS/S2, IR) */
+ case 69500: /* WinTV-HVR4000LITE (DVBS/S2, No IR) */
+ case 69559:
+ /* WinTV-HVR4000 (DVBS/S2/T, Video no IR, back panel inputs) */
+ case 69569: /* WinTV-HVR4000 (DVBS/S2/T, Video no IR) */
+ case 90002: /* Nova-T-PCI (9002) */
+ case 92001: /* Nova-S-Plus (Video and IR) */
+ case 92002: /* Nova-S-Plus (Video and IR) */
+ case 90003: /* Nova-T-PCI (9002 No RF out) */
+ case 90500: /* Nova-T-PCI (oem) */
+ case 90501: /* Nova-T-PCI (oem/IR) */
+ case 92000: /* Nova-SE2 (OEM, No Video or IR) */
+ case 92900: /* WinTV-IROnly (No analog or digital Video inputs) */
+ case 94009: /* WinTV-HVR1100 (Video and IR Retail) */
+ case 94501: /* WinTV-HVR1100 (Video and IR OEM) */
+ case 96009: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX) */
+ case 96019: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX/TX) */
+ case 96559: /* WinTV-HVR1300 (PAL Video, MPEG Video no IR) */
+ case 96569: /* WinTV-HVR1300 () */
+ case 96659: /* WinTV-HVR1300 () */
+ case 98559: /* WinTV-HVR1100LP (Video no IR, Retail - Low Profile) */
+ /* known */
+ break;
+ case CX88_BOARD_SAMSUNG_SMT_7020:
+ cx_set(MO_GP0_IO, 0x008989FF);
+ break;
+ default:
+ pr_warn("warning: unknown hauppauge model #%d\n", tv.model);
+ break;
+ }
+
+ pr_info("hauppauge eeprom: model=%d\n", tv.model);
+}
+
+/*
+ * some GDI (was: Modular Technology) specific stuff
+ */
+
+static const struct {
+ int id;
+ int fm;
+ const char *name;
+} gdi_tuner[] = {
+ [0x01] = { .id = UNSET,
+ .name = "NTSC_M" },
+ [0x02] = { .id = UNSET,
+ .name = "PAL_B" },
+ [0x03] = { .id = UNSET,
+ .name = "PAL_I" },
+ [0x04] = { .id = UNSET,
+ .name = "PAL_D" },
+ [0x05] = { .id = UNSET,
+ .name = "SECAM" },
+
+ [0x10] = { .id = UNSET,
+ .fm = 1,
+ .name = "TEMIC_4049" },
+ [0x11] = { .id = TUNER_TEMIC_4136FY5,
+ .name = "TEMIC_4136" },
+ [0x12] = { .id = UNSET,
+ .name = "TEMIC_4146" },
+
+ [0x20] = { .id = TUNER_PHILIPS_FQ1216ME,
+ .fm = 1,
+ .name = "PHILIPS_FQ1216_MK3" },
+ [0x21] = { .id = UNSET, .fm = 1,
+ .name = "PHILIPS_FQ1236_MK3" },
+ [0x22] = { .id = UNSET,
+ .name = "PHILIPS_FI1236_MK3" },
+ [0x23] = { .id = UNSET,
+ .name = "PHILIPS_FI1216_MK3" },
+};
+
+static void gdi_eeprom(struct cx88_core *core, u8 *eeprom_data)
+{
+ const char *name = (eeprom_data[0x0d] < ARRAY_SIZE(gdi_tuner))
+ ? gdi_tuner[eeprom_data[0x0d]].name : NULL;
+
+ pr_info("GDI: tuner=%s\n", name ? name : "unknown");
+ if (!name)
+ return;
+ core->board.tuner_type = gdi_tuner[eeprom_data[0x0d]].id;
+ core->board.radio.type = gdi_tuner[eeprom_data[0x0d]].fm ?
+ CX88_RADIO : 0;
+}
+
+/*
+ * some Divco specific stuff
+ */
+static int cx88_dvico_xc2028_callback(struct cx88_core *core,
+ int command, int arg)
+{
+ switch (command) {
+ case XC2028_TUNER_RESET:
+ switch (core->boardnr) {
+ case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO:
+ /* GPIO-4 xc3028 tuner */
+
+ cx_set(MO_GP0_IO, 0x00001000);
+ cx_clear(MO_GP0_IO, 0x00000010);
+ msleep(100);
+ cx_set(MO_GP0_IO, 0x00000010);
+ msleep(100);
+ break;
+ default:
+ cx_write(MO_GP0_IO, 0x101000);
+ mdelay(5);
+ cx_set(MO_GP0_IO, 0x101010);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * some Geniatech specific stuff
+ */
+
+static int cx88_xc3028_geniatech_tuner_callback(struct cx88_core *core,
+ int command, int mode)
+{
+ switch (command) {
+ case XC2028_TUNER_RESET:
+ switch (INPUT(core->input).type) {
+ case CX88_RADIO:
+ break;
+ case CX88_VMUX_DVB:
+ cx_write(MO_GP1_IO, 0x030302);
+ mdelay(50);
+ break;
+ default:
+ cx_write(MO_GP1_IO, 0x030301);
+ mdelay(50);
+ }
+ cx_write(MO_GP1_IO, 0x101010);
+ mdelay(50);
+ cx_write(MO_GP1_IO, 0x101000);
+ mdelay(50);
+ cx_write(MO_GP1_IO, 0x101010);
+ mdelay(50);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int cx88_xc3028_winfast1800h_callback(struct cx88_core *core,
+ int command, int arg)
+{
+ switch (command) {
+ case XC2028_TUNER_RESET:
+ /* GPIO 12 (xc3028 tuner reset) */
+ cx_set(MO_GP1_IO, 0x1010);
+ mdelay(50);
+ cx_clear(MO_GP1_IO, 0x10);
+ mdelay(75);
+ cx_set(MO_GP1_IO, 0x10);
+ mdelay(75);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int cx88_xc4000_winfast2000h_plus_callback(struct cx88_core *core,
+ int command, int arg)
+{
+ switch (command) {
+ case XC4000_TUNER_RESET:
+ /* GPIO 12 (xc4000 tuner reset) */
+ cx_set(MO_GP1_IO, 0x1010);
+ mdelay(50);
+ cx_clear(MO_GP1_IO, 0x10);
+ mdelay(75);
+ cx_set(MO_GP1_IO, 0x10);
+ mdelay(75);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+/*
+ * some Divco specific stuff
+ */
+static int cx88_pv_8000gt_callback(struct cx88_core *core,
+ int command, int arg)
+{
+ switch (command) {
+ case XC2028_TUNER_RESET:
+ cx_write(MO_GP2_IO, 0xcf7);
+ mdelay(50);
+ cx_write(MO_GP2_IO, 0xef5);
+ mdelay(50);
+ cx_write(MO_GP2_IO, 0xcf7);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * some DViCO specific stuff
+ */
+
+static void dvico_fusionhdtv_hybrid_init(struct cx88_core *core)
+{
+ struct i2c_msg msg = { .addr = 0x45, .flags = 0 };
+ int i, err;
+ static u8 init_bufs[13][5] = {
+ { 0x10, 0x00, 0x20, 0x01, 0x03 },
+ { 0x10, 0x10, 0x01, 0x00, 0x21 },
+ { 0x10, 0x10, 0x10, 0x00, 0xCA },
+ { 0x10, 0x10, 0x12, 0x00, 0x08 },
+ { 0x10, 0x10, 0x13, 0x00, 0x0A },
+ { 0x10, 0x10, 0x16, 0x01, 0xC0 },
+ { 0x10, 0x10, 0x22, 0x01, 0x3D },
+ { 0x10, 0x10, 0x73, 0x01, 0x2E },
+ { 0x10, 0x10, 0x72, 0x00, 0xC5 },
+ { 0x10, 0x10, 0x71, 0x01, 0x97 },
+ { 0x10, 0x10, 0x70, 0x00, 0x0F },
+ { 0x10, 0x10, 0xB0, 0x00, 0x01 },
+ { 0x03, 0x0C },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(init_bufs); i++) {
+ msg.buf = init_bufs[i];
+ msg.len = (i != 12 ? 5 : 2);
+ err = i2c_transfer(&core->i2c_adap, &msg, 1);
+ if (err != 1) {
+ pr_warn("dvico_fusionhdtv_hybrid_init buf %d failed (err = %d)!\n",
+ i, err);
+ return;
+ }
+ }
+}
+
+static int cx88_xc2028_tuner_callback(struct cx88_core *core,
+ int command, int arg)
+{
+ /* Board-specific callbacks */
+ switch (core->boardnr) {
+ case CX88_BOARD_POWERCOLOR_REAL_ANGEL:
+ case CX88_BOARD_GENIATECH_X8000_MT:
+ case CX88_BOARD_KWORLD_ATSC_120:
+ return cx88_xc3028_geniatech_tuner_callback(core,
+ command, arg);
+ case CX88_BOARD_PROLINK_PV_8000GT:
+ case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME:
+ return cx88_pv_8000gt_callback(core, command, arg);
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO:
+ case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO:
+ return cx88_dvico_xc2028_callback(core, command, arg);
+ case CX88_BOARD_NOTONLYTV_LV3H:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL:
+ case CX88_BOARD_WINFAST_DTV1800H:
+ return cx88_xc3028_winfast1800h_callback(core, command, arg);
+ }
+
+ switch (command) {
+ case XC2028_TUNER_RESET:
+ switch (INPUT(core->input).type) {
+ case CX88_RADIO:
+ dprintk(1, "setting GPIO to radio!\n");
+ cx_write(MO_GP0_IO, 0x4ff);
+ mdelay(250);
+ cx_write(MO_GP2_IO, 0xff);
+ mdelay(250);
+ break;
+ case CX88_VMUX_DVB: /* Digital TV*/
+ default: /* Analog TV */
+ dprintk(1, "setting GPIO to TV!\n");
+ break;
+ }
+ cx_write(MO_GP1_IO, 0x101010);
+ mdelay(250);
+ cx_write(MO_GP1_IO, 0x101000);
+ mdelay(250);
+ cx_write(MO_GP1_IO, 0x101010);
+ mdelay(250);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int cx88_xc4000_tuner_callback(struct cx88_core *core,
+ int command, int arg)
+{
+ /* Board-specific callbacks */
+ switch (core->boardnr) {
+ case CX88_BOARD_WINFAST_DTV1800H_XC4000:
+ case CX88_BOARD_WINFAST_DTV2000H_PLUS:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43:
+ return cx88_xc4000_winfast2000h_plus_callback(core,
+ command, arg);
+ }
+ return -EINVAL;
+}
+
+/*
+ * Tuner callback function. Currently only needed for the Pinnacle
+ * PCTV HD 800i with an xc5000 silicon tuner. This is used for both
+ * analog tuner attach (tuner-core.c) and dvb tuner attach (cx88-dvb.c)
+ */
+static int cx88_xc5000_tuner_callback(struct cx88_core *core,
+ int command, int arg)
+{
+ switch (core->boardnr) {
+ case CX88_BOARD_PINNACLE_PCTV_HD_800i:
+ if (command == 0) { /* This is the reset command from xc5000 */
+
+ /*
+ * djh - According to the engineer at PCTV Systems,
+ * the xc5000 reset pin is supposed to be on GPIO12.
+ * However, despite three nights of effort, pulling
+ * that GPIO low didn't reset the xc5000. While
+ * pulling MO_SRST_IO low does reset the xc5000, this
+ * also resets in the s5h1409 being reset as well.
+ * This causes tuning to always fail since the internal
+ * state of the s5h1409 does not match the driver's
+ * state. Given that the only two conditions in which
+ * the driver performs a reset is during firmware load
+ * and powering down the chip, I am taking out the
+ * reset. We know that the chip is being reset
+ * when the cx88 comes online, and not being able to
+ * do power management for this board is worse than
+ * not having any tuning at all.
+ */
+ return 0;
+ }
+
+ dprintk(1, "xc5000: unknown tuner callback command.\n");
+ return -EINVAL;
+ case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD:
+ if (command == 0) { /* This is the reset command from xc5000 */
+ cx_clear(MO_GP0_IO, 0x00000010);
+ usleep_range(10000, 20000);
+ cx_set(MO_GP0_IO, 0x00000010);
+ return 0;
+ }
+
+ dprintk(1, "xc5000: unknown tuner callback command.\n");
+ return -EINVAL;
+ }
+ return 0; /* Should never be here */
+}
+
+int cx88_tuner_callback(void *priv, int component, int command, int arg)
+{
+ struct i2c_algo_bit_data *i2c_algo = priv;
+ struct cx88_core *core;
+
+ if (!i2c_algo) {
+ pr_err("Error - i2c private data undefined.\n");
+ return -EINVAL;
+ }
+
+ core = i2c_algo->data;
+
+ if (!core) {
+ pr_err("Error - device struct undefined.\n");
+ return -EINVAL;
+ }
+
+ if (component != DVB_FRONTEND_COMPONENT_TUNER)
+ return -EINVAL;
+
+ switch (core->board.tuner_type) {
+ case TUNER_XC2028:
+ dprintk(1, "Calling XC2028/3028 callback\n");
+ return cx88_xc2028_tuner_callback(core, command, arg);
+ case TUNER_XC4000:
+ dprintk(1, "Calling XC4000 callback\n");
+ return cx88_xc4000_tuner_callback(core, command, arg);
+ case TUNER_XC5000:
+ dprintk(1, "Calling XC5000 callback\n");
+ return cx88_xc5000_tuner_callback(core, command, arg);
+ }
+ pr_err("Error: Calling callback for tuner %d\n",
+ core->board.tuner_type);
+ return -EINVAL;
+}
+EXPORT_SYMBOL(cx88_tuner_callback);
+
+/* ----------------------------------------------------------------------- */
+
+static void cx88_card_list(struct cx88_core *core, struct pci_dev *pci)
+{
+ int i;
+
+ if (!pci->subsystem_vendor && !pci->subsystem_device) {
+ pr_err("Your board has no valid PCI Subsystem ID and thus can't\n");
+ pr_err("be autodetected. Please pass card=<n> insmod option to\n");
+ pr_err("workaround that. Redirect complaints to the vendor of\n");
+ pr_err("the TV card\n");
+ } else {
+ pr_err("Your board isn't known (yet) to the driver. You can\n");
+ pr_err("try to pick one of the existing card configs via\n");
+ pr_err("card=<n> insmod option. Updating to the latest\n");
+ pr_err("version might help as well.\n");
+ }
+ pr_err("Here is a list of valid choices for the card=<n> insmod option:\n");
+ for (i = 0; i < ARRAY_SIZE(cx88_boards); i++)
+ pr_err(" card=%d -> %s\n", i, cx88_boards[i].name);
+}
+
+static void cx88_card_setup_pre_i2c(struct cx88_core *core)
+{
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ /*
+ * Bring the 702 demod up before i2c scanning/attach or
+ * devices are hidden.
+ *
+ * We leave here with the 702 on the bus
+ *
+ * "reset the IR receiver on GPIO[3]"
+ * Reported by Mike Crash <mike AT mikecrash.com>
+ */
+ cx_write(MO_GP0_IO, 0x0000ef88);
+ udelay(1000);
+ cx_clear(MO_GP0_IO, 0x00000088);
+ udelay(50);
+ cx_set(MO_GP0_IO, 0x00000088); /* 702 out of reset */
+ udelay(1000);
+ break;
+
+ case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME:
+ case CX88_BOARD_PROLINK_PV_8000GT:
+ cx_write(MO_GP2_IO, 0xcf7);
+ msleep(50);
+ cx_write(MO_GP2_IO, 0xef5);
+ msleep(50);
+ cx_write(MO_GP2_IO, 0xcf7);
+ usleep_range(10000, 20000);
+ break;
+
+ case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD:
+ /* Enable the xc5000 tuner */
+ cx_set(MO_GP0_IO, 0x00001010);
+ break;
+
+ case CX88_BOARD_WINFAST_DTV2000H_J:
+ case CX88_BOARD_HAUPPAUGE_HVR3000:
+ case CX88_BOARD_HAUPPAUGE_HVR4000:
+ /* Init GPIO */
+ cx_write(MO_GP0_IO, core->board.input[0].gpio0);
+ udelay(1000);
+ cx_clear(MO_GP0_IO, 0x00000080);
+ udelay(50);
+ cx_set(MO_GP0_IO, 0x00000080); /* 702 out of reset */
+ udelay(1000);
+ break;
+
+ case CX88_BOARD_NOTONLYTV_LV3H:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL:
+ case CX88_BOARD_WINFAST_DTV1800H:
+ cx88_xc3028_winfast1800h_callback(core, XC2028_TUNER_RESET, 0);
+ break;
+
+ case CX88_BOARD_WINFAST_DTV1800H_XC4000:
+ case CX88_BOARD_WINFAST_DTV2000H_PLUS:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43:
+ cx88_xc4000_winfast2000h_plus_callback(core,
+ XC4000_TUNER_RESET, 0);
+ break;
+
+ case CX88_BOARD_TWINHAN_VP1027_DVBS:
+ cx_write(MO_GP0_IO, 0x00003230);
+ cx_write(MO_GP0_IO, 0x00003210);
+ usleep_range(10000, 20000);
+ cx_write(MO_GP0_IO, 0x00001230);
+ break;
+ }
+}
+
+/*
+ * Sets board-dependent xc3028 configuration
+ */
+void cx88_setup_xc3028(struct cx88_core *core, struct xc2028_ctrl *ctl)
+{
+ memset(ctl, 0, sizeof(*ctl));
+
+ ctl->fname = XC2028_DEFAULT_FIRMWARE;
+ ctl->max_len = 64;
+
+ switch (core->boardnr) {
+ case CX88_BOARD_POWERCOLOR_REAL_ANGEL:
+ /* Now works with firmware version 2.7 */
+ if (core->i2c_algo.udelay < 16)
+ core->i2c_algo.udelay = 16;
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO:
+ case CX88_BOARD_WINFAST_DTV1800H:
+ ctl->demod = XC3028_FE_ZARLINK456;
+ break;
+ case CX88_BOARD_KWORLD_ATSC_120:
+ case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO:
+ ctl->demod = XC3028_FE_OREN538;
+ break;
+ case CX88_BOARD_GENIATECH_X8000_MT:
+ /*
+ * FIXME: For this board, the xc3028 never recovers after being
+ * powered down (the reset GPIO probably is not set properly).
+ * We don't have access to the hardware so we cannot determine
+ * which GPIO is used for xc3028, so just disable power xc3028
+ * power management for now
+ */
+ ctl->disable_power_mgmt = 1;
+ break;
+ case CX88_BOARD_NOTONLYTV_LV3H:
+ ctl->demod = XC3028_FE_ZARLINK456;
+ ctl->fname = XC3028L_DEFAULT_FIRMWARE;
+ ctl->read_not_reliable = 1;
+ break;
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL:
+ case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME:
+ case CX88_BOARD_PROLINK_PV_8000GT:
+ /*
+ * Those boards uses non-MTS firmware
+ */
+ break;
+ case CX88_BOARD_PINNACLE_HYBRID_PCTV:
+ case CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII:
+ ctl->demod = XC3028_FE_ZARLINK456;
+ ctl->mts = 1;
+ break;
+ default:
+ ctl->demod = XC3028_FE_OREN538;
+ ctl->mts = 1;
+ }
+}
+EXPORT_SYMBOL_GPL(cx88_setup_xc3028);
+
+static void cx88_card_setup(struct cx88_core *core)
+{
+ static u8 eeprom[256];
+ struct tuner_setup tun_setup;
+ unsigned int mode_mask = T_RADIO | T_ANALOG_TV;
+
+ memset(&tun_setup, 0, sizeof(tun_setup));
+
+ if (!core->i2c_rc) {
+ core->i2c_client.addr = 0xa0 >> 1;
+ tveeprom_read(&core->i2c_client, eeprom, sizeof(eeprom));
+ }
+
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE:
+ case CX88_BOARD_HAUPPAUGE_ROSLYN:
+ if (!core->i2c_rc)
+ hauppauge_eeprom(core, eeprom + 8);
+ break;
+ case CX88_BOARD_GDI:
+ if (!core->i2c_rc)
+ gdi_eeprom(core, eeprom);
+ break;
+ case CX88_BOARD_LEADTEK_PVR2000:
+ case CX88_BOARD_WINFAST_DV2000:
+ case CX88_BOARD_WINFAST2000XP_EXPERT:
+ if (!core->i2c_rc)
+ leadtek_eeprom(core, eeprom);
+ break;
+ case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1:
+ case CX88_BOARD_HAUPPAUGE_NOVASE2_S1:
+ case CX88_BOARD_HAUPPAUGE_DVB_T1:
+ case CX88_BOARD_HAUPPAUGE_HVR1100:
+ case CX88_BOARD_HAUPPAUGE_HVR1100LP:
+ case CX88_BOARD_HAUPPAUGE_HVR3000:
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ case CX88_BOARD_HAUPPAUGE_HVR4000:
+ case CX88_BOARD_HAUPPAUGE_HVR4000LITE:
+ case CX88_BOARD_HAUPPAUGE_IRONLY:
+ if (!core->i2c_rc)
+ hauppauge_eeprom(core, eeprom);
+ break;
+ case CX88_BOARD_KWORLD_DVBS_100:
+ cx_write(MO_GP0_IO, 0x000007f8);
+ cx_write(MO_GP1_IO, 0x00000001);
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO:
+ /* GPIO0:0 is hooked to demod reset */
+ /* GPIO0:4 is hooked to xc3028 reset */
+ cx_write(MO_GP0_IO, 0x00111100);
+ usleep_range(10000, 20000);
+ cx_write(MO_GP0_IO, 0x00111111);
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL:
+ /* GPIO0:6 is hooked to FX2 reset pin */
+ cx_set(MO_GP0_IO, 0x00004040);
+ cx_clear(MO_GP0_IO, 0x00000040);
+ msleep(1000);
+ cx_set(MO_GP0_IO, 0x00004040);
+ fallthrough;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1:
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS:
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID:
+ /* GPIO0:0 is hooked to mt352 reset pin */
+ cx_set(MO_GP0_IO, 0x00000101);
+ cx_clear(MO_GP0_IO, 0x00000001);
+ usleep_range(10000, 20000);
+ cx_set(MO_GP0_IO, 0x00000101);
+ if (!core->i2c_rc &&
+ core->boardnr == CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID)
+ dvico_fusionhdtv_hybrid_init(core);
+ break;
+ case CX88_BOARD_KWORLD_DVB_T:
+ case CX88_BOARD_DNTV_LIVE_DVB_T:
+ cx_set(MO_GP0_IO, 0x00000707);
+ cx_set(MO_GP2_IO, 0x00000101);
+ cx_clear(MO_GP2_IO, 0x00000001);
+ usleep_range(10000, 20000);
+ cx_clear(MO_GP0_IO, 0x00000007);
+ cx_set(MO_GP2_IO, 0x00000101);
+ break;
+ case CX88_BOARD_DNTV_LIVE_DVB_T_PRO:
+ cx_write(MO_GP0_IO, 0x00080808);
+ break;
+ case CX88_BOARD_ATI_HDTVWONDER:
+ if (!core->i2c_rc) {
+ /* enable tuner */
+ int i;
+ static const u8 buffer[][2] = {
+ {0x10, 0x12},
+ {0x13, 0x04},
+ {0x16, 0x00},
+ {0x14, 0x04},
+ {0x17, 0x00}
+ };
+ core->i2c_client.addr = 0x0a;
+
+ for (i = 0; i < ARRAY_SIZE(buffer); i++)
+ if (i2c_master_send(&core->i2c_client,
+ buffer[i], 2) != 2)
+ pr_warn("Unable to enable tuner(%i).\n",
+ i);
+ }
+ break;
+ case CX88_BOARD_MSI_TVANYWHERE_MASTER:
+ {
+ struct v4l2_priv_tun_config tea5767_cfg;
+ struct tea5767_ctrl ctl;
+
+ memset(&ctl, 0, sizeof(ctl));
+
+ ctl.high_cut = 1;
+ ctl.st_noise = 1;
+ ctl.deemph_75 = 1;
+ ctl.xtal_freq = TEA5767_HIGH_LO_13MHz;
+
+ tea5767_cfg.tuner = TUNER_TEA5767;
+ tea5767_cfg.priv = &ctl;
+
+ call_all(core, tuner, s_config, &tea5767_cfg);
+ break;
+ }
+ case CX88_BOARD_TEVII_S420:
+ case CX88_BOARD_TEVII_S460:
+ case CX88_BOARD_TEVII_S464:
+ case CX88_BOARD_OMICOM_SS4_PCI:
+ case CX88_BOARD_TBS_8910:
+ case CX88_BOARD_TBS_8920:
+ case CX88_BOARD_PROF_6200:
+ case CX88_BOARD_PROF_7300:
+ case CX88_BOARD_PROF_7301:
+ case CX88_BOARD_SATTRADE_ST4200:
+ cx_write(MO_GP0_IO, 0x8000);
+ msleep(100);
+ cx_write(MO_SRST_IO, 0);
+ usleep_range(10000, 20000);
+ cx_write(MO_GP0_IO, 0x8080);
+ msleep(100);
+ cx_write(MO_SRST_IO, 1);
+ msleep(100);
+ break;
+ } /*end switch() */
+
+ /* Setup tuners */
+ if (core->board.radio_type != UNSET) {
+ tun_setup.mode_mask = T_RADIO;
+ tun_setup.type = core->board.radio_type;
+ tun_setup.addr = core->board.radio_addr;
+ tun_setup.tuner_callback = cx88_tuner_callback;
+ call_all(core, tuner, s_type_addr, &tun_setup);
+ mode_mask &= ~T_RADIO;
+ }
+
+ if (core->board.tuner_type != UNSET) {
+ tun_setup.mode_mask = mode_mask;
+ tun_setup.type = core->board.tuner_type;
+ tun_setup.addr = core->board.tuner_addr;
+ tun_setup.tuner_callback = cx88_tuner_callback;
+
+ call_all(core, tuner, s_type_addr, &tun_setup);
+ }
+
+ if (core->board.tda9887_conf) {
+ struct v4l2_priv_tun_config tda9887_cfg;
+
+ tda9887_cfg.tuner = TUNER_TDA9887;
+ tda9887_cfg.priv = &core->board.tda9887_conf;
+
+ call_all(core, tuner, s_config, &tda9887_cfg);
+ }
+
+ if (core->board.tuner_type == TUNER_XC2028) {
+ struct v4l2_priv_tun_config xc2028_cfg;
+ struct xc2028_ctrl ctl;
+
+ /* Fills device-dependent initialization parameters */
+ cx88_setup_xc3028(core, &ctl);
+
+ /* Sends parameters to xc2028/3028 tuner */
+ memset(&xc2028_cfg, 0, sizeof(xc2028_cfg));
+ xc2028_cfg.tuner = TUNER_XC2028;
+ xc2028_cfg.priv = &ctl;
+ dprintk(1, "Asking xc2028/3028 to load firmware %s\n",
+ ctl.fname);
+ call_all(core, tuner, s_config, &xc2028_cfg);
+ }
+ call_all(core, tuner, standby);
+}
+
+/* ------------------------------------------------------------------ */
+
+static int cx88_pci_quirks(const char *name, struct pci_dev *pci)
+{
+ unsigned int lat = UNSET;
+ u8 ctrl = 0;
+ u8 value;
+
+ /* check pci quirks */
+ if (pci_pci_problems & PCIPCI_TRITON) {
+ pr_info("quirk: PCIPCI_TRITON -- set TBFX\n");
+ ctrl |= CX88X_EN_TBFX;
+ }
+ if (pci_pci_problems & PCIPCI_NATOMA) {
+ pr_info("quirk: PCIPCI_NATOMA -- set TBFX\n");
+ ctrl |= CX88X_EN_TBFX;
+ }
+ if (pci_pci_problems & PCIPCI_VIAETBF) {
+ pr_info("quirk: PCIPCI_VIAETBF -- set TBFX\n");
+ ctrl |= CX88X_EN_TBFX;
+ }
+ if (pci_pci_problems & PCIPCI_VSFX) {
+ pr_info("quirk: PCIPCI_VSFX -- set VSFX\n");
+ ctrl |= CX88X_EN_VSFX;
+ }
+#ifdef PCIPCI_ALIMAGIK
+ if (pci_pci_problems & PCIPCI_ALIMAGIK) {
+ pr_info("quirk: PCIPCI_ALIMAGIK -- latency fixup\n");
+ lat = 0x0A;
+ }
+#endif
+
+ /* check insmod options */
+ if (latency != UNSET)
+ lat = latency;
+
+ /* apply stuff */
+ if (ctrl) {
+ pci_read_config_byte(pci, CX88X_DEVCTRL, &value);
+ value |= ctrl;
+ pci_write_config_byte(pci, CX88X_DEVCTRL, value);
+ }
+ if (lat != UNSET) {
+ pr_info("setting pci latency timer to %d\n", latency);
+ pci_write_config_byte(pci, PCI_LATENCY_TIMER, latency);
+ }
+ return 0;
+}
+
+int cx88_get_resources(const struct cx88_core *core, struct pci_dev *pci)
+{
+ if (request_mem_region(pci_resource_start(pci, 0),
+ pci_resource_len(pci, 0),
+ core->name))
+ return 0;
+ pr_err("func %d: Can't get MMIO memory @ 0x%llx, subsystem: %04x:%04x\n",
+ PCI_FUNC(pci->devfn),
+ (unsigned long long)pci_resource_start(pci, 0),
+ pci->subsystem_vendor, pci->subsystem_device);
+ return -EBUSY;
+}
+
+/*
+ * Allocate and initialize the cx88 core struct. One should hold the
+ * devlist mutex before calling this.
+ */
+struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr)
+{
+ struct cx88_core *core;
+ int i;
+
+ core = kzalloc(sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return NULL;
+
+ refcount_set(&core->refcount, 1);
+ core->pci_bus = pci->bus->number;
+ core->pci_slot = PCI_SLOT(pci->devfn);
+ core->pci_irqmask = PCI_INT_RISC_RD_BERRINT | PCI_INT_RISC_WR_BERRINT |
+ PCI_INT_BRDG_BERRINT | PCI_INT_SRC_DMA_BERRINT |
+ PCI_INT_DST_DMA_BERRINT | PCI_INT_IPB_DMA_BERRINT;
+ mutex_init(&core->lock);
+
+ core->nr = nr;
+ sprintf(core->name, "cx88[%d]", core->nr);
+
+ /*
+ * Note: Setting initial standard here would cause first call to
+ * cx88_set_tvnorm() to return without programming any registers. Leave
+ * it blank for at this point and it will get set later in
+ * cx8800_initdev()
+ */
+ core->tvnorm = 0;
+
+ core->width = 320;
+ core->height = 240;
+ core->field = V4L2_FIELD_INTERLACED;
+
+ strscpy(core->v4l2_dev.name, core->name, sizeof(core->v4l2_dev.name));
+ if (v4l2_device_register(NULL, &core->v4l2_dev)) {
+ kfree(core);
+ return NULL;
+ }
+
+ if (v4l2_ctrl_handler_init(&core->video_hdl, 13)) {
+ v4l2_device_unregister(&core->v4l2_dev);
+ kfree(core);
+ return NULL;
+ }
+
+ if (v4l2_ctrl_handler_init(&core->audio_hdl, 13)) {
+ v4l2_ctrl_handler_free(&core->video_hdl);
+ v4l2_device_unregister(&core->v4l2_dev);
+ kfree(core);
+ return NULL;
+ }
+
+ if (cx88_get_resources(core, pci) != 0) {
+ v4l2_ctrl_handler_free(&core->video_hdl);
+ v4l2_ctrl_handler_free(&core->audio_hdl);
+ v4l2_device_unregister(&core->v4l2_dev);
+ kfree(core);
+ return NULL;
+ }
+
+ /* PCI stuff */
+ cx88_pci_quirks(core->name, pci);
+ core->lmmio = ioremap(pci_resource_start(pci, 0),
+ pci_resource_len(pci, 0));
+ core->bmmio = (u8 __iomem *)core->lmmio;
+
+ if (!core->lmmio) {
+ release_mem_region(pci_resource_start(pci, 0),
+ pci_resource_len(pci, 0));
+ v4l2_ctrl_handler_free(&core->video_hdl);
+ v4l2_ctrl_handler_free(&core->audio_hdl);
+ v4l2_device_unregister(&core->v4l2_dev);
+ kfree(core);
+ return NULL;
+ }
+
+ /* board config */
+ core->boardnr = UNSET;
+ if (card[core->nr] < ARRAY_SIZE(cx88_boards))
+ core->boardnr = card[core->nr];
+ for (i = 0; core->boardnr == UNSET && i < ARRAY_SIZE(cx88_subids); i++)
+ if (pci->subsystem_vendor == cx88_subids[i].subvendor &&
+ pci->subsystem_device == cx88_subids[i].subdevice)
+ core->boardnr = cx88_subids[i].card;
+ if (core->boardnr == UNSET) {
+ core->boardnr = CX88_BOARD_UNKNOWN;
+ cx88_card_list(core, pci);
+ }
+
+ core->board = cx88_boards[core->boardnr];
+
+ if (!core->board.num_frontends && (core->board.mpeg & CX88_MPEG_DVB))
+ core->board.num_frontends = 1;
+
+ pr_info("subsystem: %04x:%04x, board: %s [card=%d,%s], frontend(s): %d\n",
+ pci->subsystem_vendor, pci->subsystem_device, core->board.name,
+ core->boardnr, card[core->nr] == core->boardnr ?
+ "insmod option" : "autodetected",
+ core->board.num_frontends);
+
+ if (tuner[core->nr] != UNSET)
+ core->board.tuner_type = tuner[core->nr];
+ if (radio[core->nr] != UNSET)
+ core->board.radio_type = radio[core->nr];
+
+ dprintk(1, "TV tuner type %d, Radio tuner type %d\n",
+ core->board.tuner_type, core->board.radio_type);
+
+ /* init hardware */
+ cx88_reset(core);
+ cx88_card_setup_pre_i2c(core);
+ cx88_i2c_init(core, pci);
+
+ /* load tuner module, if needed */
+ if (core->board.tuner_type != UNSET) {
+ /*
+ * Ignore 0x6b and 0x6f on cx88 boards.
+ * FusionHDTV5 RT Gold has an ir receiver at 0x6b
+ * and an RTC at 0x6f which can get corrupted if probed.
+ */
+ static const unsigned short tv_addrs[] = {
+ 0x42, 0x43, 0x4a, 0x4b, /* tda8290 */
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e,
+ I2C_CLIENT_END
+ };
+ int has_demod = (core->board.tda9887_conf & TDA9887_PRESENT);
+
+ /*
+ * I don't trust the radio_type as is stored in the card
+ * definitions, so we just probe for it.
+ * The radio_type is sometimes missing, or set to UNSET but
+ * later code configures a tea5767.
+ */
+ v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap,
+ "tuner", 0,
+ v4l2_i2c_tuner_addrs(ADDRS_RADIO));
+ if (has_demod)
+ v4l2_i2c_new_subdev(&core->v4l2_dev,
+ &core->i2c_adap, "tuner",
+ 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD));
+ if (core->board.tuner_addr == ADDR_UNSET) {
+ v4l2_i2c_new_subdev(&core->v4l2_dev,
+ &core->i2c_adap, "tuner",
+ 0, has_demod ? tv_addrs + 4 : tv_addrs);
+ } else {
+ v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap,
+ "tuner", core->board.tuner_addr,
+ NULL);
+ }
+ }
+
+ cx88_card_setup(core);
+ if (!disable_ir) {
+ cx88_i2c_init_ir(core);
+ cx88_ir_init(core, pci);
+ }
+
+ return core;
+}
diff --git a/drivers/media/pci/cx88/cx88-core.c b/drivers/media/pci/cx88/cx88-core.c
new file mode 100644
index 000000000..52be42f9a
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-core.c
@@ -0,0 +1,1099 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * device driver for Conexant 2388x based TV cards
+ * driver core
+ *
+ * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
+ *
+ * (c) 2005-2006 Mauro Carvalho Chehab <mchehab@kernel.org>
+ * - Multituner support
+ * - video_ioctl2 conversion
+ * - PAL/M fixes
+ */
+
+#include "cx88.h"
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/kmod.h>
+#include <linux/sound.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards");
+MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+MODULE_LICENSE("GPL v2");
+
+/* ------------------------------------------------------------------ */
+
+unsigned int cx88_core_debug;
+module_param_named(core_debug, cx88_core_debug, int, 0644);
+MODULE_PARM_DESC(core_debug, "enable debug messages [core]");
+
+static unsigned int nicam;
+module_param(nicam, int, 0644);
+MODULE_PARM_DESC(nicam, "tv audio is nicam");
+
+static unsigned int nocomb;
+module_param(nocomb, int, 0644);
+MODULE_PARM_DESC(nocomb, "disable comb filter");
+
+#define dprintk0(fmt, arg...) \
+ printk(KERN_DEBUG pr_fmt("%s: core:" fmt), \
+ __func__, ##arg) \
+
+#define dprintk(level, fmt, arg...) do { \
+ if (cx88_core_debug >= level) \
+ printk(KERN_DEBUG pr_fmt("%s: core:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+static unsigned int cx88_devcount;
+static LIST_HEAD(cx88_devlist);
+static DEFINE_MUTEX(devlist);
+
+#define NO_SYNC_LINE (-1U)
+
+/*
+ * @lpi: lines per IRQ, or 0 to not generate irqs. Note: IRQ to be
+ * generated _after_ lpi lines are transferred.
+ */
+static __le32 *cx88_risc_field(__le32 *rp, struct scatterlist *sglist,
+ unsigned int offset, u32 sync_line,
+ unsigned int bpl, unsigned int padding,
+ unsigned int lines, unsigned int lpi, bool jump)
+{
+ struct scatterlist *sg;
+ unsigned int line, todo, sol;
+
+ if (jump) {
+ (*rp++) = cpu_to_le32(RISC_JUMP);
+ (*rp++) = 0;
+ }
+
+ /* sync instruction */
+ if (sync_line != NO_SYNC_LINE)
+ *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line);
+
+ /* scan lines */
+ sg = sglist;
+ for (line = 0; line < lines; line++) {
+ while (offset && offset >= sg_dma_len(sg)) {
+ offset -= sg_dma_len(sg);
+ sg = sg_next(sg);
+ }
+ if (lpi && line > 0 && !(line % lpi))
+ sol = RISC_SOL | RISC_IRQ1 | RISC_CNT_INC;
+ else
+ sol = RISC_SOL;
+ if (bpl <= sg_dma_len(sg) - offset) {
+ /* fits into current chunk */
+ *(rp++) = cpu_to_le32(RISC_WRITE | sol |
+ RISC_EOL | bpl);
+ *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
+ offset += bpl;
+ } else {
+ /* scanline needs to be split */
+ todo = bpl;
+ *(rp++) = cpu_to_le32(RISC_WRITE | sol |
+ (sg_dma_len(sg) - offset));
+ *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
+ todo -= (sg_dma_len(sg) - offset);
+ offset = 0;
+ sg = sg_next(sg);
+ while (todo > sg_dma_len(sg)) {
+ *(rp++) = cpu_to_le32(RISC_WRITE |
+ sg_dma_len(sg));
+ *(rp++) = cpu_to_le32(sg_dma_address(sg));
+ todo -= sg_dma_len(sg);
+ sg = sg_next(sg);
+ }
+ *(rp++) = cpu_to_le32(RISC_WRITE | RISC_EOL | todo);
+ *(rp++) = cpu_to_le32(sg_dma_address(sg));
+ offset += todo;
+ }
+ offset += padding;
+ }
+
+ return rp;
+}
+
+int cx88_risc_buffer(struct pci_dev *pci, struct cx88_riscmem *risc,
+ struct scatterlist *sglist,
+ unsigned int top_offset, unsigned int bottom_offset,
+ unsigned int bpl, unsigned int padding, unsigned int lines)
+{
+ u32 instructions, fields;
+ __le32 *rp;
+
+ fields = 0;
+ if (top_offset != UNSET)
+ fields++;
+ if (bottom_offset != UNSET)
+ fields++;
+
+ /*
+ * estimate risc mem: worst case is one write per page border +
+ * one write per scan line + syncs + jump (all 2 dwords). Padding
+ * can cause next bpl to start close to a page border. First DMA
+ * region may be smaller than PAGE_SIZE
+ */
+ instructions = fields * (1 + ((bpl + padding) * lines) /
+ PAGE_SIZE + lines);
+ instructions += 4;
+ risc->size = instructions * 8;
+ risc->dma = 0;
+ risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma,
+ GFP_KERNEL);
+ if (!risc->cpu)
+ return -ENOMEM;
+
+ /* write risc instructions */
+ rp = risc->cpu;
+ if (top_offset != UNSET)
+ rp = cx88_risc_field(rp, sglist, top_offset, 0,
+ bpl, padding, lines, 0, true);
+ if (bottom_offset != UNSET)
+ rp = cx88_risc_field(rp, sglist, bottom_offset, 0x200,
+ bpl, padding, lines, 0,
+ top_offset == UNSET);
+
+ /* save pointer to jmp instruction address */
+ risc->jmp = rp;
+ WARN_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size);
+ return 0;
+}
+EXPORT_SYMBOL(cx88_risc_buffer);
+
+int cx88_risc_databuffer(struct pci_dev *pci, struct cx88_riscmem *risc,
+ struct scatterlist *sglist, unsigned int bpl,
+ unsigned int lines, unsigned int lpi)
+{
+ u32 instructions;
+ __le32 *rp;
+
+ /*
+ * estimate risc mem: worst case is one write per page border +
+ * one write per scan line + syncs + jump (all 2 dwords). Here
+ * there is no padding and no sync. First DMA region may be smaller
+ * than PAGE_SIZE
+ */
+ instructions = 1 + (bpl * lines) / PAGE_SIZE + lines;
+ instructions += 3;
+ risc->size = instructions * 8;
+ risc->dma = 0;
+ risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma,
+ GFP_KERNEL);
+ if (!risc->cpu)
+ return -ENOMEM;
+
+ /* write risc instructions */
+ rp = risc->cpu;
+ rp = cx88_risc_field(rp, sglist, 0, NO_SYNC_LINE, bpl, 0,
+ lines, lpi, !lpi);
+
+ /* save pointer to jmp instruction address */
+ risc->jmp = rp;
+ WARN_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size);
+ return 0;
+}
+EXPORT_SYMBOL(cx88_risc_databuffer);
+
+/*
+ * our SRAM memory layout
+ */
+
+/*
+ * we are going to put all thr risc programs into host memory, so we
+ * can use the whole SDRAM for the DMA fifos. To simplify things, we
+ * use a static memory layout. That surely will waste memory in case
+ * we don't use all DMA channels at the same time (which will be the
+ * case most of the time). But that still gives us enough FIFO space
+ * to be able to deal with insane long pci latencies ...
+ *
+ * FIFO space allocations:
+ * channel 21 (y video) - 10.0k
+ * channel 22 (u video) - 2.0k
+ * channel 23 (v video) - 2.0k
+ * channel 24 (vbi) - 4.0k
+ * channels 25+26 (audio) - 4.0k
+ * channel 28 (mpeg) - 4.0k
+ * channel 27 (audio rds)- 3.0k
+ * TOTAL = 29.0k
+ *
+ * Every channel has 160 bytes control data (64 bytes instruction
+ * queue and 6 CDT entries), which is close to 2k total.
+ *
+ * Address layout:
+ * 0x0000 - 0x03ff CMDs / reserved
+ * 0x0400 - 0x0bff instruction queues + CDs
+ * 0x0c00 - FIFOs
+ */
+
+const struct sram_channel cx88_sram_channels[] = {
+ [SRAM_CH21] = {
+ .name = "video y / packed",
+ .cmds_start = 0x180040,
+ .ctrl_start = 0x180400,
+ .cdt = 0x180400 + 64,
+ .fifo_start = 0x180c00,
+ .fifo_size = 0x002800,
+ .ptr1_reg = MO_DMA21_PTR1,
+ .ptr2_reg = MO_DMA21_PTR2,
+ .cnt1_reg = MO_DMA21_CNT1,
+ .cnt2_reg = MO_DMA21_CNT2,
+ },
+ [SRAM_CH22] = {
+ .name = "video u",
+ .cmds_start = 0x180080,
+ .ctrl_start = 0x1804a0,
+ .cdt = 0x1804a0 + 64,
+ .fifo_start = 0x183400,
+ .fifo_size = 0x000800,
+ .ptr1_reg = MO_DMA22_PTR1,
+ .ptr2_reg = MO_DMA22_PTR2,
+ .cnt1_reg = MO_DMA22_CNT1,
+ .cnt2_reg = MO_DMA22_CNT2,
+ },
+ [SRAM_CH23] = {
+ .name = "video v",
+ .cmds_start = 0x1800c0,
+ .ctrl_start = 0x180540,
+ .cdt = 0x180540 + 64,
+ .fifo_start = 0x183c00,
+ .fifo_size = 0x000800,
+ .ptr1_reg = MO_DMA23_PTR1,
+ .ptr2_reg = MO_DMA23_PTR2,
+ .cnt1_reg = MO_DMA23_CNT1,
+ .cnt2_reg = MO_DMA23_CNT2,
+ },
+ [SRAM_CH24] = {
+ .name = "vbi",
+ .cmds_start = 0x180100,
+ .ctrl_start = 0x1805e0,
+ .cdt = 0x1805e0 + 64,
+ .fifo_start = 0x184400,
+ .fifo_size = 0x001000,
+ .ptr1_reg = MO_DMA24_PTR1,
+ .ptr2_reg = MO_DMA24_PTR2,
+ .cnt1_reg = MO_DMA24_CNT1,
+ .cnt2_reg = MO_DMA24_CNT2,
+ },
+ [SRAM_CH25] = {
+ .name = "audio from",
+ .cmds_start = 0x180140,
+ .ctrl_start = 0x180680,
+ .cdt = 0x180680 + 64,
+ .fifo_start = 0x185400,
+ .fifo_size = 0x001000,
+ .ptr1_reg = MO_DMA25_PTR1,
+ .ptr2_reg = MO_DMA25_PTR2,
+ .cnt1_reg = MO_DMA25_CNT1,
+ .cnt2_reg = MO_DMA25_CNT2,
+ },
+ [SRAM_CH26] = {
+ .name = "audio to",
+ .cmds_start = 0x180180,
+ .ctrl_start = 0x180720,
+ .cdt = 0x180680 + 64, /* same as audio IN */
+ .fifo_start = 0x185400, /* same as audio IN */
+ .fifo_size = 0x001000, /* same as audio IN */
+ .ptr1_reg = MO_DMA26_PTR1,
+ .ptr2_reg = MO_DMA26_PTR2,
+ .cnt1_reg = MO_DMA26_CNT1,
+ .cnt2_reg = MO_DMA26_CNT2,
+ },
+ [SRAM_CH28] = {
+ .name = "mpeg",
+ .cmds_start = 0x180200,
+ .ctrl_start = 0x1807C0,
+ .cdt = 0x1807C0 + 64,
+ .fifo_start = 0x186400,
+ .fifo_size = 0x001000,
+ .ptr1_reg = MO_DMA28_PTR1,
+ .ptr2_reg = MO_DMA28_PTR2,
+ .cnt1_reg = MO_DMA28_CNT1,
+ .cnt2_reg = MO_DMA28_CNT2,
+ },
+ [SRAM_CH27] = {
+ .name = "audio rds",
+ .cmds_start = 0x1801C0,
+ .ctrl_start = 0x180860,
+ .cdt = 0x180860 + 64,
+ .fifo_start = 0x187400,
+ .fifo_size = 0x000C00,
+ .ptr1_reg = MO_DMA27_PTR1,
+ .ptr2_reg = MO_DMA27_PTR2,
+ .cnt1_reg = MO_DMA27_CNT1,
+ .cnt2_reg = MO_DMA27_CNT2,
+ },
+};
+EXPORT_SYMBOL(cx88_sram_channels);
+
+int cx88_sram_channel_setup(struct cx88_core *core,
+ const struct sram_channel *ch,
+ unsigned int bpl, u32 risc)
+{
+ unsigned int i, lines;
+ u32 cdt;
+
+ bpl = (bpl + 7) & ~7; /* alignment */
+ cdt = ch->cdt;
+ lines = ch->fifo_size / bpl;
+ if (lines > 6)
+ lines = 6;
+ WARN_ON(lines < 2);
+
+ /* write CDT */
+ for (i = 0; i < lines; i++)
+ cx_write(cdt + 16 * i, ch->fifo_start + bpl * i);
+
+ /* write CMDS */
+ cx_write(ch->cmds_start + 0, risc);
+ cx_write(ch->cmds_start + 4, cdt);
+ cx_write(ch->cmds_start + 8, (lines * 16) >> 3);
+ cx_write(ch->cmds_start + 12, ch->ctrl_start);
+ cx_write(ch->cmds_start + 16, 64 >> 2);
+ for (i = 20; i < 64; i += 4)
+ cx_write(ch->cmds_start + i, 0);
+
+ /* fill registers */
+ cx_write(ch->ptr1_reg, ch->fifo_start);
+ cx_write(ch->ptr2_reg, cdt);
+ cx_write(ch->cnt1_reg, (bpl >> 3) - 1);
+ cx_write(ch->cnt2_reg, (lines * 16) >> 3);
+
+ dprintk(2, "sram setup %s: bpl=%d lines=%d\n", ch->name, bpl, lines);
+ return 0;
+}
+EXPORT_SYMBOL(cx88_sram_channel_setup);
+
+/* ------------------------------------------------------------------ */
+/* debug helper code */
+
+static int cx88_risc_decode(u32 risc)
+{
+ static const char * const instr[16] = {
+ [RISC_SYNC >> 28] = "sync",
+ [RISC_WRITE >> 28] = "write",
+ [RISC_WRITEC >> 28] = "writec",
+ [RISC_READ >> 28] = "read",
+ [RISC_READC >> 28] = "readc",
+ [RISC_JUMP >> 28] = "jump",
+ [RISC_SKIP >> 28] = "skip",
+ [RISC_WRITERM >> 28] = "writerm",
+ [RISC_WRITECM >> 28] = "writecm",
+ [RISC_WRITECR >> 28] = "writecr",
+ };
+ static int const incr[16] = {
+ [RISC_WRITE >> 28] = 2,
+ [RISC_JUMP >> 28] = 2,
+ [RISC_WRITERM >> 28] = 3,
+ [RISC_WRITECM >> 28] = 3,
+ [RISC_WRITECR >> 28] = 4,
+ };
+ static const char * const bits[] = {
+ "12", "13", "14", "resync",
+ "cnt0", "cnt1", "18", "19",
+ "20", "21", "22", "23",
+ "irq1", "irq2", "eol", "sol",
+ };
+ int i;
+
+ dprintk0("0x%08x [ %s", risc,
+ instr[risc >> 28] ? instr[risc >> 28] : "INVALID");
+ for (i = ARRAY_SIZE(bits) - 1; i >= 0; i--)
+ if (risc & (1 << (i + 12)))
+ pr_cont(" %s", bits[i]);
+ pr_cont(" count=%d ]\n", risc & 0xfff);
+ return incr[risc >> 28] ? incr[risc >> 28] : 1;
+}
+
+void cx88_sram_channel_dump(struct cx88_core *core,
+ const struct sram_channel *ch)
+{
+ static const char * const name[] = {
+ "initial risc",
+ "cdt base",
+ "cdt size",
+ "iq base",
+ "iq size",
+ "risc pc",
+ "iq wr ptr",
+ "iq rd ptr",
+ "cdt current",
+ "pci target",
+ "line / byte",
+ };
+ u32 risc;
+ unsigned int i, j, n;
+
+ dprintk0("%s - dma channel status dump\n", ch->name);
+ for (i = 0; i < ARRAY_SIZE(name); i++)
+ dprintk0(" cmds: %-12s: 0x%08x\n",
+ name[i], cx_read(ch->cmds_start + 4 * i));
+ for (n = 1, i = 0; i < 4; i++) {
+ risc = cx_read(ch->cmds_start + 4 * (i + 11));
+ pr_cont(" risc%d: ", i);
+ if (--n)
+ pr_cont("0x%08x [ arg #%d ]\n", risc, n);
+ else
+ n = cx88_risc_decode(risc);
+ }
+ for (i = 0; i < 16; i += n) {
+ risc = cx_read(ch->ctrl_start + 4 * i);
+ dprintk0(" iq %x: ", i);
+ n = cx88_risc_decode(risc);
+ for (j = 1; j < n; j++) {
+ risc = cx_read(ch->ctrl_start + 4 * (i + j));
+ pr_cont(" iq %x: 0x%08x [ arg #%d ]\n",
+ i + j, risc, j);
+ }
+ }
+
+ dprintk0("fifo: 0x%08x -> 0x%x\n",
+ ch->fifo_start, ch->fifo_start + ch->fifo_size);
+ dprintk0("ctrl: 0x%08x -> 0x%x\n",
+ ch->ctrl_start, ch->ctrl_start + 6 * 16);
+ dprintk0(" ptr1_reg: 0x%08x\n", cx_read(ch->ptr1_reg));
+ dprintk0(" ptr2_reg: 0x%08x\n", cx_read(ch->ptr2_reg));
+ dprintk0(" cnt1_reg: 0x%08x\n", cx_read(ch->cnt1_reg));
+ dprintk0(" cnt2_reg: 0x%08x\n", cx_read(ch->cnt2_reg));
+}
+EXPORT_SYMBOL(cx88_sram_channel_dump);
+
+static const char *cx88_pci_irqs[32] = {
+ "vid", "aud", "ts", "vip", "hst", "5", "6", "tm1",
+ "src_dma", "dst_dma", "risc_rd_err", "risc_wr_err",
+ "brdg_err", "src_dma_err", "dst_dma_err", "ipb_dma_err",
+ "i2c", "i2c_rack", "ir_smp", "gpio0", "gpio1"
+};
+
+void cx88_print_irqbits(const char *tag, const char *strings[],
+ int len, u32 bits, u32 mask)
+{
+ unsigned int i;
+
+ dprintk0("%s [0x%x]", tag, bits);
+ for (i = 0; i < len; i++) {
+ if (!(bits & (1 << i)))
+ continue;
+ if (strings[i])
+ pr_cont(" %s", strings[i]);
+ else
+ pr_cont(" %d", i);
+ if (!(mask & (1 << i)))
+ continue;
+ pr_cont("*");
+ }
+ pr_cont("\n");
+}
+EXPORT_SYMBOL(cx88_print_irqbits);
+
+/* ------------------------------------------------------------------ */
+
+int cx88_core_irq(struct cx88_core *core, u32 status)
+{
+ int handled = 0;
+
+ if (status & PCI_INT_IR_SMPINT) {
+ cx88_ir_irq(core);
+ handled++;
+ }
+ if (!handled)
+ cx88_print_irqbits("irq pci",
+ cx88_pci_irqs, ARRAY_SIZE(cx88_pci_irqs),
+ status, core->pci_irqmask);
+ return handled;
+}
+EXPORT_SYMBOL(cx88_core_irq);
+
+void cx88_wakeup(struct cx88_core *core,
+ struct cx88_dmaqueue *q, u32 count)
+{
+ struct cx88_buffer *buf;
+
+ buf = list_entry(q->active.next,
+ struct cx88_buffer, list);
+ buf->vb.vb2_buf.timestamp = ktime_get_ns();
+ buf->vb.field = core->field;
+ buf->vb.sequence = q->count++;
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+}
+EXPORT_SYMBOL(cx88_wakeup);
+
+void cx88_shutdown(struct cx88_core *core)
+{
+ /* disable RISC controller + IRQs */
+ cx_write(MO_DEV_CNTRL2, 0);
+
+ /* stop dma transfers */
+ cx_write(MO_VID_DMACNTRL, 0x0);
+ cx_write(MO_AUD_DMACNTRL, 0x0);
+ cx_write(MO_TS_DMACNTRL, 0x0);
+ cx_write(MO_VIP_DMACNTRL, 0x0);
+ cx_write(MO_GPHST_DMACNTRL, 0x0);
+
+ /* stop interrupts */
+ cx_write(MO_PCI_INTMSK, 0x0);
+ cx_write(MO_VID_INTMSK, 0x0);
+ cx_write(MO_AUD_INTMSK, 0x0);
+ cx_write(MO_TS_INTMSK, 0x0);
+ cx_write(MO_VIP_INTMSK, 0x0);
+ cx_write(MO_GPHST_INTMSK, 0x0);
+
+ /* stop capturing */
+ cx_write(VID_CAPTURE_CONTROL, 0);
+}
+EXPORT_SYMBOL(cx88_shutdown);
+
+int cx88_reset(struct cx88_core *core)
+{
+ dprintk(1, "");
+ cx88_shutdown(core);
+
+ /* clear irq status */
+ cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int
+ cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int
+ cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int
+
+ /* wait a bit */
+ msleep(100);
+
+ /* init sram */
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21],
+ 720 * 4, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH22], 128, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH23], 128, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH24], 128, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], 128, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH26], 128, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28],
+ 188 * 4, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH27], 128, 0);
+
+ /* misc init ... */
+ cx_write(MO_INPUT_FORMAT, ((1 << 13) | // agc enable
+ (1 << 12) | // agc gain
+ (1 << 11) | // adaptibe agc
+ (0 << 10) | // chroma agc
+ (0 << 9) | // ckillen
+ (7)));
+
+ /* setup image format */
+ cx_andor(MO_COLOR_CTRL, 0x4000, 0x4000);
+
+ /* setup FIFO Thresholds */
+ cx_write(MO_PDMA_STHRSH, 0x0807);
+ cx_write(MO_PDMA_DTHRSH, 0x0807);
+
+ /* fixes flashing of image */
+ cx_write(MO_AGC_SYNC_TIP1, 0x0380000F);
+ cx_write(MO_AGC_BACK_VBI, 0x00E00555);
+
+ cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int
+ cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int
+ cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int
+
+ /* Reset on-board parts */
+ cx_write(MO_SRST_IO, 0);
+ usleep_range(10000, 20000);
+ cx_write(MO_SRST_IO, 1);
+
+ return 0;
+}
+EXPORT_SYMBOL(cx88_reset);
+
+/* ------------------------------------------------------------------ */
+
+static inline unsigned int norm_swidth(v4l2_std_id norm)
+{
+ if (norm & (V4L2_STD_NTSC | V4L2_STD_PAL_M))
+ return 754;
+
+ if (norm & V4L2_STD_PAL_Nc)
+ return 745;
+
+ return 922;
+}
+
+static inline unsigned int norm_hdelay(v4l2_std_id norm)
+{
+ if (norm & (V4L2_STD_NTSC | V4L2_STD_PAL_M))
+ return 135;
+
+ if (norm & V4L2_STD_PAL_Nc)
+ return 149;
+
+ return 186;
+}
+
+static inline unsigned int norm_vdelay(v4l2_std_id norm)
+{
+ return (norm & V4L2_STD_625_50) ? 0x24 : 0x18;
+}
+
+static inline unsigned int norm_fsc8(v4l2_std_id norm)
+{
+ if (norm & V4L2_STD_PAL_M)
+ return 28604892; // 3.575611 MHz
+
+ if (norm & V4L2_STD_PAL_Nc)
+ return 28656448; // 3.582056 MHz
+
+ if (norm & V4L2_STD_NTSC) // All NTSC/M and variants
+ return 28636360; // 3.57954545 MHz +/- 10 Hz
+
+ /*
+ * SECAM have also different sub carrier for chroma,
+ * but step_db and step_dr, at cx88_set_tvnorm already handles that.
+ *
+ * The same FSC applies to PAL/BGDKIH, PAL/60, NTSC/4.43 and PAL/N
+ */
+
+ return 35468950; // 4.43361875 MHz +/- 5 Hz
+}
+
+static inline unsigned int norm_htotal(v4l2_std_id norm)
+{
+ unsigned int fsc4 = norm_fsc8(norm) / 2;
+
+ /* returns 4*FSC / vtotal / frames per seconds */
+ return (norm & V4L2_STD_625_50) ?
+ ((fsc4 + 312) / 625 + 12) / 25 :
+ ((fsc4 + 262) / 525 * 1001 + 15000) / 30000;
+}
+
+static inline unsigned int norm_vbipack(v4l2_std_id norm)
+{
+ return (norm & V4L2_STD_625_50) ? 511 : 400;
+}
+
+int cx88_set_scale(struct cx88_core *core, unsigned int width,
+ unsigned int height, enum v4l2_field field)
+{
+ unsigned int swidth = norm_swidth(core->tvnorm);
+ unsigned int sheight = norm_maxh(core->tvnorm);
+ u32 value;
+
+ dprintk(1, "set_scale: %dx%d [%s%s,%s]\n", width, height,
+ V4L2_FIELD_HAS_TOP(field) ? "T" : "",
+ V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "",
+ v4l2_norm_to_name(core->tvnorm));
+ if (!V4L2_FIELD_HAS_BOTH(field))
+ height *= 2;
+
+ // recalc H delay and scale registers
+ value = (width * norm_hdelay(core->tvnorm)) / swidth;
+ value &= 0x3fe;
+ cx_write(MO_HDELAY_EVEN, value);
+ cx_write(MO_HDELAY_ODD, value);
+ dprintk(1, "set_scale: hdelay 0x%04x (width %d)\n", value, swidth);
+
+ value = (swidth * 4096 / width) - 4096;
+ cx_write(MO_HSCALE_EVEN, value);
+ cx_write(MO_HSCALE_ODD, value);
+ dprintk(1, "set_scale: hscale 0x%04x\n", value);
+
+ cx_write(MO_HACTIVE_EVEN, width);
+ cx_write(MO_HACTIVE_ODD, width);
+ dprintk(1, "set_scale: hactive 0x%04x\n", width);
+
+ // recalc V scale Register (delay is constant)
+ cx_write(MO_VDELAY_EVEN, norm_vdelay(core->tvnorm));
+ cx_write(MO_VDELAY_ODD, norm_vdelay(core->tvnorm));
+ dprintk(1, "set_scale: vdelay 0x%04x\n", norm_vdelay(core->tvnorm));
+
+ value = (0x10000 - (sheight * 512 / height - 512)) & 0x1fff;
+ cx_write(MO_VSCALE_EVEN, value);
+ cx_write(MO_VSCALE_ODD, value);
+ dprintk(1, "set_scale: vscale 0x%04x\n", value);
+
+ cx_write(MO_VACTIVE_EVEN, sheight);
+ cx_write(MO_VACTIVE_ODD, sheight);
+ dprintk(1, "set_scale: vactive 0x%04x\n", sheight);
+
+ // setup filters
+ value = 0;
+ value |= (1 << 19); // CFILT (default)
+ if (core->tvnorm & V4L2_STD_SECAM) {
+ value |= (1 << 15);
+ value |= (1 << 16);
+ }
+ if (INPUT(core->input).type == CX88_VMUX_SVIDEO)
+ value |= (1 << 13) | (1 << 5);
+ if (field == V4L2_FIELD_INTERLACED)
+ value |= (1 << 3); // VINT (interlaced vertical scaling)
+ if (width < 385)
+ value |= (1 << 0); // 3-tap interpolation
+ if (width < 193)
+ value |= (1 << 1); // 5-tap interpolation
+ if (nocomb)
+ value |= (3 << 5); // disable comb filter
+
+ cx_andor(MO_FILTER_EVEN, 0x7ffc7f, value); /* preserve PEAKEN, PSEL */
+ cx_andor(MO_FILTER_ODD, 0x7ffc7f, value);
+ dprintk(1, "set_scale: filter 0x%04x\n", value);
+
+ return 0;
+}
+EXPORT_SYMBOL(cx88_set_scale);
+
+static const u32 xtal = 28636363;
+
+static int set_pll(struct cx88_core *core, int prescale, u32 ofreq)
+{
+ static const u32 pre[] = { 0, 0, 0, 3, 2, 1 };
+ u64 pll;
+ u32 reg;
+ int i;
+
+ if (prescale < 2)
+ prescale = 2;
+ if (prescale > 5)
+ prescale = 5;
+
+ pll = ofreq * 8 * prescale * (u64)(1 << 20);
+ do_div(pll, xtal);
+ reg = (pll & 0x3ffffff) | (pre[prescale] << 26);
+ if (((reg >> 20) & 0x3f) < 14) {
+ pr_err("pll out of range\n");
+ return -1;
+ }
+
+ dprintk(1, "set_pll: MO_PLL_REG 0x%08x [old=0x%08x,freq=%d]\n",
+ reg, cx_read(MO_PLL_REG), ofreq);
+ cx_write(MO_PLL_REG, reg);
+ for (i = 0; i < 100; i++) {
+ reg = cx_read(MO_DEVICE_STATUS);
+ if (reg & (1 << 2)) {
+ dprintk(1, "pll locked [pre=%d,ofreq=%d]\n",
+ prescale, ofreq);
+ return 0;
+ }
+ dprintk(1, "pll not locked yet, waiting ...\n");
+ usleep_range(10000, 20000);
+ }
+ dprintk(1, "pll NOT locked [pre=%d,ofreq=%d]\n", prescale, ofreq);
+ return -1;
+}
+
+int cx88_start_audio_dma(struct cx88_core *core)
+{
+ /* constant 128 made buzz in analog Nicam-stereo for bigger fifo_size */
+ int bpl = cx88_sram_channels[SRAM_CH25].fifo_size / 4;
+
+ int rds_bpl = cx88_sram_channels[SRAM_CH27].fifo_size / AUD_RDS_LINES;
+
+ /* If downstream RISC is enabled, bail out; ALSA is managing DMA */
+ if (cx_read(MO_AUD_DMACNTRL) & 0x10)
+ return 0;
+
+ /* setup fifo + format */
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], bpl, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH26], bpl, 0);
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH27],
+ rds_bpl, 0);
+
+ cx_write(MO_AUDD_LNGTH, bpl); /* fifo bpl size */
+ cx_write(MO_AUDR_LNGTH, rds_bpl); /* fifo bpl size */
+
+ /* enable Up, Down and Audio RDS fifo */
+ cx_write(MO_AUD_DMACNTRL, 0x0007);
+
+ return 0;
+}
+
+int cx88_stop_audio_dma(struct cx88_core *core)
+{
+ /* If downstream RISC is enabled, bail out; ALSA is managing DMA */
+ if (cx_read(MO_AUD_DMACNTRL) & 0x10)
+ return 0;
+
+ /* stop dma */
+ cx_write(MO_AUD_DMACNTRL, 0x0000);
+
+ return 0;
+}
+
+static int set_tvaudio(struct cx88_core *core)
+{
+ v4l2_std_id norm = core->tvnorm;
+
+ if (INPUT(core->input).type != CX88_VMUX_TELEVISION &&
+ INPUT(core->input).type != CX88_VMUX_CABLE)
+ return 0;
+
+ if (V4L2_STD_PAL_BG & norm) {
+ core->tvaudio = WW_BG;
+
+ } else if (V4L2_STD_PAL_DK & norm) {
+ core->tvaudio = WW_DK;
+
+ } else if (V4L2_STD_PAL_I & norm) {
+ core->tvaudio = WW_I;
+
+ } else if (V4L2_STD_SECAM_L & norm) {
+ core->tvaudio = WW_L;
+
+ } else if ((V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H) &
+ norm) {
+ core->tvaudio = WW_BG;
+
+ } else if (V4L2_STD_SECAM_DK & norm) {
+ core->tvaudio = WW_DK;
+
+ } else if ((V4L2_STD_NTSC_M | V4L2_STD_PAL_M | V4L2_STD_PAL_Nc) &
+ norm) {
+ core->tvaudio = WW_BTSC;
+
+ } else if (V4L2_STD_NTSC_M_JP & norm) {
+ core->tvaudio = WW_EIAJ;
+
+ } else {
+ pr_info("tvaudio support needs work for this tv norm [%s], sorry\n",
+ v4l2_norm_to_name(core->tvnorm));
+ core->tvaudio = WW_NONE;
+ return 0;
+ }
+
+ cx_andor(MO_AFECFG_IO, 0x1f, 0x0);
+ cx88_set_tvaudio(core);
+ /* cx88_set_stereo(dev,V4L2_TUNER_MODE_STEREO); */
+
+/*
+ * This should be needed only on cx88-alsa. It seems that some cx88 chips have
+ * bugs and does require DMA enabled for it to work.
+ */
+ cx88_start_audio_dma(core);
+ return 0;
+}
+
+int cx88_set_tvnorm(struct cx88_core *core, v4l2_std_id norm)
+{
+ u32 fsc8;
+ u32 adc_clock;
+ u32 vdec_clock;
+ u32 step_db, step_dr;
+ u64 tmp64;
+ u32 bdelay, agcdelay, htotal;
+ u32 cxiformat, cxoformat;
+
+ if (norm == core->tvnorm)
+ return 0;
+ if (core->v4ldev && (vb2_is_busy(&core->v4ldev->vb2_vidq) ||
+ vb2_is_busy(&core->v4ldev->vb2_vbiq)))
+ return -EBUSY;
+ if (core->dvbdev && vb2_is_busy(&core->dvbdev->vb2_mpegq))
+ return -EBUSY;
+ core->tvnorm = norm;
+ fsc8 = norm_fsc8(norm);
+ adc_clock = xtal;
+ vdec_clock = fsc8;
+ step_db = fsc8;
+ step_dr = fsc8;
+
+ if (norm & V4L2_STD_NTSC_M_JP) {
+ cxiformat = VideoFormatNTSCJapan;
+ cxoformat = 0x181f0008;
+ } else if (norm & V4L2_STD_NTSC_443) {
+ cxiformat = VideoFormatNTSC443;
+ cxoformat = 0x181f0008;
+ } else if (norm & V4L2_STD_PAL_M) {
+ cxiformat = VideoFormatPALM;
+ cxoformat = 0x1c1f0008;
+ } else if (norm & V4L2_STD_PAL_N) {
+ cxiformat = VideoFormatPALN;
+ cxoformat = 0x1c1f0008;
+ } else if (norm & V4L2_STD_PAL_Nc) {
+ cxiformat = VideoFormatPALNC;
+ cxoformat = 0x1c1f0008;
+ } else if (norm & V4L2_STD_PAL_60) {
+ cxiformat = VideoFormatPAL60;
+ cxoformat = 0x181f0008;
+ } else if (norm & V4L2_STD_NTSC) {
+ cxiformat = VideoFormatNTSC;
+ cxoformat = 0x181f0008;
+ } else if (norm & V4L2_STD_SECAM) {
+ step_db = 4250000 * 8;
+ step_dr = 4406250 * 8;
+
+ cxiformat = VideoFormatSECAM;
+ cxoformat = 0x181f0008;
+ } else { /* PAL */
+ cxiformat = VideoFormatPAL;
+ cxoformat = 0x181f0008;
+ }
+
+ dprintk(1, "set_tvnorm: \"%s\" fsc8=%d adc=%d vdec=%d db/dr=%d/%d\n",
+ v4l2_norm_to_name(core->tvnorm), fsc8, adc_clock, vdec_clock,
+ step_db, step_dr);
+ set_pll(core, 2, vdec_clock);
+
+ dprintk(1, "set_tvnorm: MO_INPUT_FORMAT 0x%08x [old=0x%08x]\n",
+ cxiformat, cx_read(MO_INPUT_FORMAT) & 0x0f);
+ /*
+ * Chroma AGC must be disabled if SECAM is used, we enable it
+ * by default on PAL and NTSC
+ */
+ cx_andor(MO_INPUT_FORMAT, 0x40f,
+ norm & V4L2_STD_SECAM ? cxiformat : cxiformat | 0x400);
+
+ // FIXME: as-is from DScaler
+ dprintk(1, "set_tvnorm: MO_OUTPUT_FORMAT 0x%08x [old=0x%08x]\n",
+ cxoformat, cx_read(MO_OUTPUT_FORMAT));
+ cx_write(MO_OUTPUT_FORMAT, cxoformat);
+
+ // MO_SCONV_REG = adc clock / video dec clock * 2^17
+ tmp64 = adc_clock * (u64)(1 << 17);
+ do_div(tmp64, vdec_clock);
+ dprintk(1, "set_tvnorm: MO_SCONV_REG 0x%08x [old=0x%08x]\n",
+ (u32)tmp64, cx_read(MO_SCONV_REG));
+ cx_write(MO_SCONV_REG, (u32)tmp64);
+
+ // MO_SUB_STEP = 8 * fsc / video dec clock * 2^22
+ tmp64 = step_db * (u64)(1 << 22);
+ do_div(tmp64, vdec_clock);
+ dprintk(1, "set_tvnorm: MO_SUB_STEP 0x%08x [old=0x%08x]\n",
+ (u32)tmp64, cx_read(MO_SUB_STEP));
+ cx_write(MO_SUB_STEP, (u32)tmp64);
+
+ // MO_SUB_STEP_DR = 8 * 4406250 / video dec clock * 2^22
+ tmp64 = step_dr * (u64)(1 << 22);
+ do_div(tmp64, vdec_clock);
+ dprintk(1, "set_tvnorm: MO_SUB_STEP_DR 0x%08x [old=0x%08x]\n",
+ (u32)tmp64, cx_read(MO_SUB_STEP_DR));
+ cx_write(MO_SUB_STEP_DR, (u32)tmp64);
+
+ // bdelay + agcdelay
+ bdelay = vdec_clock * 65 / 20000000 + 21;
+ agcdelay = vdec_clock * 68 / 20000000 + 15;
+ dprintk(1,
+ "set_tvnorm: MO_AGC_BURST 0x%08x [old=0x%08x,bdelay=%d,agcdelay=%d]\n",
+ (bdelay << 8) | agcdelay, cx_read(MO_AGC_BURST),
+ bdelay, agcdelay);
+ cx_write(MO_AGC_BURST, (bdelay << 8) | agcdelay);
+
+ // htotal
+ tmp64 = norm_htotal(norm) * (u64)vdec_clock;
+ do_div(tmp64, fsc8);
+ htotal = (u32)tmp64;
+ dprintk(1,
+ "set_tvnorm: MO_HTOTAL 0x%08x [old=0x%08x,htotal=%d]\n",
+ htotal, cx_read(MO_HTOTAL), (u32)tmp64);
+ cx_andor(MO_HTOTAL, 0x07ff, htotal);
+
+ // vbi stuff, set vbi offset to 10 (for 20 Clk*2 pixels), this makes
+ // the effective vbi offset ~244 samples, the same as the Bt8x8
+ cx_write(MO_VBI_PACKET, (10 << 11) | norm_vbipack(norm));
+
+ // this is needed as well to set all tvnorm parameter
+ cx88_set_scale(core, 320, 240, V4L2_FIELD_INTERLACED);
+
+ // audio
+ set_tvaudio(core);
+
+ // tell i2c chips
+ call_all(core, video, s_std, norm);
+
+ /*
+ * The chroma_agc control should be inaccessible
+ * if the video format is SECAM
+ */
+ v4l2_ctrl_grab(core->chroma_agc, cxiformat == VideoFormatSECAM);
+
+ // done
+ return 0;
+}
+EXPORT_SYMBOL(cx88_set_tvnorm);
+
+/* ------------------------------------------------------------------ */
+
+void cx88_vdev_init(struct cx88_core *core,
+ struct pci_dev *pci,
+ struct video_device *vfd,
+ const struct video_device *template_,
+ const char *type)
+{
+ *vfd = *template_;
+
+ /*
+ * The dev pointer of v4l2_device is NULL, instead we set the
+ * video_device dev_parent pointer to the correct PCI bus device.
+ * This driver is a rare example where there is one v4l2_device,
+ * but the video nodes have different parent (PCI) devices.
+ */
+ vfd->v4l2_dev = &core->v4l2_dev;
+ vfd->dev_parent = &pci->dev;
+ vfd->release = video_device_release_empty;
+ vfd->lock = &core->lock;
+ snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)",
+ core->name, type, core->board.name);
+}
+EXPORT_SYMBOL(cx88_vdev_init);
+
+struct cx88_core *cx88_core_get(struct pci_dev *pci)
+{
+ struct cx88_core *core;
+
+ mutex_lock(&devlist);
+ list_for_each_entry(core, &cx88_devlist, devlist) {
+ if (pci->bus->number != core->pci_bus)
+ continue;
+ if (PCI_SLOT(pci->devfn) != core->pci_slot)
+ continue;
+
+ if (cx88_get_resources(core, pci) != 0) {
+ mutex_unlock(&devlist);
+ return NULL;
+ }
+ refcount_inc(&core->refcount);
+ mutex_unlock(&devlist);
+ return core;
+ }
+
+ core = cx88_core_create(pci, cx88_devcount);
+ if (core) {
+ cx88_devcount++;
+ list_add_tail(&core->devlist, &cx88_devlist);
+ }
+
+ mutex_unlock(&devlist);
+ return core;
+}
+EXPORT_SYMBOL(cx88_core_get);
+
+void cx88_core_put(struct cx88_core *core, struct pci_dev *pci)
+{
+ release_mem_region(pci_resource_start(pci, 0),
+ pci_resource_len(pci, 0));
+
+ if (!refcount_dec_and_test(&core->refcount))
+ return;
+
+ mutex_lock(&devlist);
+ cx88_ir_fini(core);
+ if (core->i2c_rc == 0) {
+ i2c_unregister_device(core->i2c_rtc);
+ i2c_del_adapter(&core->i2c_adap);
+ }
+ list_del(&core->devlist);
+ iounmap(core->lmmio);
+ cx88_devcount--;
+ mutex_unlock(&devlist);
+ v4l2_ctrl_handler_free(&core->video_hdl);
+ v4l2_ctrl_handler_free(&core->audio_hdl);
+ v4l2_device_unregister(&core->v4l2_dev);
+ kfree(core);
+}
+EXPORT_SYMBOL(cx88_core_put);
diff --git a/drivers/media/pci/cx88/cx88-dsp.c b/drivers/media/pci/cx88/cx88-dsp.c
new file mode 100644
index 000000000..e378f3b21
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-dsp.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Stereo and SAP detection for cx88
+ *
+ * Copyright (c) 2009 Marton Balint <cus@fazekas.hu>
+ */
+
+#include "cx88.h"
+#include "cx88-reg.h"
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <asm/div64.h>
+
+#define INT_PI ((s32)(3.141592653589 * 32768.0))
+
+#define compat_remainder(a, b) \
+ ((float)(((s32)((a) * 100)) % ((s32)((b) * 100))) / 100.0)
+
+#define baseband_freq(carrier, srate, tone) ((s32)( \
+ (compat_remainder(carrier + tone, srate)) / srate * 2 * INT_PI))
+
+/*
+ * We calculate the baseband frequencies of the carrier and the pilot tones
+ * based on the sampling rate of the audio rds fifo.
+ */
+
+#define FREQ_A2_CARRIER baseband_freq(54687.5, 2689.36, 0.0)
+#define FREQ_A2_DUAL baseband_freq(54687.5, 2689.36, 274.1)
+#define FREQ_A2_STEREO baseband_freq(54687.5, 2689.36, 117.5)
+
+/*
+ * The frequencies below are from the reference driver. They probably need
+ * further adjustments, because they are not tested at all. You may even need
+ * to play a bit with the registers of the chip to select the proper signal
+ * for the input of the audio rds fifo, and measure it's sampling rate to
+ * calculate the proper baseband frequencies...
+ */
+
+#define FREQ_A2M_CARRIER ((s32)(2.114516 * 32768.0))
+#define FREQ_A2M_DUAL ((s32)(2.754916 * 32768.0))
+#define FREQ_A2M_STEREO ((s32)(2.462326 * 32768.0))
+
+#define FREQ_EIAJ_CARRIER ((s32)(1.963495 * 32768.0)) /* 5pi/8 */
+#define FREQ_EIAJ_DUAL ((s32)(2.562118 * 32768.0))
+#define FREQ_EIAJ_STEREO ((s32)(2.601053 * 32768.0))
+
+#define FREQ_BTSC_DUAL ((s32)(1.963495 * 32768.0)) /* 5pi/8 */
+#define FREQ_BTSC_DUAL_REF ((s32)(1.374446 * 32768.0)) /* 7pi/16 */
+
+#define FREQ_BTSC_SAP ((s32)(2.471532 * 32768.0))
+#define FREQ_BTSC_SAP_REF ((s32)(1.730072 * 32768.0))
+
+/* The spectrum of the signal should be empty between these frequencies. */
+#define FREQ_NOISE_START ((s32)(0.100000 * 32768.0))
+#define FREQ_NOISE_END ((s32)(1.200000 * 32768.0))
+
+static unsigned int dsp_debug;
+module_param(dsp_debug, int, 0644);
+MODULE_PARM_DESC(dsp_debug, "enable audio dsp debug messages");
+
+#define dprintk(level, fmt, arg...) do { \
+ if (dsp_debug >= level) \
+ printk(KERN_DEBUG pr_fmt("%s: dsp:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+static s32 int_cos(u32 x)
+{
+ u32 t2, t4, t6, t8;
+ s32 ret;
+ u16 period = x / INT_PI;
+
+ if (period % 2)
+ return -int_cos(x - INT_PI);
+ x = x % INT_PI;
+ if (x > INT_PI / 2)
+ return -int_cos(INT_PI / 2 - (x % (INT_PI / 2)));
+ /*
+ * Now x is between 0 and INT_PI/2.
+ * To calculate cos(x) we use it's Taylor polinom.
+ */
+ t2 = x * x / 32768 / 2;
+ t4 = t2 * x / 32768 * x / 32768 / 3 / 4;
+ t6 = t4 * x / 32768 * x / 32768 / 5 / 6;
+ t8 = t6 * x / 32768 * x / 32768 / 7 / 8;
+ ret = 32768 - t2 + t4 - t6 + t8;
+ return ret;
+}
+
+static u32 int_goertzel(s16 x[], u32 N, u32 freq)
+{
+ /*
+ * We use the Goertzel algorithm to determine the power of the
+ * given frequency in the signal
+ */
+ s32 s_prev = 0;
+ s32 s_prev2 = 0;
+ s32 coeff = 2 * int_cos(freq);
+ u32 i;
+
+ u64 tmp;
+ u32 divisor;
+
+ for (i = 0; i < N; i++) {
+ s32 s = x[i] + ((s64)coeff * s_prev / 32768) - s_prev2;
+
+ s_prev2 = s_prev;
+ s_prev = s;
+ }
+
+ tmp = (s64)s_prev2 * s_prev2 + (s64)s_prev * s_prev -
+ (s64)coeff * s_prev2 * s_prev / 32768;
+
+ /*
+ * XXX: N must be low enough so that N*N fits in s32.
+ * Else we need two divisions.
+ */
+ divisor = N * N;
+ do_div(tmp, divisor);
+
+ return (u32)tmp;
+}
+
+static u32 freq_magnitude(s16 x[], u32 N, u32 freq)
+{
+ u32 sum = int_goertzel(x, N, freq);
+
+ return (u32)int_sqrt(sum);
+}
+
+static u32 noise_magnitude(s16 x[], u32 N, u32 freq_start, u32 freq_end)
+{
+ int i;
+ u32 sum = 0;
+ u32 freq_step;
+ int samples = 5;
+
+ if (N > 192) {
+ /* The last 192 samples are enough for noise detection */
+ x += (N - 192);
+ N = 192;
+ }
+
+ freq_step = (freq_end - freq_start) / (samples - 1);
+
+ for (i = 0; i < samples; i++) {
+ sum += int_goertzel(x, N, freq_start);
+ freq_start += freq_step;
+ }
+
+ return (u32)int_sqrt(sum / samples);
+}
+
+static s32 detect_a2_a2m_eiaj(struct cx88_core *core, s16 x[], u32 N)
+{
+ s32 carrier, stereo, dual, noise;
+ s32 carrier_freq, stereo_freq, dual_freq;
+ s32 ret;
+
+ switch (core->tvaudio) {
+ case WW_BG:
+ case WW_DK:
+ carrier_freq = FREQ_A2_CARRIER;
+ stereo_freq = FREQ_A2_STEREO;
+ dual_freq = FREQ_A2_DUAL;
+ break;
+ case WW_M:
+ carrier_freq = FREQ_A2M_CARRIER;
+ stereo_freq = FREQ_A2M_STEREO;
+ dual_freq = FREQ_A2M_DUAL;
+ break;
+ case WW_EIAJ:
+ carrier_freq = FREQ_EIAJ_CARRIER;
+ stereo_freq = FREQ_EIAJ_STEREO;
+ dual_freq = FREQ_EIAJ_DUAL;
+ break;
+ default:
+ pr_warn("unsupported audio mode %d for %s\n",
+ core->tvaudio, __func__);
+ return UNSET;
+ }
+
+ carrier = freq_magnitude(x, N, carrier_freq);
+ stereo = freq_magnitude(x, N, stereo_freq);
+ dual = freq_magnitude(x, N, dual_freq);
+ noise = noise_magnitude(x, N, FREQ_NOISE_START, FREQ_NOISE_END);
+
+ dprintk(1,
+ "detect a2/a2m/eiaj: carrier=%d, stereo=%d, dual=%d, noise=%d\n",
+ carrier, stereo, dual, noise);
+
+ if (stereo > dual)
+ ret = V4L2_TUNER_SUB_STEREO;
+ else
+ ret = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2;
+
+ if (core->tvaudio == WW_EIAJ) {
+ /* EIAJ checks may need adjustments */
+ if ((carrier > max(stereo, dual) * 2) &&
+ (carrier < max(stereo, dual) * 6) &&
+ (carrier > 20 && carrier < 200) &&
+ (max(stereo, dual) > min(stereo, dual))) {
+ /*
+ * For EIAJ the carrier is always present,
+ * so we probably don't need noise detection
+ */
+ return ret;
+ }
+ } else {
+ if ((carrier > max(stereo, dual) * 2) &&
+ (carrier < max(stereo, dual) * 8) &&
+ (carrier > 20 && carrier < 200) &&
+ (noise < 10) &&
+ (max(stereo, dual) > min(stereo, dual) * 2)) {
+ return ret;
+ }
+ }
+ return V4L2_TUNER_SUB_MONO;
+}
+
+static s32 detect_btsc(struct cx88_core *core, s16 x[], u32 N)
+{
+ s32 sap_ref = freq_magnitude(x, N, FREQ_BTSC_SAP_REF);
+ s32 sap = freq_magnitude(x, N, FREQ_BTSC_SAP);
+ s32 dual_ref = freq_magnitude(x, N, FREQ_BTSC_DUAL_REF);
+ s32 dual = freq_magnitude(x, N, FREQ_BTSC_DUAL);
+
+ dprintk(1, "detect btsc: dual_ref=%d, dual=%d, sap_ref=%d, sap=%d\n",
+ dual_ref, dual, sap_ref, sap);
+ /* FIXME: Currently not supported */
+ return UNSET;
+}
+
+static s16 *read_rds_samples(struct cx88_core *core, u32 *N)
+{
+ const struct sram_channel *srch = &cx88_sram_channels[SRAM_CH27];
+ s16 *samples;
+
+ unsigned int i;
+ unsigned int bpl = srch->fifo_size / AUD_RDS_LINES;
+ unsigned int spl = bpl / 4;
+ unsigned int sample_count = spl * (AUD_RDS_LINES - 1);
+
+ u32 current_address = cx_read(srch->ptr1_reg);
+ u32 offset = (current_address - srch->fifo_start + bpl);
+
+ dprintk(1,
+ "read RDS samples: current_address=%08x (offset=%08x), sample_count=%d, aud_intstat=%08x\n",
+ current_address,
+ current_address - srch->fifo_start, sample_count,
+ cx_read(MO_AUD_INTSTAT));
+ samples = kmalloc_array(sample_count, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return NULL;
+
+ *N = sample_count;
+
+ for (i = 0; i < sample_count; i++) {
+ offset = offset % (AUD_RDS_LINES * bpl);
+ samples[i] = cx_read(srch->fifo_start + offset);
+ offset += 4;
+ }
+
+ dprintk(2, "RDS samples dump: %*ph\n", sample_count, samples);
+
+ return samples;
+}
+
+s32 cx88_dsp_detect_stereo_sap(struct cx88_core *core)
+{
+ s16 *samples;
+ u32 N = 0;
+ s32 ret = UNSET;
+
+ /* If audio RDS fifo is disabled, we can't read the samples */
+ if (!(cx_read(MO_AUD_DMACNTRL) & 0x04))
+ return ret;
+ if (!(cx_read(AUD_CTL) & EN_FMRADIO_EN_RDS))
+ return ret;
+
+ /* Wait at least 500 ms after an audio standard change */
+ if (time_before(jiffies, core->last_change + msecs_to_jiffies(500)))
+ return ret;
+
+ samples = read_rds_samples(core, &N);
+
+ if (!samples)
+ return ret;
+
+ switch (core->tvaudio) {
+ case WW_BG:
+ case WW_DK:
+ case WW_EIAJ:
+ case WW_M:
+ ret = detect_a2_a2m_eiaj(core, samples, N);
+ break;
+ case WW_BTSC:
+ ret = detect_btsc(core, samples, N);
+ break;
+ case WW_NONE:
+ case WW_I:
+ case WW_L:
+ case WW_I2SPT:
+ case WW_FM:
+ case WW_I2SADC:
+ break;
+ }
+
+ kfree(samples);
+
+ if (ret != UNSET)
+ dprintk(1, "stereo/sap detection result:%s%s%s\n",
+ (ret & V4L2_TUNER_SUB_MONO) ? " mono" : "",
+ (ret & V4L2_TUNER_SUB_STEREO) ? " stereo" : "",
+ (ret & V4L2_TUNER_SUB_LANG2) ? " dual" : "");
+
+ return ret;
+}
+EXPORT_SYMBOL(cx88_dsp_detect_stereo_sap);
+
diff --git a/drivers/media/pci/cx88/cx88-dvb.c b/drivers/media/pci/cx88/cx88-dvb.c
new file mode 100644
index 000000000..2087f2491
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-dvb.c
@@ -0,0 +1,1844 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * device driver for Conexant 2388x based TV cards
+ * MPEG Transport Stream (DVB) routines
+ *
+ * (c) 2004, 2005 Chris Pascoe <c.pascoe@itee.uq.edu.au>
+ * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
+ */
+
+#include "cx88.h"
+#include "dvb-pll.h"
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kthread.h>
+#include <linux/file.h>
+#include <linux/suspend.h>
+
+#include <media/v4l2-common.h>
+
+#include "mt352.h"
+#include "mt352_priv.h"
+#include "cx88-vp3054-i2c.h"
+#include "zl10353.h"
+#include "cx22702.h"
+#include "or51132.h"
+#include "lgdt330x.h"
+#include "s5h1409.h"
+#include "xc4000.h"
+#include "xc5000.h"
+#include "nxt200x.h"
+#include "cx24123.h"
+#include "isl6421.h"
+#include "tuner-simple.h"
+#include "tda9887.h"
+#include "s5h1411.h"
+#include "stv0299.h"
+#include "z0194a.h"
+#include "stv0288.h"
+#include "stb6000.h"
+#include "cx24116.h"
+#include "stv0900.h"
+#include "stb6100.h"
+#include "stb6100_proc.h"
+#include "mb86a16.h"
+#include "ts2020.h"
+#include "ds3000.h"
+
+MODULE_DESCRIPTION("driver for cx2388x based DVB cards");
+MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
+MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(CX88_VERSION);
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages [dvb]");
+
+static unsigned int dvb_buf_tscnt = 32;
+module_param(dvb_buf_tscnt, int, 0644);
+MODULE_PARM_DESC(dvb_buf_tscnt, "DVB Buffer TS count [dvb]");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define dprintk(level, fmt, arg...) do { \
+ if (debug >= level) \
+ printk(KERN_DEBUG pr_fmt("%s: dvb:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+/* ------------------------------------------------------------------ */
+
+static int queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct cx8802_dev *dev = q->drv_priv;
+
+ *num_planes = 1;
+ dev->ts_packet_size = 188 * 4;
+ dev->ts_packet_count = dvb_buf_tscnt;
+ sizes[0] = dev->ts_packet_size * dev->ts_packet_count;
+ *num_buffers = dvb_buf_tscnt;
+ return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8802_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+
+ return cx8802_buf_prepare(vb->vb2_queue, dev, buf);
+}
+
+static void buffer_finish(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8802_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct cx88_riscmem *risc = &buf->risc;
+
+ if (risc->cpu)
+ dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu,
+ risc->dma);
+ memset(risc, 0, sizeof(*risc));
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8802_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+
+ cx8802_buf_queue(dev, buf);
+}
+
+static int start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct cx8802_dev *dev = q->drv_priv;
+ struct cx88_dmaqueue *dmaq = &dev->mpegq;
+ struct cx88_buffer *buf;
+
+ buf = list_entry(dmaq->active.next, struct cx88_buffer, list);
+ cx8802_start_dma(dev, dmaq, buf);
+ return 0;
+}
+
+static void stop_streaming(struct vb2_queue *q)
+{
+ struct cx8802_dev *dev = q->drv_priv;
+ struct cx88_dmaqueue *dmaq = &dev->mpegq;
+ unsigned long flags;
+
+ cx8802_cancel_buffers(dev);
+
+ spin_lock_irqsave(&dev->slock, flags);
+ while (!list_empty(&dmaq->active)) {
+ struct cx88_buffer *buf = list_entry(dmaq->active.next,
+ struct cx88_buffer, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static const struct vb2_ops dvb_qops = {
+ .queue_setup = queue_setup,
+ .buf_prepare = buffer_prepare,
+ .buf_finish = buffer_finish,
+ .buf_queue = buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+};
+
+/* ------------------------------------------------------------------ */
+
+static int cx88_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx8802_driver *drv = NULL;
+ int ret = 0;
+ int fe_id;
+
+ fe_id = vb2_dvb_find_frontend(&dev->frontends, fe);
+ if (!fe_id) {
+ pr_err("%s() No frontend found\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_lock(&dev->core->lock);
+ drv = cx8802_get_driver(dev, CX88_MPEG_DVB);
+ if (drv) {
+ if (acquire) {
+ dev->frontends.active_fe_id = fe_id;
+ ret = drv->request_acquire(drv);
+ } else {
+ ret = drv->request_release(drv);
+ dev->frontends.active_fe_id = 0;
+ }
+ }
+ mutex_unlock(&dev->core->lock);
+
+ return ret;
+}
+
+static void cx88_dvb_gate_ctrl(struct cx88_core *core, int open)
+{
+ struct vb2_dvb_frontends *f;
+ struct vb2_dvb_frontend *fe;
+
+ if (!core->dvbdev)
+ return;
+
+ f = &core->dvbdev->frontends;
+
+ if (!f)
+ return;
+
+ if (f->gate <= 1) /* undefined or fe0 */
+ fe = vb2_dvb_get_frontend(f, 1);
+ else
+ fe = vb2_dvb_get_frontend(f, f->gate);
+
+ if (fe && fe->dvb.frontend && fe->dvb.frontend->ops.i2c_gate_ctrl)
+ fe->dvb.frontend->ops.i2c_gate_ctrl(fe->dvb.frontend, open);
+}
+
+/* ------------------------------------------------------------------ */
+
+static int dvico_fusionhdtv_demod_init(struct dvb_frontend *fe)
+{
+ static const u8 clock_config[] = { CLOCK_CTL, 0x38, 0x39 };
+ static const u8 reset[] = { RESET, 0x80 };
+ static const u8 adc_ctl_1_cfg[] = { ADC_CTL_1, 0x40 };
+ static const u8 agc_cfg[] = { AGC_TARGET, 0x24, 0x20 };
+ static const u8 gpp_ctl_cfg[] = { GPP_CTL, 0x33 };
+ static const u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };
+
+ mt352_write(fe, clock_config, sizeof(clock_config));
+ udelay(200);
+ mt352_write(fe, reset, sizeof(reset));
+ mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg));
+
+ mt352_write(fe, agc_cfg, sizeof(agc_cfg));
+ mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg));
+ mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+ return 0;
+}
+
+static int dvico_dual_demod_init(struct dvb_frontend *fe)
+{
+ static const u8 clock_config[] = { CLOCK_CTL, 0x38, 0x38 };
+ static const u8 reset[] = { RESET, 0x80 };
+ static const u8 adc_ctl_1_cfg[] = { ADC_CTL_1, 0x40 };
+ static const u8 agc_cfg[] = { AGC_TARGET, 0x28, 0x20 };
+ static const u8 gpp_ctl_cfg[] = { GPP_CTL, 0x33 };
+ static const u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };
+
+ mt352_write(fe, clock_config, sizeof(clock_config));
+ udelay(200);
+ mt352_write(fe, reset, sizeof(reset));
+ mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg));
+
+ mt352_write(fe, agc_cfg, sizeof(agc_cfg));
+ mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg));
+ mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+
+ return 0;
+}
+
+static int dntv_live_dvbt_demod_init(struct dvb_frontend *fe)
+{
+ static const u8 clock_config[] = { 0x89, 0x38, 0x39 };
+ static const u8 reset[] = { 0x50, 0x80 };
+ static const u8 adc_ctl_1_cfg[] = { 0x8E, 0x40 };
+ static const u8 agc_cfg[] = { 0x67, 0x10, 0x23, 0x00, 0xFF, 0xFF,
+ 0x00, 0xFF, 0x00, 0x40, 0x40 };
+ static const u8 dntv_extra[] = { 0xB5, 0x7A };
+ static const u8 capt_range_cfg[] = { 0x75, 0x32 };
+
+ mt352_write(fe, clock_config, sizeof(clock_config));
+ udelay(2000);
+ mt352_write(fe, reset, sizeof(reset));
+ mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg));
+
+ mt352_write(fe, agc_cfg, sizeof(agc_cfg));
+ udelay(2000);
+ mt352_write(fe, dntv_extra, sizeof(dntv_extra));
+ mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+
+ return 0;
+}
+
+static const struct mt352_config dvico_fusionhdtv = {
+ .demod_address = 0x0f,
+ .demod_init = dvico_fusionhdtv_demod_init,
+};
+
+static const struct mt352_config dntv_live_dvbt_config = {
+ .demod_address = 0x0f,
+ .demod_init = dntv_live_dvbt_demod_init,
+};
+
+static const struct mt352_config dvico_fusionhdtv_dual = {
+ .demod_address = 0x0f,
+ .demod_init = dvico_dual_demod_init,
+};
+
+static const struct zl10353_config cx88_terratec_cinergy_ht_pci_mkii_config = {
+ .demod_address = (0x1e >> 1),
+ .no_tuner = 1,
+ .if2 = 45600,
+};
+
+static const struct mb86a16_config twinhan_vp1027 = {
+ .demod_address = 0x08,
+};
+
+#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054)
+static int dntv_live_dvbt_pro_demod_init(struct dvb_frontend *fe)
+{
+ static const u8 clock_config[] = { 0x89, 0x38, 0x38 };
+ static const u8 reset[] = { 0x50, 0x80 };
+ static const u8 adc_ctl_1_cfg[] = { 0x8E, 0x40 };
+ static const u8 agc_cfg[] = { 0x67, 0x10, 0x20, 0x00, 0xFF, 0xFF,
+ 0x00, 0xFF, 0x00, 0x40, 0x40 };
+ static const u8 dntv_extra[] = { 0xB5, 0x7A };
+ static const u8 capt_range_cfg[] = { 0x75, 0x32 };
+
+ mt352_write(fe, clock_config, sizeof(clock_config));
+ udelay(2000);
+ mt352_write(fe, reset, sizeof(reset));
+ mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg));
+
+ mt352_write(fe, agc_cfg, sizeof(agc_cfg));
+ udelay(2000);
+ mt352_write(fe, dntv_extra, sizeof(dntv_extra));
+ mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+
+ return 0;
+}
+
+static const struct mt352_config dntv_live_dvbt_pro_config = {
+ .demod_address = 0x0f,
+ .no_tuner = 1,
+ .demod_init = dntv_live_dvbt_pro_demod_init,
+};
+#endif
+
+static const struct zl10353_config dvico_fusionhdtv_hybrid = {
+ .demod_address = 0x0f,
+ .no_tuner = 1,
+};
+
+static const struct zl10353_config dvico_fusionhdtv_xc3028 = {
+ .demod_address = 0x0f,
+ .if2 = 45600,
+ .no_tuner = 1,
+};
+
+static const struct mt352_config dvico_fusionhdtv_mt352_xc3028 = {
+ .demod_address = 0x0f,
+ .if2 = 4560,
+ .no_tuner = 1,
+ .demod_init = dvico_fusionhdtv_demod_init,
+};
+
+static const struct zl10353_config dvico_fusionhdtv_plus_v1_1 = {
+ .demod_address = 0x0f,
+};
+
+static const struct cx22702_config connexant_refboard_config = {
+ .demod_address = 0x43,
+ .output_mode = CX22702_SERIAL_OUTPUT,
+};
+
+static const struct cx22702_config hauppauge_hvr_config = {
+ .demod_address = 0x63,
+ .output_mode = CX22702_SERIAL_OUTPUT,
+};
+
+static int or51132_set_ts_param(struct dvb_frontend *fe, int is_punctured)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+
+ dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00;
+ return 0;
+}
+
+static const struct or51132_config pchdtv_hd3000 = {
+ .demod_address = 0x15,
+ .set_ts_params = or51132_set_ts_param,
+};
+
+static int lgdt330x_pll_rf_set(struct dvb_frontend *fe, int index)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ dprintk(1, "%s: index = %d\n", __func__, index);
+ if (index == 0)
+ cx_clear(MO_GP0_IO, 8);
+ else
+ cx_set(MO_GP0_IO, 8);
+ return 0;
+}
+
+static int lgdt330x_set_ts_param(struct dvb_frontend *fe, int is_punctured)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+
+ if (is_punctured)
+ dev->ts_gen_cntrl |= 0x04;
+ else
+ dev->ts_gen_cntrl &= ~0x04;
+ return 0;
+}
+
+static struct lgdt330x_config fusionhdtv_3_gold = {
+ .demod_chip = LGDT3302,
+ .serial_mpeg = 0x04, /* TPSERIAL for 3302 in TOP_CONTROL */
+ .set_ts_params = lgdt330x_set_ts_param,
+};
+
+static const struct lgdt330x_config fusionhdtv_5_gold = {
+ .demod_chip = LGDT3303,
+ .serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */
+ .set_ts_params = lgdt330x_set_ts_param,
+};
+
+static const struct lgdt330x_config pchdtv_hd5500 = {
+ .demod_chip = LGDT3303,
+ .serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */
+ .set_ts_params = lgdt330x_set_ts_param,
+};
+
+static int nxt200x_set_ts_param(struct dvb_frontend *fe, int is_punctured)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+
+ dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00;
+ return 0;
+}
+
+static const struct nxt200x_config ati_hdtvwonder = {
+ .demod_address = 0x0a,
+ .set_ts_params = nxt200x_set_ts_param,
+};
+
+static int cx24123_set_ts_param(struct dvb_frontend *fe,
+ int is_punctured)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+
+ dev->ts_gen_cntrl = 0x02;
+ return 0;
+}
+
+static int kworld_dvbs_100_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ if (voltage == SEC_VOLTAGE_OFF)
+ cx_write(MO_GP0_IO, 0x000006fb);
+ else
+ cx_write(MO_GP0_IO, 0x000006f9);
+
+ if (core->prev_set_voltage)
+ return core->prev_set_voltage(fe, voltage);
+ return 0;
+}
+
+static int geniatech_dvbs_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ if (voltage == SEC_VOLTAGE_OFF) {
+ dprintk(1, "LNB Voltage OFF\n");
+ cx_write(MO_GP0_IO, 0x0000efff);
+ }
+
+ if (core->prev_set_voltage)
+ return core->prev_set_voltage(fe, voltage);
+ return 0;
+}
+
+static int tevii_dvbs_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ cx_set(MO_GP0_IO, 0x6040);
+ switch (voltage) {
+ case SEC_VOLTAGE_13:
+ cx_clear(MO_GP0_IO, 0x20);
+ break;
+ case SEC_VOLTAGE_18:
+ cx_set(MO_GP0_IO, 0x20);
+ break;
+ case SEC_VOLTAGE_OFF:
+ cx_clear(MO_GP0_IO, 0x20);
+ break;
+ }
+
+ if (core->prev_set_voltage)
+ return core->prev_set_voltage(fe, voltage);
+ return 0;
+}
+
+static int vp1027_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ switch (voltage) {
+ case SEC_VOLTAGE_13:
+ dprintk(1, "LNB SEC Voltage=13\n");
+ cx_write(MO_GP0_IO, 0x00001220);
+ break;
+ case SEC_VOLTAGE_18:
+ dprintk(1, "LNB SEC Voltage=18\n");
+ cx_write(MO_GP0_IO, 0x00001222);
+ break;
+ case SEC_VOLTAGE_OFF:
+ dprintk(1, "LNB Voltage OFF\n");
+ cx_write(MO_GP0_IO, 0x00001230);
+ break;
+ }
+
+ if (core->prev_set_voltage)
+ return core->prev_set_voltage(fe, voltage);
+ return 0;
+}
+
+static const struct cx24123_config geniatech_dvbs_config = {
+ .demod_address = 0x55,
+ .set_ts_params = cx24123_set_ts_param,
+};
+
+static const struct cx24123_config hauppauge_novas_config = {
+ .demod_address = 0x55,
+ .set_ts_params = cx24123_set_ts_param,
+};
+
+static const struct cx24123_config kworld_dvbs_100_config = {
+ .demod_address = 0x15,
+ .set_ts_params = cx24123_set_ts_param,
+ .lnb_polarity = 1,
+};
+
+static const struct s5h1409_config pinnacle_pctv_hd_800i_config = {
+ .demod_address = 0x32 >> 1,
+ .output_mode = S5H1409_PARALLEL_OUTPUT,
+ .gpio = S5H1409_GPIO_ON,
+ .qam_if = 44000,
+ .inversion = S5H1409_INVERSION_OFF,
+ .status_mode = S5H1409_DEMODLOCKING,
+ .mpeg_timing = S5H1409_MPEGTIMING_NONCONTINUOUS_NONINVERTING_CLOCK,
+};
+
+static const struct s5h1409_config dvico_hdtv5_pci_nano_config = {
+ .demod_address = 0x32 >> 1,
+ .output_mode = S5H1409_SERIAL_OUTPUT,
+ .gpio = S5H1409_GPIO_OFF,
+ .inversion = S5H1409_INVERSION_OFF,
+ .status_mode = S5H1409_DEMODLOCKING,
+ .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK,
+};
+
+static const struct s5h1409_config kworld_atsc_120_config = {
+ .demod_address = 0x32 >> 1,
+ .output_mode = S5H1409_SERIAL_OUTPUT,
+ .gpio = S5H1409_GPIO_OFF,
+ .inversion = S5H1409_INVERSION_OFF,
+ .status_mode = S5H1409_DEMODLOCKING,
+ .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK,
+};
+
+static const struct xc5000_config pinnacle_pctv_hd_800i_tuner_config = {
+ .i2c_address = 0x64,
+ .if_khz = 5380,
+};
+
+static const struct zl10353_config cx88_pinnacle_hybrid_pctv = {
+ .demod_address = (0x1e >> 1),
+ .no_tuner = 1,
+ .if2 = 45600,
+};
+
+static const struct zl10353_config cx88_geniatech_x8000_mt = {
+ .demod_address = (0x1e >> 1),
+ .no_tuner = 1,
+ .disable_i2c_gate_ctrl = 1,
+};
+
+static const struct s5h1411_config dvico_fusionhdtv7_config = {
+ .output_mode = S5H1411_SERIAL_OUTPUT,
+ .gpio = S5H1411_GPIO_ON,
+ .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK,
+ .qam_if = S5H1411_IF_44000,
+ .vsb_if = S5H1411_IF_44000,
+ .inversion = S5H1411_INVERSION_OFF,
+ .status_mode = S5H1411_DEMODLOCKING
+};
+
+static const struct xc5000_config dvico_fusionhdtv7_tuner_config = {
+ .i2c_address = 0xc2 >> 1,
+ .if_khz = 5380,
+};
+
+static int attach_xc3028(u8 addr, struct cx8802_dev *dev)
+{
+ struct dvb_frontend *fe;
+ struct vb2_dvb_frontend *fe0 = NULL;
+ struct xc2028_ctrl ctl;
+ struct xc2028_config cfg = {
+ .i2c_adap = &dev->core->i2c_adap,
+ .i2c_addr = addr,
+ .ctrl = &ctl,
+ };
+
+ /* Get the first frontend */
+ fe0 = vb2_dvb_get_frontend(&dev->frontends, 1);
+ if (!fe0)
+ return -EINVAL;
+
+ if (!fe0->dvb.frontend) {
+ pr_err("dvb frontend not attached. Can't attach xc3028\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Some xc3028 devices may be hidden by an I2C gate. This is known
+ * to happen with some s5h1409-based devices.
+ * Now that I2C gate is open, sets up xc3028 configuration
+ */
+ cx88_setup_xc3028(dev->core, &ctl);
+
+ fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, &cfg);
+ if (!fe) {
+ pr_err("xc3028 attach failed\n");
+ dvb_frontend_detach(fe0->dvb.frontend);
+ dvb_unregister_frontend(fe0->dvb.frontend);
+ fe0->dvb.frontend = NULL;
+ return -EINVAL;
+ }
+
+ pr_info("xc3028 attached\n");
+
+ return 0;
+}
+
+static int attach_xc4000(struct cx8802_dev *dev, struct xc4000_config *cfg)
+{
+ struct dvb_frontend *fe;
+ struct vb2_dvb_frontend *fe0 = NULL;
+
+ /* Get the first frontend */
+ fe0 = vb2_dvb_get_frontend(&dev->frontends, 1);
+ if (!fe0)
+ return -EINVAL;
+
+ if (!fe0->dvb.frontend) {
+ pr_err("dvb frontend not attached. Can't attach xc4000\n");
+ return -EINVAL;
+ }
+
+ fe = dvb_attach(xc4000_attach, fe0->dvb.frontend, &dev->core->i2c_adap,
+ cfg);
+ if (!fe) {
+ pr_err("xc4000 attach failed\n");
+ dvb_frontend_detach(fe0->dvb.frontend);
+ dvb_unregister_frontend(fe0->dvb.frontend);
+ fe0->dvb.frontend = NULL;
+ return -EINVAL;
+ }
+
+ pr_info("xc4000 attached\n");
+
+ return 0;
+}
+
+static int cx24116_set_ts_param(struct dvb_frontend *fe,
+ int is_punctured)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+
+ dev->ts_gen_cntrl = 0x2;
+
+ return 0;
+}
+
+static int stv0900_set_ts_param(struct dvb_frontend *fe,
+ int is_punctured)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+
+ dev->ts_gen_cntrl = 0;
+
+ return 0;
+}
+
+static int cx24116_reset_device(struct dvb_frontend *fe)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ /* Reset the part */
+ /* Put the cx24116 into reset */
+ cx_write(MO_SRST_IO, 0);
+ usleep_range(10000, 20000);
+ /* Take the cx24116 out of reset */
+ cx_write(MO_SRST_IO, 1);
+ usleep_range(10000, 20000);
+
+ return 0;
+}
+
+static const struct cx24116_config hauppauge_hvr4000_config = {
+ .demod_address = 0x05,
+ .set_ts_params = cx24116_set_ts_param,
+ .reset_device = cx24116_reset_device,
+};
+
+static const struct cx24116_config tevii_s460_config = {
+ .demod_address = 0x55,
+ .set_ts_params = cx24116_set_ts_param,
+ .reset_device = cx24116_reset_device,
+};
+
+static int ds3000_set_ts_param(struct dvb_frontend *fe,
+ int is_punctured)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+
+ dev->ts_gen_cntrl = 4;
+
+ return 0;
+}
+
+static struct ds3000_config tevii_ds3000_config = {
+ .demod_address = 0x68,
+ .set_ts_params = ds3000_set_ts_param,
+};
+
+static struct ts2020_config tevii_ts2020_config = {
+ .tuner_address = 0x60,
+ .clk_out_div = 1,
+};
+
+static const struct stv0900_config prof_7301_stv0900_config = {
+ .demod_address = 0x6a,
+/* demod_mode = 0,*/
+ .xtal = 27000000,
+ .clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */
+ .diseqc_mode = 2,/* 2/3 PWM */
+ .tun1_maddress = 0,/* 0x60 */
+ .tun1_adc = 0,/* 2 Vpp */
+ .path1_mode = 3,
+ .set_ts_params = stv0900_set_ts_param,
+};
+
+static const struct stb6100_config prof_7301_stb6100_config = {
+ .tuner_address = 0x60,
+ .refclock = 27000000,
+};
+
+static const struct stv0299_config tevii_tuner_sharp_config = {
+ .demod_address = 0x68,
+ .inittab = sharp_z0194a_inittab,
+ .mclk = 88000000UL,
+ .invert = 1,
+ .skip_reinit = 0,
+ .lock_output = 1,
+ .volt13_op0_op1 = STV0299_VOLT13_OP1,
+ .min_delay_ms = 100,
+ .set_symbol_rate = sharp_z0194a_set_symbol_rate,
+ .set_ts_params = cx24116_set_ts_param,
+};
+
+static const struct stv0288_config tevii_tuner_earda_config = {
+ .demod_address = 0x68,
+ .min_delay_ms = 100,
+ .set_ts_params = cx24116_set_ts_param,
+};
+
+static int cx8802_alloc_frontends(struct cx8802_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+ struct vb2_dvb_frontend *fe = NULL;
+ int i;
+
+ mutex_init(&dev->frontends.lock);
+ INIT_LIST_HEAD(&dev->frontends.felist);
+
+ if (!core->board.num_frontends)
+ return -ENODEV;
+
+ pr_info("%s: allocating %d frontend(s)\n", __func__,
+ core->board.num_frontends);
+ for (i = 1; i <= core->board.num_frontends; i++) {
+ fe = vb2_dvb_alloc_frontend(&dev->frontends, i);
+ if (!fe) {
+ pr_err("%s() failed to alloc\n", __func__);
+ vb2_dvb_dealloc_frontends(&dev->frontends);
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static const u8 samsung_smt_7020_inittab[] = {
+ 0x01, 0x15,
+ 0x02, 0x00,
+ 0x03, 0x00,
+ 0x04, 0x7D,
+ 0x05, 0x0F,
+ 0x06, 0x02,
+ 0x07, 0x00,
+ 0x08, 0x60,
+
+ 0x0A, 0xC2,
+ 0x0B, 0x00,
+ 0x0C, 0x01,
+ 0x0D, 0x81,
+ 0x0E, 0x44,
+ 0x0F, 0x09,
+ 0x10, 0x3C,
+ 0x11, 0x84,
+ 0x12, 0xDA,
+ 0x13, 0x99,
+ 0x14, 0x8D,
+ 0x15, 0xCE,
+ 0x16, 0xE8,
+ 0x17, 0x43,
+ 0x18, 0x1C,
+ 0x19, 0x1B,
+ 0x1A, 0x1D,
+
+ 0x1C, 0x12,
+ 0x1D, 0x00,
+ 0x1E, 0x00,
+ 0x1F, 0x00,
+ 0x20, 0x00,
+ 0x21, 0x00,
+ 0x22, 0x00,
+ 0x23, 0x00,
+
+ 0x28, 0x02,
+ 0x29, 0x28,
+ 0x2A, 0x14,
+ 0x2B, 0x0F,
+ 0x2C, 0x09,
+ 0x2D, 0x05,
+
+ 0x31, 0x1F,
+ 0x32, 0x19,
+ 0x33, 0xFC,
+ 0x34, 0x13,
+ 0xff, 0xff,
+};
+
+static int samsung_smt_7020_tuner_set_params(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct cx8802_dev *dev = fe->dvb->priv;
+ u8 buf[4];
+ u32 div;
+ struct i2c_msg msg = {
+ .addr = 0x61,
+ .flags = 0,
+ .buf = buf,
+ .len = sizeof(buf) };
+
+ div = c->frequency / 125;
+
+ buf[0] = (div >> 8) & 0x7f;
+ buf[1] = div & 0xff;
+ buf[2] = 0x84; /* 0xC4 */
+ buf[3] = 0x00;
+
+ if (c->frequency < 1500000)
+ buf[3] |= 0x10;
+
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 1);
+
+ if (i2c_transfer(&dev->core->i2c_adap, &msg, 1) != 1)
+ return -EIO;
+
+ return 0;
+}
+
+static int samsung_smt_7020_set_tone(struct dvb_frontend *fe,
+ enum fe_sec_tone_mode tone)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ cx_set(MO_GP0_IO, 0x0800);
+
+ switch (tone) {
+ case SEC_TONE_ON:
+ cx_set(MO_GP0_IO, 0x08);
+ break;
+ case SEC_TONE_OFF:
+ cx_clear(MO_GP0_IO, 0x08);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int samsung_smt_7020_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ struct cx8802_dev *dev = fe->dvb->priv;
+ struct cx88_core *core = dev->core;
+
+ u8 data;
+ struct i2c_msg msg = {
+ .addr = 8,
+ .flags = 0,
+ .buf = &data,
+ .len = sizeof(data) };
+
+ cx_set(MO_GP0_IO, 0x8000);
+
+ switch (voltage) {
+ case SEC_VOLTAGE_OFF:
+ break;
+ case SEC_VOLTAGE_13:
+ data = ISL6421_EN1 | ISL6421_LLC1;
+ cx_clear(MO_GP0_IO, 0x80);
+ break;
+ case SEC_VOLTAGE_18:
+ data = ISL6421_EN1 | ISL6421_LLC1 | ISL6421_VSEL1;
+ cx_clear(MO_GP0_IO, 0x80);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return (i2c_transfer(&dev->core->i2c_adap, &msg, 1) == 1) ? 0 : -EIO;
+}
+
+static int samsung_smt_7020_stv0299_set_symbol_rate(struct dvb_frontend *fe,
+ u32 srate, u32 ratio)
+{
+ u8 aclk = 0;
+ u8 bclk = 0;
+
+ if (srate < 1500000) {
+ aclk = 0xb7;
+ bclk = 0x47;
+ } else if (srate < 3000000) {
+ aclk = 0xb7;
+ bclk = 0x4b;
+ } else if (srate < 7000000) {
+ aclk = 0xb7;
+ bclk = 0x4f;
+ } else if (srate < 14000000) {
+ aclk = 0xb7;
+ bclk = 0x53;
+ } else if (srate < 30000000) {
+ aclk = 0xb6;
+ bclk = 0x53;
+ } else if (srate < 45000000) {
+ aclk = 0xb4;
+ bclk = 0x51;
+ }
+
+ stv0299_writereg(fe, 0x13, aclk);
+ stv0299_writereg(fe, 0x14, bclk);
+ stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff);
+ stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff);
+ stv0299_writereg(fe, 0x21, ratio & 0xf0);
+
+ return 0;
+}
+
+static const struct stv0299_config samsung_stv0299_config = {
+ .demod_address = 0x68,
+ .inittab = samsung_smt_7020_inittab,
+ .mclk = 88000000UL,
+ .invert = 0,
+ .skip_reinit = 0,
+ .lock_output = STV0299_LOCKOUTPUT_LK,
+ .volt13_op0_op1 = STV0299_VOLT13_OP1,
+ .min_delay_ms = 100,
+ .set_symbol_rate = samsung_smt_7020_stv0299_set_symbol_rate,
+};
+
+static int dvb_register(struct cx8802_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+ struct vb2_dvb_frontend *fe0, *fe1 = NULL;
+ int mfe_shared = 0; /* bus not shared by default */
+ int res = -EINVAL;
+
+ if (core->i2c_rc != 0) {
+ pr_err("no i2c-bus available, cannot attach dvb drivers\n");
+ goto frontend_detach;
+ }
+
+ /* Get the first frontend */
+ fe0 = vb2_dvb_get_frontend(&dev->frontends, 1);
+ if (!fe0)
+ goto frontend_detach;
+
+ /* multi-frontend gate control is undefined or defaults to fe0 */
+ dev->frontends.gate = 0;
+
+ /* Sets the gate control callback to be used by i2c command calls */
+ core->gate_ctrl = cx88_dvb_gate_ctrl;
+
+ /* init frontend(s) */
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE_DVB_T1:
+ fe0->dvb.frontend = dvb_attach(cx22702_attach,
+ &connexant_refboard_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x61, &core->i2c_adap,
+ DVB_PLL_THOMSON_DTT759X))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1:
+ case CX88_BOARD_CONEXANT_DVB_T1:
+ case CX88_BOARD_KWORLD_DVB_T_CX22702:
+ case CX88_BOARD_WINFAST_DTV1000:
+ fe0->dvb.frontend = dvb_attach(cx22702_attach,
+ &connexant_refboard_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x60, &core->i2c_adap,
+ DVB_PLL_THOMSON_DTT7579))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_WINFAST_DTV2000H:
+ case CX88_BOARD_HAUPPAUGE_HVR1100:
+ case CX88_BOARD_HAUPPAUGE_HVR1100LP:
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ fe0->dvb.frontend = dvb_attach(cx22702_attach,
+ &hauppauge_hvr_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_PHILIPS_FMD1216ME_MK3))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_WINFAST_DTV2000H_J:
+ fe0->dvb.frontend = dvb_attach(cx22702_attach,
+ &hauppauge_hvr_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_PHILIPS_FMD1216MEX_MK3))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_HAUPPAUGE_HVR3000:
+ /* MFE frontend 1 */
+ mfe_shared = 1;
+ dev->frontends.gate = 2;
+ /* DVB-S init */
+ fe0->dvb.frontend = dvb_attach(cx24123_attach,
+ &hauppauge_novas_config,
+ &dev->core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(isl6421_attach,
+ fe0->dvb.frontend,
+ &dev->core->i2c_adap,
+ 0x08, ISL6421_DCL, 0x00, false))
+ goto frontend_detach;
+ }
+ /* MFE frontend 2 */
+ fe1 = vb2_dvb_get_frontend(&dev->frontends, 2);
+ if (!fe1)
+ goto frontend_detach;
+ /* DVB-T init */
+ fe1->dvb.frontend = dvb_attach(cx22702_attach,
+ &hauppauge_hvr_config,
+ &dev->core->i2c_adap);
+ if (fe1->dvb.frontend) {
+ fe1->dvb.frontend->id = 1;
+ if (!dvb_attach(simple_tuner_attach,
+ fe1->dvb.frontend,
+ &dev->core->i2c_adap,
+ 0x61, TUNER_PHILIPS_FMD1216ME_MK3))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS:
+ fe0->dvb.frontend = dvb_attach(mt352_attach,
+ &dvico_fusionhdtv,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x60, NULL, DVB_PLL_THOMSON_DTT7579))
+ goto frontend_detach;
+ break;
+ }
+ /* ZL10353 replaces MT352 on later cards */
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &dvico_fusionhdtv_plus_v1_1,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x60, NULL, DVB_PLL_THOMSON_DTT7579))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL:
+ /*
+ * The tin box says DEE1601, but it seems to be DTT7579
+ * compatible, with a slightly different MT352 AGC gain.
+ */
+ fe0->dvb.frontend = dvb_attach(mt352_attach,
+ &dvico_fusionhdtv_dual,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x61, NULL, DVB_PLL_THOMSON_DTT7579))
+ goto frontend_detach;
+ break;
+ }
+ /* ZL10353 replaces MT352 on later cards */
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &dvico_fusionhdtv_plus_v1_1,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x61, NULL, DVB_PLL_THOMSON_DTT7579))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1:
+ fe0->dvb.frontend = dvb_attach(mt352_attach,
+ &dvico_fusionhdtv,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x61, NULL, DVB_PLL_LG_Z201))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_KWORLD_DVB_T:
+ case CX88_BOARD_DNTV_LIVE_DVB_T:
+ case CX88_BOARD_ADSTECH_DVB_T_PCI:
+ fe0->dvb.frontend = dvb_attach(mt352_attach,
+ &dntv_live_dvbt_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend,
+ 0x61, NULL, DVB_PLL_UNKNOWN_1))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DNTV_LIVE_DVB_T_PRO:
+#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054)
+ /* MT352 is on a secondary I2C bus made from some GPIO lines */
+ fe0->dvb.frontend = dvb_attach(mt352_attach,
+ &dntv_live_dvbt_pro_config,
+ &dev->vp3054->adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_PHILIPS_FMD1216ME_MK3))
+ goto frontend_detach;
+ }
+#else
+ pr_err("built without vp3054 support\n");
+#endif
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID:
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &dvico_fusionhdtv_hybrid,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_THOMSON_FE6600))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO:
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &dvico_fusionhdtv_xc3028,
+ &core->i2c_adap);
+ if (!fe0->dvb.frontend)
+ fe0->dvb.frontend = dvb_attach(mt352_attach,
+ &dvico_fusionhdtv_mt352_xc3028,
+ &core->i2c_adap);
+ /*
+ * On this board, the demod provides the I2C bus pullup.
+ * We must not permit gate_ctrl to be performed, or
+ * the xc3028 cannot communicate on the bus.
+ */
+ if (fe0->dvb.frontend)
+ fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL;
+ if (attach_xc3028(0x61, dev) < 0)
+ goto frontend_detach;
+ break;
+ case CX88_BOARD_PCHDTV_HD3000:
+ fe0->dvb.frontend = dvb_attach(or51132_attach, &pchdtv_hd3000,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_THOMSON_DTT761X))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q:
+ dev->ts_gen_cntrl = 0x08;
+
+ /* Do a hardware reset of chip before using it. */
+ cx_clear(MO_GP0_IO, 1);
+ msleep(100);
+ cx_set(MO_GP0_IO, 1);
+ msleep(200);
+
+ /* Select RF connector callback */
+ fusionhdtv_3_gold.pll_rf_set = lgdt330x_pll_rf_set;
+ fe0->dvb.frontend = dvb_attach(lgdt330x_attach,
+ &fusionhdtv_3_gold,
+ 0x0e,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_MICROTUNE_4042FI5))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T:
+ dev->ts_gen_cntrl = 0x08;
+
+ /* Do a hardware reset of chip before using it. */
+ cx_clear(MO_GP0_IO, 1);
+ msleep(100);
+ cx_set(MO_GP0_IO, 9);
+ msleep(200);
+ fe0->dvb.frontend = dvb_attach(lgdt330x_attach,
+ &fusionhdtv_3_gold,
+ 0x0e,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_THOMSON_DTT761X))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD:
+ dev->ts_gen_cntrl = 0x08;
+
+ /* Do a hardware reset of chip before using it. */
+ cx_clear(MO_GP0_IO, 1);
+ msleep(100);
+ cx_set(MO_GP0_IO, 1);
+ msleep(200);
+ fe0->dvb.frontend = dvb_attach(lgdt330x_attach,
+ &fusionhdtv_5_gold,
+ 0x0e,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_LG_TDVS_H06XF))
+ goto frontend_detach;
+ if (!dvb_attach(tda9887_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x43))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_PCHDTV_HD5500:
+ dev->ts_gen_cntrl = 0x08;
+
+ /* Do a hardware reset of chip before using it. */
+ cx_clear(MO_GP0_IO, 1);
+ msleep(100);
+ cx_set(MO_GP0_IO, 1);
+ msleep(200);
+ fe0->dvb.frontend = dvb_attach(lgdt330x_attach,
+ &pchdtv_hd5500,
+ 0x59,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_LG_TDVS_H06XF))
+ goto frontend_detach;
+ if (!dvb_attach(tda9887_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x43))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_ATI_HDTVWONDER:
+ fe0->dvb.frontend = dvb_attach(nxt200x_attach,
+ &ati_hdtvwonder,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x61,
+ TUNER_PHILIPS_TUV1236D))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1:
+ case CX88_BOARD_HAUPPAUGE_NOVASE2_S1:
+ fe0->dvb.frontend = dvb_attach(cx24123_attach,
+ &hauppauge_novas_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ bool override_tone;
+
+ if (core->model == 92001)
+ override_tone = true;
+ else
+ override_tone = false;
+
+ if (!dvb_attach(isl6421_attach, fe0->dvb.frontend,
+ &core->i2c_adap, 0x08, ISL6421_DCL,
+ 0x00, override_tone))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_KWORLD_DVBS_100:
+ fe0->dvb.frontend = dvb_attach(cx24123_attach,
+ &kworld_dvbs_100_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage;
+ fe0->dvb.frontend->ops.set_voltage = kworld_dvbs_100_set_voltage;
+ }
+ break;
+ case CX88_BOARD_GENIATECH_DVBS:
+ fe0->dvb.frontend = dvb_attach(cx24123_attach,
+ &geniatech_dvbs_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage;
+ fe0->dvb.frontend->ops.set_voltage = geniatech_dvbs_set_voltage;
+ }
+ break;
+ case CX88_BOARD_PINNACLE_PCTV_HD_800i:
+ fe0->dvb.frontend = dvb_attach(s5h1409_attach,
+ &pinnacle_pctv_hd_800i_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(xc5000_attach, fe0->dvb.frontend,
+ &core->i2c_adap,
+ &pinnacle_pctv_hd_800i_tuner_config))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO:
+ fe0->dvb.frontend = dvb_attach(s5h1409_attach,
+ &dvico_hdtv5_pci_nano_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ struct dvb_frontend *fe;
+ struct xc2028_config cfg = {
+ .i2c_adap = &core->i2c_adap,
+ .i2c_addr = 0x61,
+ };
+ static struct xc2028_ctrl ctl = {
+ .fname = XC2028_DEFAULT_FIRMWARE,
+ .max_len = 64,
+ .scode_table = XC3028_FE_OREN538,
+ };
+
+ fe = dvb_attach(xc2028_attach,
+ fe0->dvb.frontend, &cfg);
+ if (fe && fe->ops.tuner_ops.set_config)
+ fe->ops.tuner_ops.set_config(fe, &ctl);
+ }
+ break;
+ case CX88_BOARD_NOTONLYTV_LV3H:
+ case CX88_BOARD_PINNACLE_HYBRID_PCTV:
+ case CX88_BOARD_WINFAST_DTV1800H:
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &cx88_pinnacle_hybrid_pctv,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL;
+ if (attach_xc3028(0x61, dev) < 0)
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_WINFAST_DTV1800H_XC4000:
+ case CX88_BOARD_WINFAST_DTV2000H_PLUS:
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &cx88_pinnacle_hybrid_pctv,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ struct xc4000_config cfg = {
+ .i2c_address = 0x61,
+ .default_pm = 0,
+ .dvb_amplitude = 134,
+ .set_smoothedcvbs = 1,
+ .if_khz = 4560
+ };
+ fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL;
+ if (attach_xc4000(dev, &cfg) < 0)
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_GENIATECH_X8000_MT:
+ dev->ts_gen_cntrl = 0x00;
+
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &cx88_geniatech_x8000_mt,
+ &core->i2c_adap);
+ if (attach_xc3028(0x61, dev) < 0)
+ goto frontend_detach;
+ break;
+ case CX88_BOARD_KWORLD_ATSC_120:
+ fe0->dvb.frontend = dvb_attach(s5h1409_attach,
+ &kworld_atsc_120_config,
+ &core->i2c_adap);
+ if (attach_xc3028(0x61, dev) < 0)
+ goto frontend_detach;
+ break;
+ case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD:
+ fe0->dvb.frontend = dvb_attach(s5h1411_attach,
+ &dvico_fusionhdtv7_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(xc5000_attach, fe0->dvb.frontend,
+ &core->i2c_adap,
+ &dvico_fusionhdtv7_tuner_config))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_HAUPPAUGE_HVR4000:
+ /* MFE frontend 1 */
+ mfe_shared = 1;
+ dev->frontends.gate = 2;
+ /* DVB-S/S2 Init */
+ fe0->dvb.frontend = dvb_attach(cx24116_attach,
+ &hauppauge_hvr4000_config,
+ &dev->core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(isl6421_attach,
+ fe0->dvb.frontend,
+ &dev->core->i2c_adap,
+ 0x08, ISL6421_DCL, 0x00, false))
+ goto frontend_detach;
+ }
+ /* MFE frontend 2 */
+ fe1 = vb2_dvb_get_frontend(&dev->frontends, 2);
+ if (!fe1)
+ goto frontend_detach;
+ /* DVB-T Init */
+ fe1->dvb.frontend = dvb_attach(cx22702_attach,
+ &hauppauge_hvr_config,
+ &dev->core->i2c_adap);
+ if (fe1->dvb.frontend) {
+ fe1->dvb.frontend->id = 1;
+ if (!dvb_attach(simple_tuner_attach,
+ fe1->dvb.frontend,
+ &dev->core->i2c_adap,
+ 0x61, TUNER_PHILIPS_FMD1216ME_MK3))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_HAUPPAUGE_HVR4000LITE:
+ fe0->dvb.frontend = dvb_attach(cx24116_attach,
+ &hauppauge_hvr4000_config,
+ &dev->core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(isl6421_attach,
+ fe0->dvb.frontend,
+ &dev->core->i2c_adap,
+ 0x08, ISL6421_DCL, 0x00, false))
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_PROF_6200:
+ case CX88_BOARD_TBS_8910:
+ case CX88_BOARD_TEVII_S420:
+ fe0->dvb.frontend = dvb_attach(stv0299_attach,
+ &tevii_tuner_sharp_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, 0x60,
+ &core->i2c_adap, DVB_PLL_OPERA1))
+ goto frontend_detach;
+ core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage;
+ fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage;
+
+ } else {
+ fe0->dvb.frontend = dvb_attach(stv0288_attach,
+ &tevii_tuner_earda_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(stb6000_attach,
+ fe0->dvb.frontend, 0x61,
+ &core->i2c_adap))
+ goto frontend_detach;
+ core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage;
+ fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage;
+ }
+ }
+ break;
+ case CX88_BOARD_TEVII_S460:
+ fe0->dvb.frontend = dvb_attach(cx24116_attach,
+ &tevii_s460_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend)
+ fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage;
+ break;
+ case CX88_BOARD_TEVII_S464:
+ fe0->dvb.frontend = dvb_attach(ds3000_attach,
+ &tevii_ds3000_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ dvb_attach(ts2020_attach, fe0->dvb.frontend,
+ &tevii_ts2020_config, &core->i2c_adap);
+ fe0->dvb.frontend->ops.set_voltage =
+ tevii_dvbs_set_voltage;
+ }
+ break;
+ case CX88_BOARD_OMICOM_SS4_PCI:
+ case CX88_BOARD_TBS_8920:
+ case CX88_BOARD_PROF_7300:
+ case CX88_BOARD_SATTRADE_ST4200:
+ fe0->dvb.frontend = dvb_attach(cx24116_attach,
+ &hauppauge_hvr4000_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend)
+ fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage;
+ break;
+ case CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII:
+ fe0->dvb.frontend = dvb_attach(zl10353_attach,
+ &cx88_terratec_cinergy_ht_pci_mkii_config,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL;
+ if (attach_xc3028(0x61, dev) < 0)
+ goto frontend_detach;
+ }
+ break;
+ case CX88_BOARD_PROF_7301:{
+ struct dvb_tuner_ops *tuner_ops = NULL;
+
+ fe0->dvb.frontend = dvb_attach(stv0900_attach,
+ &prof_7301_stv0900_config,
+ &core->i2c_adap, 0);
+ if (fe0->dvb.frontend) {
+ if (!dvb_attach(stb6100_attach, fe0->dvb.frontend,
+ &prof_7301_stb6100_config,
+ &core->i2c_adap))
+ goto frontend_detach;
+
+ tuner_ops = &fe0->dvb.frontend->ops.tuner_ops;
+ tuner_ops->set_frequency = stb6100_set_freq;
+ tuner_ops->get_frequency = stb6100_get_freq;
+ tuner_ops->set_bandwidth = stb6100_set_bandw;
+ tuner_ops->get_bandwidth = stb6100_get_bandw;
+
+ core->prev_set_voltage =
+ fe0->dvb.frontend->ops.set_voltage;
+ fe0->dvb.frontend->ops.set_voltage =
+ tevii_dvbs_set_voltage;
+ }
+ break;
+ }
+ case CX88_BOARD_SAMSUNG_SMT_7020:
+ dev->ts_gen_cntrl = 0x08;
+
+ cx_set(MO_GP0_IO, 0x0101);
+
+ cx_clear(MO_GP0_IO, 0x01);
+ msleep(100);
+ cx_set(MO_GP0_IO, 0x01);
+ msleep(200);
+
+ fe0->dvb.frontend = dvb_attach(stv0299_attach,
+ &samsung_stv0299_config,
+ &dev->core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ fe0->dvb.frontend->ops.tuner_ops.set_params =
+ samsung_smt_7020_tuner_set_params;
+ fe0->dvb.frontend->tuner_priv =
+ &dev->core->i2c_adap;
+ fe0->dvb.frontend->ops.set_voltage =
+ samsung_smt_7020_set_voltage;
+ fe0->dvb.frontend->ops.set_tone =
+ samsung_smt_7020_set_tone;
+ }
+
+ break;
+ case CX88_BOARD_TWINHAN_VP1027_DVBS:
+ dev->ts_gen_cntrl = 0x00;
+ fe0->dvb.frontend = dvb_attach(mb86a16_attach,
+ &twinhan_vp1027,
+ &core->i2c_adap);
+ if (fe0->dvb.frontend) {
+ core->prev_set_voltage =
+ fe0->dvb.frontend->ops.set_voltage;
+ fe0->dvb.frontend->ops.set_voltage =
+ vp1027_set_voltage;
+ }
+ break;
+
+ default:
+ pr_err("The frontend of your DVB/ATSC card isn't supported yet\n");
+ break;
+ }
+
+ if ((NULL == fe0->dvb.frontend) || (fe1 && NULL == fe1->dvb.frontend)) {
+ pr_err("frontend initialization failed\n");
+ goto frontend_detach;
+ }
+ /* define general-purpose callback pointer */
+ fe0->dvb.frontend->callback = cx88_tuner_callback;
+
+ /* Ensure all frontends negotiate bus access */
+ fe0->dvb.frontend->ops.ts_bus_ctrl = cx88_dvb_bus_ctrl;
+ if (fe1)
+ fe1->dvb.frontend->ops.ts_bus_ctrl = cx88_dvb_bus_ctrl;
+
+ /* Put the tuner in standby to keep it quiet */
+ call_all(core, tuner, standby);
+
+ /* register everything */
+ res = vb2_dvb_register_bus(&dev->frontends, THIS_MODULE, dev,
+ &dev->pci->dev, NULL, adapter_nr,
+ mfe_shared);
+ if (res)
+ goto frontend_detach;
+ return res;
+
+frontend_detach:
+ core->gate_ctrl = NULL;
+ vb2_dvb_dealloc_frontends(&dev->frontends);
+ return res;
+}
+
+/* ----------------------------------------------------------- */
+
+/* CX8802 MPEG -> mini driver - We have been given the hardware */
+static int cx8802_dvb_advise_acquire(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ int err = 0;
+
+ dprintk(1, "%s\n", __func__);
+
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ /* We arrive here with either the cx23416 or the cx22702
+ * on the bus. Take the bus from the cx23416 and enable the
+ * cx22702 demod
+ */
+ /* Toggle reset on cx22702 leaving i2c active */
+ cx_set(MO_GP0_IO, 0x00000080);
+ udelay(1000);
+ cx_clear(MO_GP0_IO, 0x00000080);
+ udelay(50);
+ cx_set(MO_GP0_IO, 0x00000080);
+ udelay(1000);
+ /* enable the cx22702 pins */
+ cx_clear(MO_GP0_IO, 0x00000004);
+ udelay(1000);
+ break;
+
+ case CX88_BOARD_HAUPPAUGE_HVR3000:
+ case CX88_BOARD_HAUPPAUGE_HVR4000:
+ /* Toggle reset on cx22702 leaving i2c active */
+ cx_set(MO_GP0_IO, 0x00000080);
+ udelay(1000);
+ cx_clear(MO_GP0_IO, 0x00000080);
+ udelay(50);
+ cx_set(MO_GP0_IO, 0x00000080);
+ udelay(1000);
+ switch (core->dvbdev->frontends.active_fe_id) {
+ case 1: /* DVB-S/S2 Enabled */
+ /* tri-state the cx22702 pins */
+ cx_set(MO_GP0_IO, 0x00000004);
+ /* Take the cx24116/cx24123 out of reset */
+ cx_write(MO_SRST_IO, 1);
+ core->dvbdev->ts_gen_cntrl = 0x02; /* Parallel IO */
+ break;
+ case 2: /* DVB-T Enabled */
+ /* Put the cx24116/cx24123 into reset */
+ cx_write(MO_SRST_IO, 0);
+ /* enable the cx22702 pins */
+ cx_clear(MO_GP0_IO, 0x00000004);
+ core->dvbdev->ts_gen_cntrl = 0x0c; /* Serial IO */
+ break;
+ }
+ udelay(1000);
+ break;
+
+ case CX88_BOARD_WINFAST_DTV2000H_PLUS:
+ /* set RF input to AIR for DVB-T (GPIO 16) */
+ cx_write(MO_GP2_IO, 0x0101);
+ break;
+
+ default:
+ err = -ENODEV;
+ }
+ return err;
+}
+
+/* CX8802 MPEG -> mini driver - We no longer have the hardware */
+static int cx8802_dvb_advise_release(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ int err = 0;
+
+ dprintk(1, "%s\n", __func__);
+
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ /* Do Nothing, leave the cx22702 on the bus. */
+ break;
+ case CX88_BOARD_HAUPPAUGE_HVR3000:
+ case CX88_BOARD_HAUPPAUGE_HVR4000:
+ break;
+ default:
+ err = -ENODEV;
+ }
+ return err;
+}
+
+static int cx8802_dvb_probe(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ struct cx8802_dev *dev = drv->core->dvbdev;
+ int err;
+ struct vb2_dvb_frontend *fe;
+ int i;
+
+ dprintk(1, "%s\n", __func__);
+ dprintk(1, " ->being probed by Card=%d Name=%s, PCI %02x:%02x\n",
+ core->boardnr,
+ core->name,
+ core->pci_bus,
+ core->pci_slot);
+
+ err = -ENODEV;
+ if (!(core->board.mpeg & CX88_MPEG_DVB))
+ goto fail_core;
+
+ /* If vp3054 isn't enabled, a stub will just return 0 */
+ err = vp3054_i2c_probe(dev);
+ if (err != 0)
+ goto fail_core;
+
+ /* dvb stuff */
+ pr_info("cx2388x based DVB/ATSC card\n");
+ dev->ts_gen_cntrl = 0x0c;
+
+ err = cx8802_alloc_frontends(dev);
+ if (err)
+ goto fail_core;
+
+ for (i = 1; i <= core->board.num_frontends; i++) {
+ struct vb2_queue *q;
+
+ fe = vb2_dvb_get_frontend(&core->dvbdev->frontends, i);
+ if (!fe) {
+ pr_err("%s() failed to get frontend(%d)\n",
+ __func__, i);
+ err = -ENODEV;
+ goto fail_probe;
+ }
+ q = &fe->dvb.dvbq;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
+ q->gfp_flags = GFP_DMA32;
+ q->min_buffers_needed = 2;
+ q->drv_priv = dev;
+ q->buf_struct_size = sizeof(struct cx88_buffer);
+ q->ops = &dvb_qops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &core->lock;
+ q->dev = &dev->pci->dev;
+
+ err = vb2_queue_init(q);
+ if (err < 0)
+ goto fail_probe;
+
+ /* init struct vb2_dvb */
+ fe->dvb.name = dev->core->name;
+ }
+
+ err = dvb_register(dev);
+ if (err)
+ /* frontends/adapter de-allocated in dvb_register */
+ pr_err("dvb_register failed (err = %d)\n", err);
+ return err;
+fail_probe:
+ vb2_dvb_dealloc_frontends(&core->dvbdev->frontends);
+fail_core:
+ return err;
+}
+
+static int cx8802_dvb_remove(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ struct cx8802_dev *dev = drv->core->dvbdev;
+
+ dprintk(1, "%s\n", __func__);
+
+ vb2_dvb_unregister_bus(&dev->frontends);
+
+ vp3054_i2c_remove(dev);
+
+ core->gate_ctrl = NULL;
+
+ return 0;
+}
+
+static struct cx8802_driver cx8802_dvb_driver = {
+ .type_id = CX88_MPEG_DVB,
+ .hw_access = CX8802_DRVCTL_SHARED,
+ .probe = cx8802_dvb_probe,
+ .remove = cx8802_dvb_remove,
+ .advise_acquire = cx8802_dvb_advise_acquire,
+ .advise_release = cx8802_dvb_advise_release,
+};
+
+static int __init dvb_init(void)
+{
+ pr_info("cx2388x dvb driver version %s loaded\n", CX88_VERSION);
+ return cx8802_register_driver(&cx8802_dvb_driver);
+}
+
+static void __exit dvb_fini(void)
+{
+ cx8802_unregister_driver(&cx8802_dvb_driver);
+}
+
+module_init(dvb_init);
+module_exit(dvb_fini);
diff --git a/drivers/media/pci/cx88/cx88-i2c.c b/drivers/media/pci/cx88/cx88-i2c.c
new file mode 100644
index 000000000..7fc64aef1
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-i2c.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ *
+ * cx88-i2c.c -- all the i2c code is here
+ *
+ * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
+ * & Marcus Metzler (mocm@thp.uni-koeln.de)
+ * (c) 2002 Yurij Sysoev <yurij@naturesoft.net>
+ * (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org>
+ * (c) 2005 Mauro Carvalho Chehab <mchehab@kernel.org>
+ * - Multituner support and i2c address binding
+ */
+
+#include "cx88.h"
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <media/v4l2-common.h>
+
+static unsigned int i2c_debug;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, 0444);
+MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
+
+static unsigned int i2c_udelay = 5;
+module_param(i2c_udelay, int, 0644);
+MODULE_PARM_DESC(i2c_udelay,
+ "i2c delay at insmod time, in usecs (should be 5 or higher). Lower value means higher bus speed.");
+
+#define dprintk(level, fmt, arg...) do { \
+ if (i2c_debug >= level) \
+ printk(KERN_DEBUG pr_fmt("%s: i2c:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+/* ----------------------------------------------------------------------- */
+
+static void cx8800_bit_setscl(void *data, int state)
+{
+ struct cx88_core *core = data;
+
+ if (state)
+ core->i2c_state |= 0x02;
+ else
+ core->i2c_state &= ~0x02;
+ cx_write(MO_I2C, core->i2c_state);
+ cx_read(MO_I2C);
+}
+
+static void cx8800_bit_setsda(void *data, int state)
+{
+ struct cx88_core *core = data;
+
+ if (state)
+ core->i2c_state |= 0x01;
+ else
+ core->i2c_state &= ~0x01;
+ cx_write(MO_I2C, core->i2c_state);
+ cx_read(MO_I2C);
+}
+
+static int cx8800_bit_getscl(void *data)
+{
+ struct cx88_core *core = data;
+ u32 state;
+
+ state = cx_read(MO_I2C);
+ return state & 0x02 ? 1 : 0;
+}
+
+static int cx8800_bit_getsda(void *data)
+{
+ struct cx88_core *core = data;
+ u32 state;
+
+ state = cx_read(MO_I2C);
+ return state & 0x01;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static const struct i2c_algo_bit_data cx8800_i2c_algo_template = {
+ .setsda = cx8800_bit_setsda,
+ .setscl = cx8800_bit_setscl,
+ .getsda = cx8800_bit_getsda,
+ .getscl = cx8800_bit_getscl,
+ .udelay = 16,
+ .timeout = 200,
+};
+
+/* ----------------------------------------------------------------------- */
+
+static const char * const i2c_devs[128] = {
+ [0x1c >> 1] = "lgdt330x",
+ [0x86 >> 1] = "tda9887/cx22702",
+ [0xa0 >> 1] = "eeprom",
+ [0xc0 >> 1] = "tuner (analog)",
+ [0xc2 >> 1] = "tuner (analog/dvb)",
+ [0xc8 >> 1] = "xc5000",
+};
+
+static void do_i2c_scan(const char *name, struct i2c_client *c)
+{
+ unsigned char buf;
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) {
+ c->addr = i;
+ rc = i2c_master_recv(c, &buf, 0);
+ if (rc < 0)
+ continue;
+ pr_info("i2c scan: found device @ 0x%x [%s]\n",
+ i << 1, i2c_devs[i] ? i2c_devs[i] : "???");
+ }
+}
+
+/* init + register i2c adapter */
+int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci)
+{
+ /* Prevents usage of invalid delay values */
+ if (i2c_udelay < 5)
+ i2c_udelay = 5;
+
+ core->i2c_algo = cx8800_i2c_algo_template;
+
+ core->i2c_adap.dev.parent = &pci->dev;
+ strscpy(core->i2c_adap.name, core->name, sizeof(core->i2c_adap.name));
+ core->i2c_adap.owner = THIS_MODULE;
+ core->i2c_algo.udelay = i2c_udelay;
+ core->i2c_algo.data = core;
+ i2c_set_adapdata(&core->i2c_adap, &core->v4l2_dev);
+ core->i2c_adap.algo_data = &core->i2c_algo;
+ core->i2c_client.adapter = &core->i2c_adap;
+ strscpy(core->i2c_client.name, "cx88xx internal", I2C_NAME_SIZE);
+
+ cx8800_bit_setscl(core, 1);
+ cx8800_bit_setsda(core, 1);
+
+ core->i2c_rc = i2c_bit_add_bus(&core->i2c_adap);
+ if (core->i2c_rc == 0) {
+ static u8 tuner_data[] = {
+ 0x0b, 0xdc, 0x86, 0x52 };
+ static struct i2c_msg tuner_msg = {
+ .flags = 0,
+ .addr = 0xc2 >> 1,
+ .buf = tuner_data,
+ .len = 4
+ };
+
+ dprintk(1, "i2c register ok\n");
+ switch (core->boardnr) {
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ case CX88_BOARD_HAUPPAUGE_HVR3000:
+ case CX88_BOARD_HAUPPAUGE_HVR4000:
+ pr_info("i2c init: enabling analog demod on HVR1300/3000/4000 tuner\n");
+ i2c_transfer(core->i2c_client.adapter, &tuner_msg, 1);
+ break;
+ default:
+ break;
+ }
+ if (i2c_scan)
+ do_i2c_scan(core->name, &core->i2c_client);
+ } else
+ pr_err("i2c register FAILED\n");
+
+ return core->i2c_rc;
+}
diff --git a/drivers/media/pci/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c
new file mode 100644
index 000000000..a04a1d33f
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-input.c
@@ -0,0 +1,651 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Device driver for GPIO attached remote control interfaces
+ * on Conexant 2388x based TV/DVB cards.
+ *
+ * Copyright (c) 2003 Pavel Machek
+ * Copyright (c) 2004 Gerd Knorr
+ * Copyright (c) 2004, 2005 Chris Pascoe
+ */
+
+#include "cx88.h"
+
+#include <linux/init.h>
+#include <linux/hrtimer.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include <media/rc-core.h>
+
+#define MODULE_NAME "cx88xx"
+
+/* ---------------------------------------------------------------------- */
+
+struct cx88_IR {
+ struct cx88_core *core;
+ struct rc_dev *dev;
+
+ int users;
+
+ char name[32];
+ char phys[32];
+
+ /* sample from gpio pin 16 */
+ u32 sampling;
+
+ /* poll external decoder */
+ int polling;
+ struct hrtimer timer;
+ u32 gpio_addr;
+ u32 last_gpio;
+ u32 mask_keycode;
+ u32 mask_keydown;
+ u32 mask_keyup;
+};
+
+static unsigned int ir_samplerate = 4;
+module_param(ir_samplerate, uint, 0444);
+MODULE_PARM_DESC(ir_samplerate, "IR samplerate in kHz, 1 - 20, default 4");
+
+static int ir_debug;
+module_param(ir_debug, int, 0644); /* debug level [IR] */
+MODULE_PARM_DESC(ir_debug, "enable debug messages [IR]");
+
+#define ir_dprintk(fmt, arg...) do { \
+ if (ir_debug) \
+ printk(KERN_DEBUG "%s IR: " fmt, ir->core->name, ##arg);\
+} while (0)
+
+#define dprintk(fmt, arg...) do { \
+ if (ir_debug) \
+ printk(KERN_DEBUG "cx88 IR: " fmt, ##arg); \
+} while (0)
+
+/* ---------------------------------------------------------------------- */
+
+static void cx88_ir_handle_key(struct cx88_IR *ir)
+{
+ struct cx88_core *core = ir->core;
+ u32 gpio, data, auxgpio;
+
+ /* read gpio value */
+ gpio = cx_read(ir->gpio_addr);
+ switch (core->boardnr) {
+ case CX88_BOARD_NPGTECH_REALTV_TOP10FM:
+ /*
+ * This board apparently uses a combination of 2 GPIO
+ * to represent the keys. Additionally, the second GPIO
+ * can be used for parity.
+ *
+ * Example:
+ *
+ * for key "5"
+ * gpio = 0x758, auxgpio = 0xe5 or 0xf5
+ * for key "Power"
+ * gpio = 0x758, auxgpio = 0xed or 0xfd
+ */
+
+ auxgpio = cx_read(MO_GP1_IO);
+ /* Take out the parity part */
+ gpio = (gpio & 0x7fd) + (auxgpio & 0xef);
+ break;
+ case CX88_BOARD_WINFAST_DTV1000:
+ case CX88_BOARD_WINFAST_DTV1800H:
+ case CX88_BOARD_WINFAST_DTV1800H_XC4000:
+ case CX88_BOARD_WINFAST_DTV2000H_PLUS:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43:
+ gpio = (gpio & 0x6ff) | ((cx_read(MO_GP1_IO) << 8) & 0x900);
+ auxgpio = gpio;
+ break;
+ default:
+ auxgpio = gpio;
+ }
+ if (ir->polling) {
+ if (ir->last_gpio == auxgpio)
+ return;
+ ir->last_gpio = auxgpio;
+ }
+
+ /* extract data */
+ data = ir_extract_bits(gpio, ir->mask_keycode);
+ ir_dprintk("irq gpio=0x%x code=%d | %s%s%s\n",
+ gpio, data,
+ ir->polling ? "poll" : "irq",
+ (gpio & ir->mask_keydown) ? " down" : "",
+ (gpio & ir->mask_keyup) ? " up" : "");
+
+ if (ir->core->boardnr == CX88_BOARD_NORWOOD_MICRO) {
+ u32 gpio_key = cx_read(MO_GP0_IO);
+
+ data = (data << 4) | ((gpio_key & 0xf0) >> 4);
+
+ rc_keydown(ir->dev, RC_PROTO_UNKNOWN, data, 0);
+
+ } else if (ir->core->boardnr == CX88_BOARD_PROLINK_PLAYTVPVR ||
+ ir->core->boardnr == CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO) {
+ /* bit cleared on keydown, NEC scancode, 0xAAAACC, A = 0x866b */
+ u16 addr;
+ u8 cmd;
+ u32 scancode;
+
+ addr = (data >> 8) & 0xffff;
+ cmd = (data >> 0) & 0x00ff;
+ scancode = RC_SCANCODE_NECX(addr, cmd);
+
+ if (0 == (gpio & ir->mask_keyup))
+ rc_keydown_notimeout(ir->dev, RC_PROTO_NECX, scancode,
+ 0);
+ else
+ rc_keyup(ir->dev);
+
+ } else if (ir->mask_keydown) {
+ /* bit set on keydown */
+ if (gpio & ir->mask_keydown)
+ rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data,
+ 0);
+ else
+ rc_keyup(ir->dev);
+
+ } else if (ir->mask_keyup) {
+ /* bit cleared on keydown */
+ if (0 == (gpio & ir->mask_keyup))
+ rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data,
+ 0);
+ else
+ rc_keyup(ir->dev);
+
+ } else {
+ /* can't distinguish keydown/up :-/ */
+ rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, 0);
+ rc_keyup(ir->dev);
+ }
+}
+
+static enum hrtimer_restart cx88_ir_work(struct hrtimer *timer)
+{
+ u64 missed;
+ struct cx88_IR *ir = container_of(timer, struct cx88_IR, timer);
+
+ cx88_ir_handle_key(ir);
+ missed = hrtimer_forward_now(&ir->timer,
+ ktime_set(0, ir->polling * 1000000));
+ if (missed > 1)
+ ir_dprintk("Missed ticks %llu\n", missed - 1);
+
+ return HRTIMER_RESTART;
+}
+
+static int __cx88_ir_start(void *priv)
+{
+ struct cx88_core *core = priv;
+ struct cx88_IR *ir;
+
+ if (!core || !core->ir)
+ return -EINVAL;
+
+ ir = core->ir;
+
+ if (ir->polling) {
+ hrtimer_init(&ir->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ ir->timer.function = cx88_ir_work;
+ hrtimer_start(&ir->timer,
+ ktime_set(0, ir->polling * 1000000),
+ HRTIMER_MODE_REL);
+ }
+ if (ir->sampling) {
+ core->pci_irqmask |= PCI_INT_IR_SMPINT;
+ cx_write(MO_DDS_IO, 0x33F286 * ir_samplerate); /* samplerate */
+ cx_write(MO_DDSCFG_IO, 0x5); /* enable */
+ }
+ return 0;
+}
+
+static void __cx88_ir_stop(void *priv)
+{
+ struct cx88_core *core = priv;
+ struct cx88_IR *ir;
+
+ if (!core || !core->ir)
+ return;
+
+ ir = core->ir;
+ if (ir->sampling) {
+ cx_write(MO_DDSCFG_IO, 0x0);
+ core->pci_irqmask &= ~PCI_INT_IR_SMPINT;
+ }
+
+ if (ir->polling)
+ hrtimer_cancel(&ir->timer);
+}
+
+int cx88_ir_start(struct cx88_core *core)
+{
+ if (core->ir->users)
+ return __cx88_ir_start(core);
+
+ return 0;
+}
+EXPORT_SYMBOL(cx88_ir_start);
+
+void cx88_ir_stop(struct cx88_core *core)
+{
+ if (core->ir->users)
+ __cx88_ir_stop(core);
+}
+EXPORT_SYMBOL(cx88_ir_stop);
+
+static int cx88_ir_open(struct rc_dev *rc)
+{
+ struct cx88_core *core = rc->priv;
+
+ core->ir->users++;
+ return __cx88_ir_start(core);
+}
+
+static void cx88_ir_close(struct rc_dev *rc)
+{
+ struct cx88_core *core = rc->priv;
+
+ core->ir->users--;
+ if (!core->ir->users)
+ __cx88_ir_stop(core);
+}
+
+/* ---------------------------------------------------------------------- */
+
+int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci)
+{
+ struct cx88_IR *ir;
+ struct rc_dev *dev;
+ char *ir_codes = NULL;
+ u64 rc_proto = RC_PROTO_BIT_OTHER;
+ int err = -ENOMEM;
+ u32 hardware_mask = 0; /* For devices with a hardware mask, when
+ * used with a full-code IR table
+ */
+
+ ir = kzalloc(sizeof(*ir), GFP_KERNEL);
+ dev = rc_allocate_device(RC_DRIVER_IR_RAW);
+ if (!ir || !dev)
+ goto err_out_free;
+
+ ir->dev = dev;
+
+ /* detect & configure */
+ switch (core->boardnr) {
+ case CX88_BOARD_DNTV_LIVE_DVB_T:
+ case CX88_BOARD_KWORLD_DVB_T:
+ case CX88_BOARD_KWORLD_DVB_T_CX22702:
+ ir_codes = RC_MAP_DNTV_LIVE_DVB_T;
+ ir->gpio_addr = MO_GP1_IO;
+ ir->mask_keycode = 0x1f;
+ ir->mask_keyup = 0x60;
+ ir->polling = 50; /* ms */
+ break;
+ case CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1:
+ ir_codes = RC_MAP_CINERGY_1400;
+ ir->sampling = 0xeb04; /* address */
+ break;
+ case CX88_BOARD_HAUPPAUGE:
+ case CX88_BOARD_HAUPPAUGE_DVB_T1:
+ case CX88_BOARD_HAUPPAUGE_NOVASE2_S1:
+ case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1:
+ case CX88_BOARD_HAUPPAUGE_HVR1100:
+ case CX88_BOARD_HAUPPAUGE_HVR3000:
+ case CX88_BOARD_HAUPPAUGE_HVR4000:
+ case CX88_BOARD_HAUPPAUGE_HVR4000LITE:
+ case CX88_BOARD_PCHDTV_HD3000:
+ case CX88_BOARD_PCHDTV_HD5500:
+ case CX88_BOARD_HAUPPAUGE_IRONLY:
+ ir_codes = RC_MAP_HAUPPAUGE;
+ ir->sampling = 1;
+ break;
+ case CX88_BOARD_WINFAST_DTV2000H:
+ case CX88_BOARD_WINFAST_DTV2000H_J:
+ case CX88_BOARD_WINFAST_DTV1800H:
+ case CX88_BOARD_WINFAST_DTV1800H_XC4000:
+ case CX88_BOARD_WINFAST_DTV2000H_PLUS:
+ ir_codes = RC_MAP_WINFAST;
+ ir->gpio_addr = MO_GP0_IO;
+ ir->mask_keycode = 0x8f8;
+ ir->mask_keyup = 0x100;
+ ir->polling = 50; /* ms */
+ break;
+ case CX88_BOARD_WINFAST2000XP_EXPERT:
+ case CX88_BOARD_WINFAST_DTV1000:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36:
+ case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43:
+ ir_codes = RC_MAP_WINFAST;
+ ir->gpio_addr = MO_GP0_IO;
+ ir->mask_keycode = 0x8f8;
+ ir->mask_keyup = 0x100;
+ ir->polling = 1; /* ms */
+ break;
+ case CX88_BOARD_IODATA_GVBCTV7E:
+ ir_codes = RC_MAP_IODATA_BCTV7E;
+ ir->gpio_addr = MO_GP0_IO;
+ ir->mask_keycode = 0xfd;
+ ir->mask_keydown = 0x02;
+ ir->polling = 5; /* ms */
+ break;
+ case CX88_BOARD_PROLINK_PLAYTVPVR:
+ case CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO:
+ /*
+ * It seems that this hardware is paired with NEC extended
+ * address 0x866b. So, unfortunately, its usage with other
+ * IR's with different address won't work. Still, there are
+ * other IR's from the same manufacturer that works, like the
+ * 002-T mini RC, provided with newer PV hardware
+ */
+ ir_codes = RC_MAP_PIXELVIEW_MK12;
+ rc_proto = RC_PROTO_BIT_NECX;
+ ir->gpio_addr = MO_GP1_IO;
+ ir->mask_keyup = 0x80;
+ ir->polling = 10; /* ms */
+ hardware_mask = 0x3f; /* Hardware returns only 6 bits from command part */
+ break;
+ case CX88_BOARD_PROLINK_PV_8000GT:
+ case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME:
+ ir_codes = RC_MAP_PIXELVIEW_NEW;
+ ir->gpio_addr = MO_GP1_IO;
+ ir->mask_keycode = 0x3f;
+ ir->mask_keyup = 0x80;
+ ir->polling = 1; /* ms */
+ break;
+ case CX88_BOARD_KWORLD_LTV883:
+ ir_codes = RC_MAP_PIXELVIEW;
+ ir->gpio_addr = MO_GP1_IO;
+ ir->mask_keycode = 0x1f;
+ ir->mask_keyup = 0x60;
+ ir->polling = 1; /* ms */
+ break;
+ case CX88_BOARD_ADSTECH_DVB_T_PCI:
+ ir_codes = RC_MAP_ADSTECH_DVB_T_PCI;
+ ir->gpio_addr = MO_GP1_IO;
+ ir->mask_keycode = 0xbf;
+ ir->mask_keyup = 0x40;
+ ir->polling = 50; /* ms */
+ break;
+ case CX88_BOARD_MSI_TVANYWHERE_MASTER:
+ ir_codes = RC_MAP_MSI_TVANYWHERE;
+ ir->gpio_addr = MO_GP1_IO;
+ ir->mask_keycode = 0x1f;
+ ir->mask_keyup = 0x40;
+ ir->polling = 1; /* ms */
+ break;
+ case CX88_BOARD_AVERTV_303:
+ case CX88_BOARD_AVERTV_STUDIO_303:
+ ir_codes = RC_MAP_AVERTV_303;
+ ir->gpio_addr = MO_GP2_IO;
+ ir->mask_keycode = 0xfb;
+ ir->mask_keydown = 0x02;
+ ir->polling = 50; /* ms */
+ break;
+ case CX88_BOARD_OMICOM_SS4_PCI:
+ case CX88_BOARD_SATTRADE_ST4200:
+ case CX88_BOARD_TBS_8920:
+ case CX88_BOARD_TBS_8910:
+ case CX88_BOARD_PROF_7300:
+ case CX88_BOARD_PROF_7301:
+ case CX88_BOARD_PROF_6200:
+ ir_codes = RC_MAP_TBS_NEC;
+ ir->sampling = 0xff00; /* address */
+ break;
+ case CX88_BOARD_TEVII_S464:
+ case CX88_BOARD_TEVII_S460:
+ case CX88_BOARD_TEVII_S420:
+ ir_codes = RC_MAP_TEVII_NEC;
+ ir->sampling = 0xff00; /* address */
+ break;
+ case CX88_BOARD_DNTV_LIVE_DVB_T_PRO:
+ ir_codes = RC_MAP_DNTV_LIVE_DVBT_PRO;
+ ir->sampling = 0xff00; /* address */
+ break;
+ case CX88_BOARD_NORWOOD_MICRO:
+ ir_codes = RC_MAP_NORWOOD;
+ ir->gpio_addr = MO_GP1_IO;
+ ir->mask_keycode = 0x0e;
+ ir->mask_keyup = 0x80;
+ ir->polling = 50; /* ms */
+ break;
+ case CX88_BOARD_NPGTECH_REALTV_TOP10FM:
+ ir_codes = RC_MAP_NPGTECH;
+ ir->gpio_addr = MO_GP0_IO;
+ ir->mask_keycode = 0xfa;
+ ir->polling = 50; /* ms */
+ break;
+ case CX88_BOARD_PINNACLE_PCTV_HD_800i:
+ ir_codes = RC_MAP_PINNACLE_PCTV_HD;
+ ir->sampling = 1;
+ break;
+ case CX88_BOARD_POWERCOLOR_REAL_ANGEL:
+ ir_codes = RC_MAP_POWERCOLOR_REAL_ANGEL;
+ ir->gpio_addr = MO_GP2_IO;
+ ir->mask_keycode = 0x7e;
+ ir->polling = 100; /* ms */
+ break;
+ case CX88_BOARD_TWINHAN_VP1027_DVBS:
+ ir_codes = RC_MAP_TWINHAN_VP1027_DVBS;
+ ir->sampling = 0xff00; /* address */
+ break;
+ }
+
+ if (!ir_codes) {
+ err = -ENODEV;
+ goto err_out_free;
+ }
+
+ /*
+ * The usage of mask_keycode were very convenient, due to several
+ * reasons. Among others, the scancode tables were using the scancode
+ * as the index elements. So, the less bits it was used, the smaller
+ * the table were stored. After the input changes, the better is to use
+ * the full scancodes, since it allows replacing the IR remote by
+ * another one. Unfortunately, there are still some hardware, like
+ * Pixelview Ultra Pro, where only part of the scancode is sent via
+ * GPIO. So, there's no way to get the full scancode. Due to that,
+ * hardware_mask were introduced here: it represents those hardware
+ * that has such limits.
+ */
+ if (hardware_mask && !ir->mask_keycode)
+ ir->mask_keycode = hardware_mask;
+
+ /* init input device */
+ snprintf(ir->name, sizeof(ir->name), "cx88 IR (%s)", core->board.name);
+ snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", pci_name(pci));
+
+ dev->device_name = ir->name;
+ dev->input_phys = ir->phys;
+ dev->input_id.bustype = BUS_PCI;
+ dev->input_id.version = 1;
+ if (pci->subsystem_vendor) {
+ dev->input_id.vendor = pci->subsystem_vendor;
+ dev->input_id.product = pci->subsystem_device;
+ } else {
+ dev->input_id.vendor = pci->vendor;
+ dev->input_id.product = pci->device;
+ }
+ dev->dev.parent = &pci->dev;
+ dev->map_name = ir_codes;
+ dev->driver_name = MODULE_NAME;
+ dev->priv = core;
+ dev->open = cx88_ir_open;
+ dev->close = cx88_ir_close;
+ dev->scancode_mask = hardware_mask;
+
+ if (ir->sampling) {
+ dev->timeout = MS_TO_US(10); /* 10 ms */
+ } else {
+ dev->driver_type = RC_DRIVER_SCANCODE;
+ dev->allowed_protocols = rc_proto;
+ }
+
+ ir->core = core;
+ core->ir = ir;
+
+ /* all done */
+ err = rc_register_device(dev);
+ if (err)
+ goto err_out_free;
+
+ return 0;
+
+err_out_free:
+ rc_free_device(dev);
+ core->ir = NULL;
+ kfree(ir);
+ return err;
+}
+
+int cx88_ir_fini(struct cx88_core *core)
+{
+ struct cx88_IR *ir = core->ir;
+
+ /* skip detach on non attached boards */
+ if (!ir)
+ return 0;
+
+ cx88_ir_stop(core);
+ rc_unregister_device(ir->dev);
+ kfree(ir);
+
+ /* done */
+ core->ir = NULL;
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void cx88_ir_irq(struct cx88_core *core)
+{
+ struct cx88_IR *ir = core->ir;
+ u32 samples;
+ unsigned int todo, bits;
+ struct ir_raw_event ev = {};
+
+ if (!ir || !ir->sampling)
+ return;
+
+ /*
+ * Samples are stored in a 32 bit register, oldest sample in
+ * the msb. A set bit represents space and an unset bit
+ * represents a pulse.
+ */
+ samples = cx_read(MO_SAMPLE_IO);
+
+ if (samples == 0xff && ir->dev->idle)
+ return;
+
+ for (todo = 32; todo > 0; todo -= bits) {
+ ev.pulse = samples & 0x80000000 ? false : true;
+ bits = min(todo, 32U - fls(ev.pulse ? samples : ~samples));
+ ev.duration = (bits * (USEC_PER_SEC / 1000)) / ir_samplerate;
+ ir_raw_event_store_with_filter(ir->dev, &ev);
+ samples <<= bits;
+ }
+ ir_raw_event_handle(ir->dev);
+}
+
+static int get_key_pvr2000(struct IR_i2c *ir, enum rc_proto *protocol,
+ u32 *scancode, u8 *toggle)
+{
+ int flags, code;
+
+ /* poll IR chip */
+ flags = i2c_smbus_read_byte_data(ir->c, 0x10);
+ if (flags < 0) {
+ dprintk("read error\n");
+ return 0;
+ }
+ /* key pressed ? */
+ if (0 == (flags & 0x80))
+ return 0;
+
+ /* read actual key code */
+ code = i2c_smbus_read_byte_data(ir->c, 0x00);
+ if (code < 0) {
+ dprintk("read error\n");
+ return 0;
+ }
+
+ dprintk("IR Key/Flags: (0x%02x/0x%02x)\n",
+ code & 0xff, flags & 0xff);
+
+ *protocol = RC_PROTO_UNKNOWN;
+ *scancode = code & 0xff;
+ *toggle = 0;
+ return 1;
+}
+
+void cx88_i2c_init_ir(struct cx88_core *core)
+{
+ struct i2c_board_info info;
+ static const unsigned short default_addr_list[] = {
+ 0x18, 0x33, 0x6b, 0x71,
+ I2C_CLIENT_END
+ };
+ static const unsigned short pvr2000_addr_list[] = {
+ 0x18, 0x1a,
+ I2C_CLIENT_END
+ };
+ const unsigned short *addr_list = default_addr_list;
+ const unsigned short *addrp;
+ /* Instantiate the IR receiver device, if present */
+ if (core->i2c_rc != 0)
+ return;
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ strscpy(info.type, "ir_video", I2C_NAME_SIZE);
+
+ switch (core->boardnr) {
+ case CX88_BOARD_LEADTEK_PVR2000:
+ addr_list = pvr2000_addr_list;
+ core->init_data.name = "cx88 Leadtek PVR 2000 remote";
+ core->init_data.type = RC_PROTO_BIT_UNKNOWN;
+ core->init_data.get_key = get_key_pvr2000;
+ core->init_data.ir_codes = RC_MAP_EMPTY;
+ break;
+ }
+
+ /*
+ * We can't call i2c_new_scanned_device() because it uses
+ * quick writes for probing and at least some RC receiver
+ * devices only reply to reads.
+ * Also, Hauppauge XVR needs to be specified, as address 0x71
+ * conflicts with another remote type used with saa7134
+ */
+ for (addrp = addr_list; *addrp != I2C_CLIENT_END; addrp++) {
+ info.platform_data = NULL;
+ memset(&core->init_data, 0, sizeof(core->init_data));
+
+ if (*addrp == 0x71) {
+ /* Hauppauge Z8F0811 */
+ strscpy(info.type, "ir_z8f0811_haup", I2C_NAME_SIZE);
+ core->init_data.name = core->board.name;
+ core->init_data.ir_codes = RC_MAP_HAUPPAUGE;
+ core->init_data.type = RC_PROTO_BIT_RC5 |
+ RC_PROTO_BIT_RC6_MCE | RC_PROTO_BIT_RC6_6A_32;
+ core->init_data.internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR;
+
+ info.platform_data = &core->init_data;
+ }
+ if (i2c_smbus_xfer(&core->i2c_adap, *addrp, 0,
+ I2C_SMBUS_READ, 0,
+ I2C_SMBUS_QUICK, NULL) >= 0) {
+ info.addr = *addrp;
+ i2c_new_client_device(&core->i2c_adap, &info);
+ break;
+ }
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+MODULE_AUTHOR("Gerd Knorr, Pavel Machek, Chris Pascoe");
+MODULE_DESCRIPTION("input driver for cx88 GPIO-based IR remote controls");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/pci/cx88/cx88-mpeg.c b/drivers/media/pci/cx88/cx88-mpeg.c
new file mode 100644
index 000000000..2c1d5137a
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-mpeg.c
@@ -0,0 +1,807 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Support for the mpeg transport stream transfers
+ * PCI function #2 of the cx2388x.
+ *
+ * (c) 2004 Jelle Foks <jelle@foks.us>
+ * (c) 2004 Chris Pascoe <c.pascoe@itee.uq.edu.au>
+ * (c) 2004 Gerd Knorr <kraxel@bytesex.org>
+ */
+
+#include "cx88.h"
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+/* ------------------------------------------------------------------ */
+
+MODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards");
+MODULE_AUTHOR("Jelle Foks <jelle@foks.us>");
+MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
+MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(CX88_VERSION);
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages [mpeg]");
+
+#define dprintk(level, fmt, arg...) do { \
+ if (debug + 1 > level) \
+ printk(KERN_DEBUG pr_fmt("%s: mpeg:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+#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);
+}
+
+static void flush_request_modules(struct cx8802_dev *dev)
+{
+ flush_work(&dev->request_module_wk);
+}
+#else
+#define request_modules(dev)
+#define flush_request_modules(dev)
+#endif /* CONFIG_MODULES */
+
+static LIST_HEAD(cx8802_devlist);
+static DEFINE_MUTEX(cx8802_mutex);
+/* ------------------------------------------------------------------ */
+
+int cx8802_start_dma(struct cx8802_dev *dev,
+ struct cx88_dmaqueue *q,
+ struct cx88_buffer *buf)
+{
+ struct cx88_core *core = dev->core;
+
+ dprintk(1, "w: %d, h: %d, f: %d\n",
+ core->width, core->height, core->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, dev->ts_packet_size);
+
+ /*
+ * 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_SAMSUNG_SMT_7020:
+ cx_write(TS_SOP_STAT, 0x00);
+ break;
+ case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1:
+ case CX88_BOARD_HAUPPAUGE_NOVASE2_S1:
+ /* Enable MPEG parallel IO and video signal pins */
+ cx_write(MO_PINMUX_IO, 0x88);
+ udelay(100);
+ break;
+ case CX88_BOARD_HAUPPAUGE_HVR1300:
+ /* Enable MPEG parallel IO and video signal pins */
+ cx_write(MO_PINMUX_IO, 0x88);
+ cx_write(TS_SOP_STAT, 0);
+ cx_write(TS_VALERR_CNTRL, 0);
+ 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 */
+
+ /* punctured clock TS & posedge driven & software reset */
+ cx_write(TS_GEN_CNTRL, 0x46);
+ udelay(100);
+
+ cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */
+ cx_write(TS_VALERR_CNTRL, 0x2000);
+
+ /* punctured clock TS & posedge driven */
+ cx_write(TS_GEN_CNTRL, 0x06);
+ udelay(100);
+ } else {
+ pr_err("%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 = 0;
+
+ /* clear interrupt status register */
+ cx_write(MO_TS_INTSTAT, 0x1f1111);
+
+ /* 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;
+}
+EXPORT_SYMBOL(cx8802_start_dma);
+
+static int cx8802_stop_dma(struct cx8802_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+
+ dprintk(1, "\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, "\n");
+ if (list_empty(&q->active))
+ return 0;
+
+ buf = list_entry(q->active.next, struct cx88_buffer, list);
+ dprintk(2, "restart_queue [%p/%d]: restart dma\n",
+ buf, buf->vb.vb2_buf.index);
+ cx8802_start_dma(dev, q, buf);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+int cx8802_buf_prepare(struct vb2_queue *q, struct cx8802_dev *dev,
+ struct cx88_buffer *buf)
+{
+ int size = dev->ts_packet_size * dev->ts_packet_count;
+ struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0);
+ struct cx88_riscmem *risc = &buf->risc;
+ int rc;
+
+ if (vb2_plane_size(&buf->vb.vb2_buf, 0) < size)
+ return -EINVAL;
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
+
+ rc = cx88_risc_databuffer(dev->pci, risc, sgt->sgl,
+ dev->ts_packet_size, dev->ts_packet_count, 0);
+ if (rc) {
+ if (risc->cpu)
+ dma_free_coherent(&dev->pci->dev, risc->size,
+ risc->cpu, risc->dma);
+ memset(risc, 0, sizeof(*risc));
+ return rc;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(cx8802_buf_prepare);
+
+void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf)
+{
+ struct cx88_buffer *prev;
+ struct cx88_dmaqueue *cx88q = &dev->mpegq;
+
+ dprintk(1, "\n");
+ /* add jump to start */
+ buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8);
+ buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC);
+ buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8);
+
+ if (list_empty(&cx88q->active)) {
+ dprintk(1, "queue is empty - first active\n");
+ list_add_tail(&buf->list, &cx88q->active);
+ dprintk(1, "[%p/%d] %s - first active\n",
+ buf, buf->vb.vb2_buf.index, __func__);
+
+ } else {
+ buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1);
+ dprintk(1, "queue is not empty - append to active\n");
+ prev = list_entry(cx88q->active.prev, struct cx88_buffer, list);
+ list_add_tail(&buf->list, &cx88q->active);
+ prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+ dprintk(1, "[%p/%d] %s - append to active\n",
+ buf, buf->vb.vb2_buf.index, __func__);
+ }
+}
+EXPORT_SYMBOL(cx8802_buf_queue);
+
+/* ----------------------------------------------------------- */
+
+static void do_cancel_buffers(struct cx8802_dev *dev)
+{
+ 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, list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+void cx8802_cancel_buffers(struct cx8802_dev *dev)
+{
+ dprintk(1, "\n");
+ cx8802_stop_dma(dev);
+ do_cancel_buffers(dev);
+}
+EXPORT_SYMBOL(cx8802_cancel_buffers);
+
+static const 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, "\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("irq mpeg ",
+ cx88_mpeg_irqs, ARRAY_SIZE(cx88_mpeg_irqs),
+ status, mask);
+
+ /* risc op code error */
+ if (status & (1 << 16)) {
+ pr_warn("mpeg risc op code error\n");
+ 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);
+ }
+
+ /* other general errors */
+ if (status & 0x1f0100) {
+ dprintk(0, "general errors: 0x%08x\n", status & 0x1f0100);
+ spin_lock(&dev->slock);
+ cx8802_stop_dma(dev);
+ 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 (status == 0)
+ 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 (loop == MAX_IRQ_LOOP) {
+ dprintk(0, "clearing mask\n");
+ pr_warn("irq loop -- clearing mask\n");
+ 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);
+ err = dma_set_mask(&dev->pci->dev, DMA_BIT_MASK(32));
+ if (err) {
+ pr_err("Oops: no 32bit PCI DMA ???\n");
+ return -EIO;
+ }
+
+ dev->pci_rev = dev->pci->revision;
+ pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER, &dev->pci_lat);
+ pr_info("found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n",
+ 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);
+
+ /* get irq */
+ err = request_irq(dev->pci->irq, cx8802_irq,
+ IRQF_SHARED, dev->core->name, dev);
+ if (err < 0) {
+ pr_err("can't get IRQ %d\n", 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, "\n");
+ cx8802_stop_dma(dev);
+ pci_disable_device(dev->pci);
+
+ /* unregister stuff */
+ free_irq(dev->pci->irq, dev);
+}
+
+/* ----------------------------------------------------------- */
+
+static int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state)
+{
+ struct cx8802_dev *dev = pci_get_drvdata(pci_dev);
+ unsigned long flags;
+
+ /* stop mpeg dma */
+ spin_lock_irqsave(&dev->slock, flags);
+ if (!list_empty(&dev->mpegq.active)) {
+ dprintk(2, "suspend\n");
+ pr_info("suspend mpeg\n");
+ cx8802_stop_dma(dev);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+
+ /* FIXME -- shutdown device */
+ cx88_shutdown(dev->core);
+
+ pci_save_state(pci_dev);
+ if (pci_set_power_state(pci_dev,
+ pci_choose_state(pci_dev, state)) != 0) {
+ 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);
+ unsigned long flags;
+ int err;
+
+ if (dev->state.disabled) {
+ err = pci_enable_device(pci_dev);
+ if (err) {
+ pr_err("can't enable device\n");
+ return err;
+ }
+ dev->state.disabled = 0;
+ }
+ err = pci_set_power_state(pci_dev, PCI_D0);
+ if (err) {
+ pr_err("can't enable device\n");
+ 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_irqsave(&dev->slock, flags);
+ if (!list_empty(&dev->mpegq.active)) {
+ pr_info("resume mpeg\n");
+ cx8802_restart_queue(dev, &dev->mpegq);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+
+ return 0;
+}
+
+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;
+}
+EXPORT_SYMBOL(cx8802_get_driver);
+
+/* Driver asked for hardware access. */
+static int cx8802_request_acquire(struct cx8802_driver *drv)
+{
+ struct cx88_core *core = drv->core;
+ unsigned int i;
+
+ /* 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;
+
+ if (drv->type_id == CX88_MPEG_DVB) {
+ /* When switching to DVB, always set the input to the tuner */
+ core->last_analog_input = core->input;
+ core->input = 0;
+ for (i = 0;
+ i < ARRAY_SIZE(core->board.input);
+ i++) {
+ if (core->board.input[i].type == CX88_VMUX_DVB) {
+ core->input = i;
+ break;
+ }
+ }
+ }
+
+ if (drv->advise_acquire) {
+ core->active_ref++;
+ if (core->active_type_id == CX88_BOARD_NONE) {
+ core->active_type_id = drv->type_id;
+ drv->advise_acquire(drv);
+ }
+
+ dprintk(1, "Post acquire GPIO=%x\n", 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;
+
+ if (drv->advise_release && --core->active_ref == 0) {
+ if (drv->type_id == CX88_MPEG_DVB) {
+ /*
+ * If the DVB driver is releasing, reset the input
+ * state to the last configured analog input
+ */
+ core->input = core->last_analog_input;
+ }
+
+ drv->advise_release(drv);
+ core->active_type_id = CX88_BOARD_NONE;
+ dprintk(1, "Post release GPIO=%x\n", cx_read(MO_GP0_IO));
+ }
+
+ return 0;
+}
+
+static int cx8802_check_driver(struct cx8802_driver *drv)
+{
+ if (!drv)
+ 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) ||
+ (!drv->remove) ||
+ (!drv->advise_acquire) ||
+ (!drv->advise_release))
+ return -EINVAL;
+
+ return 0;
+}
+
+int cx8802_register_driver(struct cx8802_driver *drv)
+{
+ struct cx8802_dev *dev;
+ struct cx8802_driver *driver;
+ int err, i = 0;
+
+ pr_info("registering cx8802 driver, type: %s access: %s\n",
+ drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird",
+ drv->hw_access == CX8802_DRVCTL_SHARED ?
+ "shared" : "exclusive");
+
+ err = cx8802_check_driver(drv);
+ if (err) {
+ pr_err("cx8802_driver is invalid\n");
+ return err;
+ }
+
+ mutex_lock(&cx8802_mutex);
+
+ list_for_each_entry(dev, &cx8802_devlist, devlist) {
+ pr_info("subsystem: %04x:%04x, board: %s [card=%d]\n",
+ 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) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* 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));
+
+ mutex_lock(&drv->core->lock);
+ err = drv->probe(driver);
+ if (err == 0) {
+ i++;
+ list_add_tail(&driver->drvlist, &dev->drvlist);
+ } else {
+ pr_err("cx8802 probe failed, err = %d\n", err);
+ }
+ mutex_unlock(&drv->core->lock);
+ }
+
+ err = i ? 0 : -ENODEV;
+out:
+ mutex_unlock(&cx8802_mutex);
+ return err;
+}
+EXPORT_SYMBOL(cx8802_register_driver);
+
+int cx8802_unregister_driver(struct cx8802_driver *drv)
+{
+ struct cx8802_dev *dev;
+ struct cx8802_driver *d, *dtmp;
+ int err = 0;
+
+ pr_info("unregistering cx8802 driver, type: %s access: %s\n",
+ drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird",
+ drv->hw_access == CX8802_DRVCTL_SHARED ?
+ "shared" : "exclusive");
+
+ mutex_lock(&cx8802_mutex);
+
+ list_for_each_entry(dev, &cx8802_devlist, devlist) {
+ pr_info("subsystem: %04x:%04x, board: %s [card=%d]\n",
+ dev->pci->subsystem_vendor,
+ dev->pci->subsystem_device, dev->core->board.name,
+ dev->core->boardnr);
+
+ mutex_lock(&dev->core->lock);
+
+ 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) {
+ list_del(&d->drvlist);
+ kfree(d);
+ } else
+ pr_err("cx8802 driver remove failed (%d)\n",
+ err);
+ }
+
+ mutex_unlock(&dev->core->lock);
+ }
+
+ mutex_unlock(&cx8802_mutex);
+
+ return err;
+}
+EXPORT_SYMBOL(cx8802_unregister_driver);
+
+/* ----------------------------------------------------------- */
+static int cx8802_probe(struct pci_dev *pci_dev,
+ const struct pci_device_id *pci_id)
+{
+ struct cx8802_dev *dev;
+ struct cx88_core *core;
+ int err;
+
+ /* general setup */
+ core = cx88_core_get(pci_dev);
+ if (!core)
+ return -EINVAL;
+
+ pr_info("cx2388x 8802 Driver Manager\n");
+
+ err = -ENODEV;
+ if (!core->board.mpeg)
+ goto fail_core;
+
+ err = -ENOMEM;
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ goto fail_core;
+ dev->pci = pci_dev;
+ dev->core = core;
+
+ /* Maintain a reference so cx88-video can query the 8802 device. */
+ core->dvbdev = dev;
+
+ err = cx8802_init_common(dev);
+ if (err != 0)
+ goto fail_dev;
+
+ INIT_LIST_HEAD(&dev->drvlist);
+ mutex_lock(&cx8802_mutex);
+ list_add_tail(&dev->devlist, &cx8802_devlist);
+ mutex_unlock(&cx8802_mutex);
+
+ /* now autoload cx88-dvb or cx88-blackbird */
+ request_modules(dev);
+ return 0;
+
+ fail_dev:
+ kfree(dev);
+ fail_core:
+ core->dvbdev = NULL;
+ cx88_core_put(core, pci_dev);
+ return err;
+}
+
+static void cx8802_remove(struct pci_dev *pci_dev)
+{
+ struct cx8802_dev *dev;
+
+ dev = pci_get_drvdata(pci_dev);
+
+ dprintk(1, "%s\n", __func__);
+
+ flush_request_modules(dev);
+
+ mutex_lock(&dev->core->lock);
+
+ if (!list_empty(&dev->drvlist)) {
+ struct cx8802_driver *drv, *tmp;
+ int err;
+
+ pr_warn("Trying to remove cx8802 driver while cx8802 sub-drivers still loaded?!\n");
+
+ list_for_each_entry_safe(drv, tmp, &dev->drvlist, drvlist) {
+ err = drv->remove(drv);
+ if (err == 0) {
+ list_del(&drv->drvlist);
+ } else
+ pr_err("cx8802 driver remove failed (%d)\n",
+ err);
+ kfree(drv);
+ }
+ }
+
+ mutex_unlock(&dev->core->lock);
+
+ /* Destroy any 8802 reference. */
+ dev->core->dvbdev = NULL;
+
+ /* common */
+ cx8802_fini_common(dev);
+ cx88_core_put(dev->core, dev->pci);
+ kfree(dev);
+}
+
+static const 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 = cx8802_remove,
+};
+
+module_pci_driver(cx8802_pci_driver);
diff --git a/drivers/media/pci/cx88/cx88-reg.h b/drivers/media/pci/cx88/cx88-reg.h
new file mode 100644
index 000000000..866f85f86
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-reg.h
@@ -0,0 +1,816 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * cx88x-hw.h - CX2388x register offsets
+ *
+ * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
+ * 2001 Michael Eskin
+ * 2002 Yurij Sysoev <yurij@naturesoft.net>
+ * 2003 Gerd Knorr <kraxel@bytesex.org>
+ */
+
+#ifndef _CX88_REG_H_
+#define _CX88_REG_H_
+
+/*
+ * PCI IDs and config space
+ */
+
+#ifndef PCI_VENDOR_ID_CONEXANT
+# define PCI_VENDOR_ID_CONEXANT 0x14F1
+#endif
+#ifndef PCI_DEVICE_ID_CX2300_VID
+# define PCI_DEVICE_ID_CX2300_VID 0x8800
+#endif
+
+#define CX88X_DEVCTRL 0x40
+#define CX88X_EN_TBFX 0x02
+#define CX88X_EN_VSFX 0x04
+
+/*
+ * PCI controller registers
+ */
+
+/* Command and Status Register */
+#define F0_CMD_STAT_MM 0x2f0004
+#define F1_CMD_STAT_MM 0x2f0104
+#define F2_CMD_STAT_MM 0x2f0204
+#define F3_CMD_STAT_MM 0x2f0304
+#define F4_CMD_STAT_MM 0x2f0404
+
+/* Device Control #1 */
+#define F0_DEV_CNTRL1_MM 0x2f0040
+#define F1_DEV_CNTRL1_MM 0x2f0140
+#define F2_DEV_CNTRL1_MM 0x2f0240
+#define F3_DEV_CNTRL1_MM 0x2f0340
+#define F4_DEV_CNTRL1_MM 0x2f0440
+
+/* Device Control #1 */
+#define F0_BAR0_MM 0x2f0010
+#define F1_BAR0_MM 0x2f0110
+#define F2_BAR0_MM 0x2f0210
+#define F3_BAR0_MM 0x2f0310
+#define F4_BAR0_MM 0x2f0410
+
+/*
+ * DMA Controller registers
+ */
+
+#define MO_PDMA_STHRSH 0x200000 // Source threshold
+#define MO_PDMA_STADRS 0x200004 // Source target address
+#define MO_PDMA_SIADRS 0x200008 // Source internal address
+#define MO_PDMA_SCNTRL 0x20000C // Source control
+#define MO_PDMA_DTHRSH 0x200010 // Destination threshold
+#define MO_PDMA_DTADRS 0x200014 // Destination target address
+#define MO_PDMA_DIADRS 0x200018 // Destination internal address
+#define MO_PDMA_DCNTRL 0x20001C // Destination control
+#define MO_LD_SSID 0x200030 // Load subsystem ID
+#define MO_DEV_CNTRL2 0x200034 // Device control
+#define MO_PCI_INTMSK 0x200040 // PCI interrupt mask
+#define MO_PCI_INTSTAT 0x200044 // PCI interrupt status
+#define MO_PCI_INTMSTAT 0x200048 // PCI interrupt masked status
+#define MO_VID_INTMSK 0x200050 // Video interrupt mask
+#define MO_VID_INTSTAT 0x200054 // Video interrupt status
+#define MO_VID_INTMSTAT 0x200058 // Video interrupt masked status
+#define MO_VID_INTSSTAT 0x20005C // Video interrupt set status
+#define MO_AUD_INTMSK 0x200060 // Audio interrupt mask
+#define MO_AUD_INTSTAT 0x200064 // Audio interrupt status
+#define MO_AUD_INTMSTAT 0x200068 // Audio interrupt masked status
+#define MO_AUD_INTSSTAT 0x20006C // Audio interrupt set status
+#define MO_TS_INTMSK 0x200070 // Transport stream interrupt mask
+#define MO_TS_INTSTAT 0x200074 // Transport stream interrupt status
+#define MO_TS_INTMSTAT 0x200078 // Transport stream interrupt mask status
+#define MO_TS_INTSSTAT 0x20007C // Transport stream interrupt set status
+#define MO_VIP_INTMSK 0x200080 // VIP interrupt mask
+#define MO_VIP_INTSTAT 0x200084 // VIP interrupt status
+#define MO_VIP_INTMSTAT 0x200088 // VIP interrupt masked status
+#define MO_VIP_INTSSTAT 0x20008C // VIP interrupt set status
+#define MO_GPHST_INTMSK 0x200090 // Host interrupt mask
+#define MO_GPHST_INTSTAT 0x200094 // Host interrupt status
+#define MO_GPHST_INTMSTAT 0x200098 // Host interrupt masked status
+#define MO_GPHST_INTSSTAT 0x20009C // Host interrupt set status
+
+// DMA Channels 1-6 belong to SPIPE
+#define MO_DMA7_PTR1 0x300018 // {24}RW* DMA Current Ptr : Ch#7
+#define MO_DMA8_PTR1 0x30001C // {24}RW* DMA Current Ptr : Ch#8
+
+// DMA Channels 9-20 belong to SPIPE
+#define MO_DMA21_PTR1 0x300080 // {24}R0* DMA Current Ptr : Ch#21
+#define MO_DMA22_PTR1 0x300084 // {24}R0* DMA Current Ptr : Ch#22
+#define MO_DMA23_PTR1 0x300088 // {24}R0* DMA Current Ptr : Ch#23
+#define MO_DMA24_PTR1 0x30008C // {24}R0* DMA Current Ptr : Ch#24
+#define MO_DMA25_PTR1 0x300090 // {24}R0* DMA Current Ptr : Ch#25
+#define MO_DMA26_PTR1 0x300094 // {24}R0* DMA Current Ptr : Ch#26
+#define MO_DMA27_PTR1 0x300098 // {24}R0* DMA Current Ptr : Ch#27
+#define MO_DMA28_PTR1 0x30009C // {24}R0* DMA Current Ptr : Ch#28
+#define MO_DMA29_PTR1 0x3000A0 // {24}R0* DMA Current Ptr : Ch#29
+#define MO_DMA30_PTR1 0x3000A4 // {24}R0* DMA Current Ptr : Ch#30
+#define MO_DMA31_PTR1 0x3000A8 // {24}R0* DMA Current Ptr : Ch#31
+#define MO_DMA32_PTR1 0x3000AC // {24}R0* DMA Current Ptr : Ch#32
+
+#define MO_DMA21_PTR2 0x3000C0 // {24}RW* DMA Tab Ptr : Ch#21
+#define MO_DMA22_PTR2 0x3000C4 // {24}RW* DMA Tab Ptr : Ch#22
+#define MO_DMA23_PTR2 0x3000C8 // {24}RW* DMA Tab Ptr : Ch#23
+#define MO_DMA24_PTR2 0x3000CC // {24}RW* DMA Tab Ptr : Ch#24
+#define MO_DMA25_PTR2 0x3000D0 // {24}RW* DMA Tab Ptr : Ch#25
+#define MO_DMA26_PTR2 0x3000D4 // {24}RW* DMA Tab Ptr : Ch#26
+#define MO_DMA27_PTR2 0x3000D8 // {24}RW* DMA Tab Ptr : Ch#27
+#define MO_DMA28_PTR2 0x3000DC // {24}RW* DMA Tab Ptr : Ch#28
+#define MO_DMA29_PTR2 0x3000E0 // {24}RW* DMA Tab Ptr : Ch#29
+#define MO_DMA30_PTR2 0x3000E4 // {24}RW* DMA Tab Ptr : Ch#30
+#define MO_DMA31_PTR2 0x3000E8 // {24}RW* DMA Tab Ptr : Ch#31
+#define MO_DMA32_PTR2 0x3000EC // {24}RW* DMA Tab Ptr : Ch#32
+
+#define MO_DMA21_CNT1 0x300100 // {11}RW* DMA Buffer Size : Ch#21
+#define MO_DMA22_CNT1 0x300104 // {11}RW* DMA Buffer Size : Ch#22
+#define MO_DMA23_CNT1 0x300108 // {11}RW* DMA Buffer Size : Ch#23
+#define MO_DMA24_CNT1 0x30010C // {11}RW* DMA Buffer Size : Ch#24
+#define MO_DMA25_CNT1 0x300110 // {11}RW* DMA Buffer Size : Ch#25
+#define MO_DMA26_CNT1 0x300114 // {11}RW* DMA Buffer Size : Ch#26
+#define MO_DMA27_CNT1 0x300118 // {11}RW* DMA Buffer Size : Ch#27
+#define MO_DMA28_CNT1 0x30011C // {11}RW* DMA Buffer Size : Ch#28
+#define MO_DMA29_CNT1 0x300120 // {11}RW* DMA Buffer Size : Ch#29
+#define MO_DMA30_CNT1 0x300124 // {11}RW* DMA Buffer Size : Ch#30
+#define MO_DMA31_CNT1 0x300128 // {11}RW* DMA Buffer Size : Ch#31
+#define MO_DMA32_CNT1 0x30012C // {11}RW* DMA Buffer Size : Ch#32
+
+#define MO_DMA21_CNT2 0x300140 // {11}RW* DMA Table Size : Ch#21
+#define MO_DMA22_CNT2 0x300144 // {11}RW* DMA Table Size : Ch#22
+#define MO_DMA23_CNT2 0x300148 // {11}RW* DMA Table Size : Ch#23
+#define MO_DMA24_CNT2 0x30014C // {11}RW* DMA Table Size : Ch#24
+#define MO_DMA25_CNT2 0x300150 // {11}RW* DMA Table Size : Ch#25
+#define MO_DMA26_CNT2 0x300154 // {11}RW* DMA Table Size : Ch#26
+#define MO_DMA27_CNT2 0x300158 // {11}RW* DMA Table Size : Ch#27
+#define MO_DMA28_CNT2 0x30015C // {11}RW* DMA Table Size : Ch#28
+#define MO_DMA29_CNT2 0x300160 // {11}RW* DMA Table Size : Ch#29
+#define MO_DMA30_CNT2 0x300164 // {11}RW* DMA Table Size : Ch#30
+#define MO_DMA31_CNT2 0x300168 // {11}RW* DMA Table Size : Ch#31
+#define MO_DMA32_CNT2 0x30016C // {11}RW* DMA Table Size : Ch#32
+
+/*
+ * Video registers
+ */
+
+#define MO_VIDY_DMA 0x310000 // {64}RWp Video Y
+#define MO_VIDU_DMA 0x310008 // {64}RWp Video U
+#define MO_VIDV_DMA 0x310010 // {64}RWp Video V
+#define MO_VBI_DMA 0x310018 // {64}RWp VBI (Vertical blanking interval)
+
+#define MO_DEVICE_STATUS 0x310100
+#define MO_INPUT_FORMAT 0x310104
+#define MO_AGC_BURST 0x31010c
+#define MO_CONTR_BRIGHT 0x310110
+#define MO_UV_SATURATION 0x310114
+#define MO_HUE 0x310118
+#define MO_HTOTAL 0x310120
+#define MO_HDELAY_EVEN 0x310124
+#define MO_HDELAY_ODD 0x310128
+#define MO_VDELAY_ODD 0x31012c
+#define MO_VDELAY_EVEN 0x310130
+#define MO_HACTIVE_EVEN 0x31013c
+#define MO_HACTIVE_ODD 0x310140
+#define MO_VACTIVE_EVEN 0x310144
+#define MO_VACTIVE_ODD 0x310148
+#define MO_HSCALE_EVEN 0x31014c
+#define MO_HSCALE_ODD 0x310150
+#define MO_VSCALE_EVEN 0x310154
+#define MO_FILTER_EVEN 0x31015c
+#define MO_VSCALE_ODD 0x310158
+#define MO_FILTER_ODD 0x310160
+#define MO_OUTPUT_FORMAT 0x310164
+
+#define MO_PLL_REG 0x310168 // PLL register
+#define MO_PLL_ADJ_CTRL 0x31016c // PLL adjust control register
+#define MO_SCONV_REG 0x310170 // sample rate conversion register
+#define MO_SCONV_FIFO 0x310174 // sample rate conversion fifo
+#define MO_SUB_STEP 0x310178 // subcarrier step size
+#define MO_SUB_STEP_DR 0x31017c // subcarrier step size for DR line
+
+#define MO_CAPTURE_CTRL 0x310180 // capture control
+#define MO_COLOR_CTRL 0x310184
+#define MO_VBI_PACKET 0x310188 // vbi packet size / delay
+#define MO_FIELD_COUNT 0x310190 // field counter
+#define MO_VIP_CONFIG 0x310194
+#define MO_VBOS_CONTROL 0x3101a8
+
+#define MO_AGC_BACK_VBI 0x310200
+#define MO_AGC_SYNC_TIP1 0x310208
+
+#define MO_VIDY_GPCNT 0x31C020 // {16}RO Video Y general purpose counter
+#define MO_VIDU_GPCNT 0x31C024 // {16}RO Video U general purpose counter
+#define MO_VIDV_GPCNT 0x31C028 // {16}RO Video V general purpose counter
+#define MO_VBI_GPCNT 0x31C02C // {16}RO VBI general purpose counter
+#define MO_VIDY_GPCNTRL 0x31C030 // {2}WO Video Y general purpose control
+#define MO_VIDU_GPCNTRL 0x31C034 // {2}WO Video U general purpose control
+#define MO_VIDV_GPCNTRL 0x31C038 // {2}WO Video V general purpose control
+#define MO_VBI_GPCNTRL 0x31C03C // {2}WO VBI general purpose counter
+#define MO_VID_DMACNTRL 0x31C040 // {8}RW Video DMA control
+#define MO_VID_XFR_STAT 0x31C044 // {1}RO Video transfer status
+
+/*
+ * audio registers
+ */
+
+#define MO_AUDD_DMA 0x320000 // {64}RWp Audio downstream
+#define MO_AUDU_DMA 0x320008 // {64}RWp Audio upstream
+#define MO_AUDR_DMA 0x320010 // {64}RWp Audio RDS (downstream)
+#define MO_AUDD_GPCNT 0x32C020 // {16}RO Audio down general purpose counter
+#define MO_AUDU_GPCNT 0x32C024 // {16}RO Audio up general purpose counter
+#define MO_AUDR_GPCNT 0x32C028 // {16}RO Audio RDS general purpose counter
+#define MO_AUDD_GPCNTRL 0x32C030 // {2}WO Audio down general purpose control
+#define MO_AUDU_GPCNTRL 0x32C034 // {2}WO Audio up general purpose control
+#define MO_AUDR_GPCNTRL 0x32C038 // {2}WO Audio RDS general purpose control
+#define MO_AUD_DMACNTRL 0x32C040 // {6}RW Audio DMA control
+#define MO_AUD_XFR_STAT 0x32C044 // {1}RO Audio transfer status
+#define MO_AUDD_LNGTH 0x32C048 // {12}RW Audio down line length
+#define MO_AUDR_LNGTH 0x32C04C // {12}RW Audio RDS line length
+
+#define AUD_INIT 0x320100
+#define AUD_INIT_LD 0x320104
+#define AUD_SOFT_RESET 0x320108
+#define AUD_I2SINPUTCNTL 0x320120
+#define AUD_BAUDRATE 0x320124
+#define AUD_I2SOUTPUTCNTL 0x320128
+#define AAGC_HYST 0x320134
+#define AAGC_GAIN 0x320138
+#define AAGC_DEF 0x32013c
+#define AUD_IIR1_0_SEL 0x320150
+#define AUD_IIR1_0_SHIFT 0x320154
+#define AUD_IIR1_1_SEL 0x320158
+#define AUD_IIR1_1_SHIFT 0x32015c
+#define AUD_IIR1_2_SEL 0x320160
+#define AUD_IIR1_2_SHIFT 0x320164
+#define AUD_IIR1_3_SEL 0x320168
+#define AUD_IIR1_3_SHIFT 0x32016c
+#define AUD_IIR1_4_SEL 0x320170
+#define AUD_IIR1_4_SHIFT 0x32017c
+#define AUD_IIR1_5_SEL 0x320180
+#define AUD_IIR1_5_SHIFT 0x320184
+#define AUD_IIR2_0_SEL 0x320190
+#define AUD_IIR2_0_SHIFT 0x320194
+#define AUD_IIR2_1_SEL 0x320198
+#define AUD_IIR2_1_SHIFT 0x32019c
+#define AUD_IIR2_2_SEL 0x3201a0
+#define AUD_IIR2_2_SHIFT 0x3201a4
+#define AUD_IIR2_3_SEL 0x3201a8
+#define AUD_IIR2_3_SHIFT 0x3201ac
+#define AUD_IIR3_0_SEL 0x3201c0
+#define AUD_IIR3_0_SHIFT 0x3201c4
+#define AUD_IIR3_1_SEL 0x3201c8
+#define AUD_IIR3_1_SHIFT 0x3201cc
+#define AUD_IIR3_2_SEL 0x3201d0
+#define AUD_IIR3_2_SHIFT 0x3201d4
+#define AUD_IIR4_0_SEL 0x3201e0
+#define AUD_IIR4_0_SHIFT 0x3201e4
+#define AUD_IIR4_1_SEL 0x3201e8
+#define AUD_IIR4_1_SHIFT 0x3201ec
+#define AUD_IIR4_2_SEL 0x3201f0
+#define AUD_IIR4_2_SHIFT 0x3201f4
+#define AUD_IIR4_0_CA0 0x320200
+#define AUD_IIR4_0_CA1 0x320204
+#define AUD_IIR4_0_CA2 0x320208
+#define AUD_IIR4_0_CB0 0x32020c
+#define AUD_IIR4_0_CB1 0x320210
+#define AUD_IIR4_1_CA0 0x320214
+#define AUD_IIR4_1_CA1 0x320218
+#define AUD_IIR4_1_CA2 0x32021c
+#define AUD_IIR4_1_CB0 0x320220
+#define AUD_IIR4_1_CB1 0x320224
+#define AUD_IIR4_2_CA0 0x320228
+#define AUD_IIR4_2_CA1 0x32022c
+#define AUD_IIR4_2_CA2 0x320230
+#define AUD_IIR4_2_CB0 0x320234
+#define AUD_IIR4_2_CB1 0x320238
+#define AUD_HP_MD_IIR4_1 0x320250
+#define AUD_HP_PROG_IIR4_1 0x320254
+#define AUD_FM_MODE_ENABLE 0x320258
+#define AUD_POLY0_DDS_CONSTANT 0x320270
+#define AUD_DN0_FREQ 0x320274
+#define AUD_DN1_FREQ 0x320278
+#define AUD_DN1_FREQ_SHIFT 0x32027c
+#define AUD_DN1_AFC 0x320280
+#define AUD_DN1_SRC_SEL 0x320284
+#define AUD_DN1_SHFT 0x320288
+#define AUD_DN2_FREQ 0x32028c
+#define AUD_DN2_FREQ_SHIFT 0x320290
+#define AUD_DN2_AFC 0x320294
+#define AUD_DN2_SRC_SEL 0x320298
+#define AUD_DN2_SHFT 0x32029c
+#define AUD_CRDC0_SRC_SEL 0x320300
+#define AUD_CRDC0_SHIFT 0x320304
+#define AUD_CORDIC_SHIFT_0 0x320308
+#define AUD_CRDC1_SRC_SEL 0x32030c
+#define AUD_CRDC1_SHIFT 0x320310
+#define AUD_CORDIC_SHIFT_1 0x320314
+#define AUD_DCOC_0_SRC 0x320320
+#define AUD_DCOC0_SHIFT 0x320324
+#define AUD_DCOC_0_SHIFT_IN0 0x320328
+#define AUD_DCOC_0_SHIFT_IN1 0x32032c
+#define AUD_DCOC_1_SRC 0x320330
+#define AUD_DCOC1_SHIFT 0x320334
+#define AUD_DCOC_1_SHIFT_IN0 0x320338
+#define AUD_DCOC_1_SHIFT_IN1 0x32033c
+#define AUD_DCOC_2_SRC 0x320340
+#define AUD_DCOC2_SHIFT 0x320344
+#define AUD_DCOC_2_SHIFT_IN0 0x320348
+#define AUD_DCOC_2_SHIFT_IN1 0x32034c
+#define AUD_DCOC_PASS_IN 0x320350
+#define AUD_PDET_SRC 0x320370
+#define AUD_PDET_SHIFT 0x320374
+#define AUD_PILOT_BQD_1_K0 0x320380
+#define AUD_PILOT_BQD_1_K1 0x320384
+#define AUD_PILOT_BQD_1_K2 0x320388
+#define AUD_PILOT_BQD_1_K3 0x32038c
+#define AUD_PILOT_BQD_1_K4 0x320390
+#define AUD_PILOT_BQD_2_K0 0x320394
+#define AUD_PILOT_BQD_2_K1 0x320398
+#define AUD_PILOT_BQD_2_K2 0x32039c
+#define AUD_PILOT_BQD_2_K3 0x3203a0
+#define AUD_PILOT_BQD_2_K4 0x3203a4
+#define AUD_THR_FR 0x3203c0
+#define AUD_X_PROG 0x3203c4
+#define AUD_Y_PROG 0x3203c8
+#define AUD_HARMONIC_MULT 0x3203cc
+#define AUD_C1_UP_THR 0x3203d0
+#define AUD_C1_LO_THR 0x3203d4
+#define AUD_C2_UP_THR 0x3203d8
+#define AUD_C2_LO_THR 0x3203dc
+#define AUD_PLL_EN 0x320400
+#define AUD_PLL_SRC 0x320404
+#define AUD_PLL_SHIFT 0x320408
+#define AUD_PLL_IF_SEL 0x32040c
+#define AUD_PLL_IF_SHIFT 0x320410
+#define AUD_BIQUAD_PLL_K0 0x320414
+#define AUD_BIQUAD_PLL_K1 0x320418
+#define AUD_BIQUAD_PLL_K2 0x32041c
+#define AUD_BIQUAD_PLL_K3 0x320420
+#define AUD_BIQUAD_PLL_K4 0x320424
+#define AUD_DEEMPH0_SRC_SEL 0x320440
+#define AUD_DEEMPH0_SHIFT 0x320444
+#define AUD_DEEMPH0_G0 0x320448
+#define AUD_DEEMPH0_A0 0x32044c
+#define AUD_DEEMPH0_B0 0x320450
+#define AUD_DEEMPH0_A1 0x320454
+#define AUD_DEEMPH0_B1 0x320458
+#define AUD_DEEMPH1_SRC_SEL 0x32045c
+#define AUD_DEEMPH1_SHIFT 0x320460
+#define AUD_DEEMPH1_G0 0x320464
+#define AUD_DEEMPH1_A0 0x320468
+#define AUD_DEEMPH1_B0 0x32046c
+#define AUD_DEEMPH1_A1 0x320470
+#define AUD_DEEMPH1_B1 0x320474
+#define AUD_OUT0_SEL 0x320490
+#define AUD_OUT0_SHIFT 0x320494
+#define AUD_OUT1_SEL 0x320498
+#define AUD_OUT1_SHIFT 0x32049c
+#define AUD_RDSI_SEL 0x3204a0
+#define AUD_RDSI_SHIFT 0x3204a4
+#define AUD_RDSQ_SEL 0x3204a8
+#define AUD_RDSQ_SHIFT 0x3204ac
+#define AUD_DBX_IN_GAIN 0x320500
+#define AUD_DBX_WBE_GAIN 0x320504
+#define AUD_DBX_SE_GAIN 0x320508
+#define AUD_DBX_RMS_WBE 0x32050c
+#define AUD_DBX_RMS_SE 0x320510
+#define AUD_DBX_SE_BYPASS 0x320514
+#define AUD_FAWDETCTL 0x320530
+#define AUD_FAWDETWINCTL 0x320534
+#define AUD_DEEMPHGAIN_R 0x320538
+#define AUD_DEEMPHNUMER1_R 0x32053c
+#define AUD_DEEMPHNUMER2_R 0x320540
+#define AUD_DEEMPHDENOM1_R 0x320544
+#define AUD_DEEMPHDENOM2_R 0x320548
+#define AUD_ERRLOGPERIOD_R 0x32054c
+#define AUD_ERRINTRPTTHSHLD1_R 0x320550
+#define AUD_ERRINTRPTTHSHLD2_R 0x320554
+#define AUD_ERRINTRPTTHSHLD3_R 0x320558
+#define AUD_NICAM_STATUS1 0x32055c
+#define AUD_NICAM_STATUS2 0x320560
+#define AUD_ERRLOG1 0x320564
+#define AUD_ERRLOG2 0x320568
+#define AUD_ERRLOG3 0x32056c
+#define AUD_DAC_BYPASS_L 0x320580
+#define AUD_DAC_BYPASS_R 0x320584
+#define AUD_DAC_BYPASS_CTL 0x320588
+#define AUD_CTL 0x32058c
+#define AUD_STATUS 0x320590
+#define AUD_VOL_CTL 0x320594
+#define AUD_BAL_CTL 0x320598
+#define AUD_START_TIMER 0x3205b0
+#define AUD_MODE_CHG_TIMER 0x3205b4
+#define AUD_POLYPH80SCALEFAC 0x3205b8
+#define AUD_DMD_RA_DDS 0x3205bc
+#define AUD_I2S_RA_DDS 0x3205c0
+#define AUD_RATE_THRES_DMD 0x3205d0
+#define AUD_RATE_THRES_I2S 0x3205d4
+#define AUD_RATE_ADJ1 0x3205d8
+#define AUD_RATE_ADJ2 0x3205dc
+#define AUD_RATE_ADJ3 0x3205e0
+#define AUD_RATE_ADJ4 0x3205e4
+#define AUD_RATE_ADJ5 0x3205e8
+#define AUD_APB_IN_RATE_ADJ 0x3205ec
+#define AUD_I2SCNTL 0x3205ec
+#define AUD_PHASE_FIX_CTL 0x3205f0
+#define AUD_PLL_PRESCALE 0x320600
+#define AUD_PLL_DDS 0x320604
+#define AUD_PLL_INT 0x320608
+#define AUD_PLL_FRAC 0x32060c
+#define AUD_PLL_JTAG 0x320620
+#define AUD_PLL_SPMP 0x320624
+#define AUD_AFE_12DB_EN 0x320628
+
+// Audio QAM Register Addresses
+#define AUD_PDF_DDS_CNST_BYTE2 0x320d01
+#define AUD_PDF_DDS_CNST_BYTE1 0x320d02
+#define AUD_PDF_DDS_CNST_BYTE0 0x320d03
+#define AUD_PHACC_FREQ_8MSB 0x320d2a
+#define AUD_PHACC_FREQ_8LSB 0x320d2b
+#define AUD_QAM_MODE 0x320d04
+
+/*
+ * transport stream registers
+ */
+
+#define MO_TS_DMA 0x330000 // {64}RWp Transport stream downstream
+#define MO_TS_GPCNT 0x33C020 // {16}RO TS general purpose counter
+#define MO_TS_GPCNTRL 0x33C030 // {2}WO TS general purpose control
+#define MO_TS_DMACNTRL 0x33C040 // {6}RW TS DMA control
+#define MO_TS_XFR_STAT 0x33C044 // {1}RO TS transfer status
+#define MO_TS_LNGTH 0x33C048 // {12}RW TS line length
+
+#define TS_HW_SOP_CNTRL 0x33C04C
+#define TS_GEN_CNTRL 0x33C050
+#define TS_BD_PKT_STAT 0x33C054
+#define TS_SOP_STAT 0x33C058
+#define TS_FIFO_OVFL_STAT 0x33C05C
+#define TS_VALERR_CNTRL 0x33C060
+
+/*
+ * VIP registers
+ */
+
+#define MO_VIPD_DMA 0x340000 // {64}RWp VIP downstream
+#define MO_VIPU_DMA 0x340008 // {64}RWp VIP upstream
+#define MO_VIPD_GPCNT 0x34C020 // {16}RO VIP down general purpose counter
+#define MO_VIPU_GPCNT 0x34C024 // {16}RO VIP up general purpose counter
+#define MO_VIPD_GPCNTRL 0x34C030 // {2}WO VIP down general purpose control
+#define MO_VIPU_GPCNTRL 0x34C034 // {2}WO VIP up general purpose control
+#define MO_VIP_DMACNTRL 0x34C040 // {6}RW VIP DMA control
+#define MO_VIP_XFR_STAT 0x34C044 // {1}RO VIP transfer status
+#define MO_VIP_CFG 0x340048 // VIP configuration
+#define MO_VIPU_CNTRL 0x34004C // VIP upstream control #1
+#define MO_VIPD_CNTRL 0x340050 // VIP downstream control #2
+#define MO_VIPD_LNGTH 0x340054 // VIP downstream line length
+#define MO_VIP_BRSTLN 0x340058 // VIP burst length
+#define MO_VIP_INTCNTRL 0x34C05C // VIP Interrupt Control
+#define MO_VIP_XFTERM 0x340060 // VIP transfer terminate
+
+/*
+ * misc registers
+ */
+
+#define MO_M2M_DMA 0x350000 // {64}RWp Mem2Mem DMA Bfr
+#define MO_GP0_IO 0x350010 // {32}RW* GPIOoutput enablesdata I/O
+#define MO_GP1_IO 0x350014 // {32}RW* GPIOoutput enablesdata I/O
+#define MO_GP2_IO 0x350018 // {32}RW* GPIOoutput enablesdata I/O
+#define MO_GP3_IO 0x35001C // {32}RW* GPIO Mode/Ctrloutput enables
+#define MO_GPIO 0x350020 // {32}RW* GPIO I2C Ctrldata I/O
+#define MO_GPOE 0x350024 // {32}RW GPIO I2C Ctrloutput enables
+#define MO_GP_ISM 0x350028 // {16}WO GPIO Intr Sens/Pol
+
+#define MO_PLL_B 0x35C008 // {32}RW* PLL Control for ASB bus clks
+#define MO_M2M_CNT 0x35C024 // {32}RW Mem2Mem DMA Cnt
+#define MO_M2M_XSUM 0x35C028 // {32}RO M2M XOR-Checksum
+#define MO_CRC 0x35C02C // {16}RW CRC16 init/result
+#define MO_CRC_D 0x35C030 // {32}WO CRC16 new data in
+#define MO_TM_CNT_LDW 0x35C034 // {32}RO Timer : Counter low dword
+#define MO_TM_CNT_UW 0x35C038 // {16}RO Timer : Counter high word
+#define MO_TM_LMT_LDW 0x35C03C // {32}RW Timer : Limit low dword
+#define MO_TM_LMT_UW 0x35C040 // {32}RW Timer : Limit high word
+#define MO_PINMUX_IO 0x35C044 // {8}RW Pin Mux Control
+#define MO_TSTSEL_IO 0x35C048 // {2}RW Pin Mux Control
+#define MO_AFECFG_IO 0x35C04C // AFE configuration reg
+#define MO_DDS_IO 0x35C050 // DDS Increment reg
+#define MO_DDSCFG_IO 0x35C054 // DDS Configuration reg
+#define MO_SAMPLE_IO 0x35C058 // IRIn sample reg
+#define MO_SRST_IO 0x35C05C // Output system reset reg
+
+#define MO_INT1_MSK 0x35C060 // DMA RISC interrupt mask
+#define MO_INT1_STAT 0x35C064 // DMA RISC interrupt status
+#define MO_INT1_MSTAT 0x35C068 // DMA RISC interrupt masked status
+
+/*
+ * i2c bus registers
+ */
+
+#define MO_I2C 0x368000 // I2C data/control
+#define MO_I2C_DIV (0xf<<4)
+#define MO_I2C_SYNC (1<<3)
+#define MO_I2C_W3B (1<<2)
+#define MO_I2C_SCL (1<<1)
+#define MO_I2C_SDA (1<<0)
+
+
+/*
+ * general purpose host registers
+ *
+ * FIXME: tyops? s/0x35/0x38/ ??
+ */
+
+#define MO_GPHSTD_DMA 0x350000 // {64}RWp Host downstream
+#define MO_GPHSTU_DMA 0x350008 // {64}RWp Host upstream
+#define MO_GPHSTU_CNTRL 0x380048 // Host upstream control #1
+#define MO_GPHSTD_CNTRL 0x38004C // Host downstream control #2
+#define MO_GPHSTD_LNGTH 0x380050 // Host downstream line length
+#define MO_GPHST_WSC 0x380054 // Host wait state control
+#define MO_GPHST_XFR 0x380058 // Host transfer control
+#define MO_GPHST_WDTH 0x38005C // Host interface width
+#define MO_GPHST_HDSHK 0x380060 // Host peripheral handshake
+#define MO_GPHST_MUX16 0x380064 // Host muxed 16-bit transfer parameters
+#define MO_GPHST_MODE 0x380068 // Host mode select
+
+#define MO_GPHSTD_GPCNT 0x35C020 // Host down general purpose counter
+#define MO_GPHSTU_GPCNT 0x35C024 // Host up general purpose counter
+#define MO_GPHSTD_GPCNTRL 0x38C030 // Host down general purpose control
+#define MO_GPHSTU_GPCNTRL 0x38C034 // Host up general purpose control
+#define MO_GPHST_DMACNTRL 0x38C040 // Host DMA control
+#define MO_GPHST_XFR_STAT 0x38C044 // Host transfer status
+#define MO_GPHST_SOFT_RST 0x38C06C // Host software reset
+
+/*
+ * RISC instructions
+ */
+
+#define RISC_SYNC 0x80000000
+#define RISC_SYNC_ODD 0x80000000
+#define RISC_SYNC_EVEN 0x80000200
+#define RISC_RESYNC 0x80008000
+#define RISC_RESYNC_ODD 0x80008000
+#define RISC_RESYNC_EVEN 0x80008200
+#define RISC_WRITE 0x10000000
+#define RISC_WRITEC 0x50000000
+#define RISC_READ 0x90000000
+#define RISC_READC 0xA0000000
+#define RISC_JUMP 0x70000000
+#define RISC_SKIP 0x20000000
+#define RISC_WRITERM 0xB0000000
+#define RISC_WRITECM 0xC0000000
+#define RISC_WRITECR 0xD0000000
+#define RISC_IMM 0x00000001
+
+#define RISC_SOL 0x08000000
+#define RISC_EOL 0x04000000
+
+#define RISC_IRQ2 0x02000000
+#define RISC_IRQ1 0x01000000
+
+#define RISC_CNT_NONE 0x00000000
+#define RISC_CNT_INC 0x00010000
+#define RISC_CNT_RSVR 0x00020000
+#define RISC_CNT_RESET 0x00030000
+#define RISC_JMP_SRP 0x01
+
+/*
+ * various constants
+ */
+
+// DMA
+/* Interrupt mask/status */
+#define PCI_INT_VIDINT (1 << 0)
+#define PCI_INT_AUDINT (1 << 1)
+#define PCI_INT_TSINT (1 << 2)
+#define PCI_INT_VIPINT (1 << 3)
+#define PCI_INT_HSTINT (1 << 4)
+#define PCI_INT_TM1INT (1 << 5)
+#define PCI_INT_SRCDMAINT (1 << 6)
+#define PCI_INT_DSTDMAINT (1 << 7)
+#define PCI_INT_RISC_RD_BERRINT (1 << 10)
+#define PCI_INT_RISC_WR_BERRINT (1 << 11)
+#define PCI_INT_BRDG_BERRINT (1 << 12)
+#define PCI_INT_SRC_DMA_BERRINT (1 << 13)
+#define PCI_INT_DST_DMA_BERRINT (1 << 14)
+#define PCI_INT_IPB_DMA_BERRINT (1 << 15)
+#define PCI_INT_I2CDONE (1 << 16)
+#define PCI_INT_I2CRACK (1 << 17)
+#define PCI_INT_IR_SMPINT (1 << 18)
+#define PCI_INT_GPIO_INT0 (1 << 19)
+#define PCI_INT_GPIO_INT1 (1 << 20)
+
+#define SEL_BTSC 0x01
+#define SEL_EIAJ 0x02
+#define SEL_A2 0x04
+#define SEL_SAP 0x08
+#define SEL_NICAM 0x10
+#define SEL_FMRADIO 0x20
+
+// AUD_CTL
+#define AUD_INT_DN_RISCI1 (1 << 0)
+#define AUD_INT_UP_RISCI1 (1 << 1)
+#define AUD_INT_RDS_DN_RISCI1 (1 << 2)
+#define AUD_INT_DN_RISCI2 (1 << 4) /* yes, 3 is skipped */
+#define AUD_INT_UP_RISCI2 (1 << 5)
+#define AUD_INT_RDS_DN_RISCI2 (1 << 6)
+#define AUD_INT_DN_SYNC (1 << 12)
+#define AUD_INT_UP_SYNC (1 << 13)
+#define AUD_INT_RDS_DN_SYNC (1 << 14)
+#define AUD_INT_OPC_ERR (1 << 16)
+#define AUD_INT_BER_IRQ (1 << 20)
+#define AUD_INT_MCHG_IRQ (1 << 21)
+
+#define EN_BTSC_FORCE_MONO 0
+#define EN_BTSC_FORCE_STEREO 1
+#define EN_BTSC_FORCE_SAP 2
+#define EN_BTSC_AUTO_STEREO 3
+#define EN_BTSC_AUTO_SAP 4
+
+#define EN_A2_FORCE_MONO1 8
+#define EN_A2_FORCE_MONO2 9
+#define EN_A2_FORCE_STEREO 10
+#define EN_A2_AUTO_MONO2 11
+#define EN_A2_AUTO_STEREO 12
+
+#define EN_EIAJ_FORCE_MONO1 16
+#define EN_EIAJ_FORCE_MONO2 17
+#define EN_EIAJ_FORCE_STEREO 18
+#define EN_EIAJ_AUTO_MONO2 19
+#define EN_EIAJ_AUTO_STEREO 20
+
+#define EN_NICAM_FORCE_MONO1 32
+#define EN_NICAM_FORCE_MONO2 33
+#define EN_NICAM_FORCE_STEREO 34
+#define EN_NICAM_AUTO_MONO2 35
+#define EN_NICAM_AUTO_STEREO 36
+
+#define EN_FMRADIO_FORCE_MONO 24
+#define EN_FMRADIO_FORCE_STEREO 25
+#define EN_FMRADIO_AUTO_STEREO 26
+
+#define EN_NICAM_AUTO_FALLBACK 0x00000040
+#define EN_FMRADIO_EN_RDS 0x00000200
+#define EN_NICAM_TRY_AGAIN_BIT 0x00000400
+#define EN_DAC_ENABLE 0x00001000
+#define EN_I2SOUT_ENABLE 0x00002000
+#define EN_I2SIN_STR2DAC 0x00004000
+#define EN_I2SIN_ENABLE 0x00008000
+
+#define EN_DMTRX_SUMDIFF (0 << 7)
+#define EN_DMTRX_SUMR (1 << 7)
+#define EN_DMTRX_LR (2 << 7)
+#define EN_DMTRX_MONO (3 << 7)
+#define EN_DMTRX_BYPASS (1 << 11)
+
+// Video
+#define VID_CAPTURE_CONTROL 0x310180
+
+#define CX23880_CAP_CTL_CAPTURE_VBI_ODD (1<<3)
+#define CX23880_CAP_CTL_CAPTURE_VBI_EVEN (1<<2)
+#define CX23880_CAP_CTL_CAPTURE_ODD (1<<1)
+#define CX23880_CAP_CTL_CAPTURE_EVEN (1<<0)
+
+#define VideoInputMux0 0x0
+#define VideoInputMux1 0x1
+#define VideoInputMux2 0x2
+#define VideoInputMux3 0x3
+#define VideoInputTuner 0x0
+#define VideoInputComposite 0x1
+#define VideoInputSVideo 0x2
+#define VideoInputOther 0x3
+
+#define Xtal0 0x1
+#define Xtal1 0x2
+#define XtalAuto 0x3
+
+#define VideoFormatAuto 0x0
+#define VideoFormatNTSC 0x1
+#define VideoFormatNTSCJapan 0x2
+#define VideoFormatNTSC443 0x3
+#define VideoFormatPAL 0x4
+#define VideoFormatPALB 0x4
+#define VideoFormatPALD 0x4
+#define VideoFormatPALG 0x4
+#define VideoFormatPALH 0x4
+#define VideoFormatPALI 0x4
+#define VideoFormatPALBDGHI 0x4
+#define VideoFormatPALM 0x5
+#define VideoFormatPALN 0x6
+#define VideoFormatPALNC 0x7
+#define VideoFormatPAL60 0x8
+#define VideoFormatSECAM 0x9
+
+#define VideoFormatAuto27MHz 0x10
+#define VideoFormatNTSC27MHz 0x11
+#define VideoFormatNTSCJapan27MHz 0x12
+#define VideoFormatNTSC44327MHz 0x13
+#define VideoFormatPAL27MHz 0x14
+#define VideoFormatPALB27MHz 0x14
+#define VideoFormatPALD27MHz 0x14
+#define VideoFormatPALG27MHz 0x14
+#define VideoFormatPALH27MHz 0x14
+#define VideoFormatPALI27MHz 0x14
+#define VideoFormatPALBDGHI27MHz 0x14
+#define VideoFormatPALM27MHz 0x15
+#define VideoFormatPALN27MHz 0x16
+#define VideoFormatPALNC27MHz 0x17
+#define VideoFormatPAL6027MHz 0x18
+#define VideoFormatSECAM27MHz 0x19
+
+#define NominalUSECAM 0x87
+#define NominalVSECAM 0x85
+#define NominalUNTSC 0xFE
+#define NominalVNTSC 0xB4
+
+#define NominalContrast 0xD8
+
+#define HFilterAutoFormat 0x0
+#define HFilterCIF 0x1
+#define HFilterQCIF 0x2
+#define HFilterICON 0x3
+
+#define VFilter2TapInterpolate 0
+#define VFilter3TapInterpolate 1
+#define VFilter4TapInterpolate 2
+#define VFilter5TapInterpolate 3
+#define VFilter2TapNoInterpolate 4
+#define VFilter3TapNoInterpolate 5
+#define VFilter4TapNoInterpolate 6
+#define VFilter5TapNoInterpolate 7
+
+#define ColorFormatRGB32 0x0000
+#define ColorFormatRGB24 0x0011
+#define ColorFormatRGB16 0x0022
+#define ColorFormatRGB15 0x0033
+#define ColorFormatYUY2 0x0044
+#define ColorFormatBTYUV 0x0055
+#define ColorFormatY8 0x0066
+#define ColorFormatRGB8 0x0077
+#define ColorFormatPL422 0x0088
+#define ColorFormatPL411 0x0099
+#define ColorFormatYUV12 0x00AA
+#define ColorFormatYUV9 0x00BB
+#define ColorFormatRAW 0x00EE
+#define ColorFormatBSWAP 0x0300
+#define ColorFormatWSWAP 0x0c00
+#define ColorFormatEvenMask 0x050f
+#define ColorFormatOddMask 0x0af0
+#define ColorFormatGamma 0x1000
+
+#define Interlaced 0x1
+#define NonInterlaced 0x0
+
+#define FieldEven 0x1
+#define FieldOdd 0x0
+
+#define TGReadWriteMode 0x0
+#define TGEnableMode 0x1
+
+#define DV_CbAlign 0x0
+#define DV_Y0Align 0x1
+#define DV_CrAlign 0x2
+#define DV_Y1Align 0x3
+
+#define DVF_Analog 0x0
+#define DVF_CCIR656 0x1
+#define DVF_ByteStream 0x2
+#define DVF_ExtVSYNC 0x4
+#define DVF_ExtField 0x5
+
+#define CHANNEL_VID_Y 0x1
+#define CHANNEL_VID_U 0x2
+#define CHANNEL_VID_V 0x3
+#define CHANNEL_VID_VBI 0x4
+#define CHANNEL_AUD_DN 0x5
+#define CHANNEL_AUD_UP 0x6
+#define CHANNEL_AUD_RDS_DN 0x7
+#define CHANNEL_MPEG_DN 0x8
+#define CHANNEL_VIP_DN 0x9
+#define CHANNEL_VIP_UP 0xA
+#define CHANNEL_HOST_DN 0xB
+#define CHANNEL_HOST_UP 0xC
+#define CHANNEL_FIRST 0x1
+#define CHANNEL_LAST 0xC
+
+#define GP_COUNT_CONTROL_NONE 0x0
+#define GP_COUNT_CONTROL_INC 0x1
+#define GP_COUNT_CONTROL_RESERVED 0x2
+#define GP_COUNT_CONTROL_RESET 0x3
+
+#define PLL_PRESCALE_BY_2 2
+#define PLL_PRESCALE_BY_3 3
+#define PLL_PRESCALE_BY_4 4
+#define PLL_PRESCALE_BY_5 5
+
+#define HLNotchFilter4xFsc 0
+#define HLNotchFilterSquare 1
+#define HLNotchFilter135NTSC 2
+#define HLNotchFilter135PAL 3
+
+#define NTSC_8x_SUB_CARRIER 28.63636E6
+#define PAL_8x_SUB_CARRIER 35.46895E6
+
+// Default analog settings
+#define DEFAULT_HUE_NTSC 0x00
+#define DEFAULT_BRIGHTNESS_NTSC 0x00
+#define DEFAULT_CONTRAST_NTSC 0x39
+#define DEFAULT_SAT_U_NTSC 0x7F
+#define DEFAULT_SAT_V_NTSC 0x5A
+
+#endif /* _CX88_REG_H_ */
diff --git a/drivers/media/pci/cx88/cx88-tvaudio.c b/drivers/media/pci/cx88/cx88-tvaudio.c
new file mode 100644
index 000000000..9d25d77fb
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-tvaudio.c
@@ -0,0 +1,1046 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * cx88x-audio.c - Conexant CX23880/23881 audio downstream driver driver
+ *
+ * (c) 2001 Michael Eskin, Tom Zakrajsek [Windows version]
+ * (c) 2002 Yurij Sysoev <yurij@naturesoft.net>
+ * (c) 2003 Gerd Knorr <kraxel@bytesex.org>
+ *
+ * -----------------------------------------------------------------------
+ *
+ * Lot of voodoo here. Even the data sheet doesn't help to
+ * understand what is going on here, the documentation for the audio
+ * part of the cx2388x chip is *very* bad.
+ *
+ * Some of this comes from party done linux driver sources I got from
+ * [undocumented].
+ *
+ * Some comes from the dscaler sources, one of the dscaler driver guy works
+ * for Conexant ...
+ *
+ * -----------------------------------------------------------------------
+ */
+
+#include "cx88.h"
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/freezer.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/poll.h>
+#include <linux/signal.h>
+#include <linux/ioport.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+
+static unsigned int audio_debug;
+module_param(audio_debug, int, 0644);
+MODULE_PARM_DESC(audio_debug, "enable debug messages [audio]");
+
+static unsigned int always_analog;
+module_param(always_analog, int, 0644);
+MODULE_PARM_DESC(always_analog, "force analog audio out");
+
+static unsigned int radio_deemphasis;
+module_param(radio_deemphasis, int, 0644);
+MODULE_PARM_DESC(radio_deemphasis,
+ "Radio deemphasis time constant, 0=None, 1=50us (elsewhere), 2=75us (USA)");
+
+#define dprintk(fmt, arg...) do { \
+ if (audio_debug) \
+ printk(KERN_DEBUG pr_fmt("%s: tvaudio:" fmt), \
+ __func__, ##arg); \
+} while (0)
+/* ----------------------------------------------------------- */
+
+static const char * const aud_ctl_names[64] = {
+ [EN_BTSC_FORCE_MONO] = "BTSC_FORCE_MONO",
+ [EN_BTSC_FORCE_STEREO] = "BTSC_FORCE_STEREO",
+ [EN_BTSC_FORCE_SAP] = "BTSC_FORCE_SAP",
+ [EN_BTSC_AUTO_STEREO] = "BTSC_AUTO_STEREO",
+ [EN_BTSC_AUTO_SAP] = "BTSC_AUTO_SAP",
+ [EN_A2_FORCE_MONO1] = "A2_FORCE_MONO1",
+ [EN_A2_FORCE_MONO2] = "A2_FORCE_MONO2",
+ [EN_A2_FORCE_STEREO] = "A2_FORCE_STEREO",
+ [EN_A2_AUTO_MONO2] = "A2_AUTO_MONO2",
+ [EN_A2_AUTO_STEREO] = "A2_AUTO_STEREO",
+ [EN_EIAJ_FORCE_MONO1] = "EIAJ_FORCE_MONO1",
+ [EN_EIAJ_FORCE_MONO2] = "EIAJ_FORCE_MONO2",
+ [EN_EIAJ_FORCE_STEREO] = "EIAJ_FORCE_STEREO",
+ [EN_EIAJ_AUTO_MONO2] = "EIAJ_AUTO_MONO2",
+ [EN_EIAJ_AUTO_STEREO] = "EIAJ_AUTO_STEREO",
+ [EN_NICAM_FORCE_MONO1] = "NICAM_FORCE_MONO1",
+ [EN_NICAM_FORCE_MONO2] = "NICAM_FORCE_MONO2",
+ [EN_NICAM_FORCE_STEREO] = "NICAM_FORCE_STEREO",
+ [EN_NICAM_AUTO_MONO2] = "NICAM_AUTO_MONO2",
+ [EN_NICAM_AUTO_STEREO] = "NICAM_AUTO_STEREO",
+ [EN_FMRADIO_FORCE_MONO] = "FMRADIO_FORCE_MONO",
+ [EN_FMRADIO_FORCE_STEREO] = "FMRADIO_FORCE_STEREO",
+ [EN_FMRADIO_AUTO_STEREO] = "FMRADIO_AUTO_STEREO",
+};
+
+struct rlist {
+ u32 reg;
+ u32 val;
+};
+
+static void set_audio_registers(struct cx88_core *core, const struct rlist *l)
+{
+ int i;
+
+ for (i = 0; l[i].reg; i++) {
+ switch (l[i].reg) {
+ case AUD_PDF_DDS_CNST_BYTE2:
+ case AUD_PDF_DDS_CNST_BYTE1:
+ case AUD_PDF_DDS_CNST_BYTE0:
+ case AUD_QAM_MODE:
+ case AUD_PHACC_FREQ_8MSB:
+ case AUD_PHACC_FREQ_8LSB:
+ cx_writeb(l[i].reg, l[i].val);
+ break;
+ default:
+ cx_write(l[i].reg, l[i].val);
+ break;
+ }
+ }
+}
+
+static void set_audio_start(struct cx88_core *core, u32 mode)
+{
+ /* mute */
+ cx_write(AUD_VOL_CTL, (1 << 6));
+
+ /* start programming */
+ cx_write(AUD_INIT, mode);
+ cx_write(AUD_INIT_LD, 0x0001);
+ cx_write(AUD_SOFT_RESET, 0x0001);
+}
+
+static void set_audio_finish(struct cx88_core *core, u32 ctl)
+{
+ u32 volume;
+
+ /* restart dma; This avoids buzz in NICAM and is good in others */
+ cx88_stop_audio_dma(core);
+ cx_write(AUD_RATE_THRES_DMD, 0x000000C0);
+ cx88_start_audio_dma(core);
+
+ if (core->board.mpeg & CX88_MPEG_BLACKBIRD) {
+ cx_write(AUD_I2SINPUTCNTL, 4);
+ cx_write(AUD_BAUDRATE, 1);
+ /*
+ * 'pass-thru mode': this enables the i2s
+ * output to the mpeg encoder
+ */
+ cx_set(AUD_CTL, EN_I2SOUT_ENABLE);
+ cx_write(AUD_I2SOUTPUTCNTL, 1);
+ cx_write(AUD_I2SCNTL, 0);
+ /* cx_write(AUD_APB_IN_RATE_ADJ, 0); */
+ }
+ if ((always_analog) || (!(core->board.mpeg & CX88_MPEG_BLACKBIRD))) {
+ ctl |= EN_DAC_ENABLE;
+ cx_write(AUD_CTL, ctl);
+ }
+
+ /* finish programming */
+ cx_write(AUD_SOFT_RESET, 0x0000);
+
+ /* unmute */
+ volume = cx_sread(SHADOW_AUD_VOL_CTL);
+ cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, volume);
+
+ core->last_change = jiffies;
+}
+
+/* ----------------------------------------------------------- */
+
+static void set_audio_standard_BTSC(struct cx88_core *core, unsigned int sap,
+ u32 mode)
+{
+ static const struct rlist btsc[] = {
+ {AUD_AFE_12DB_EN, 0x00000001},
+ {AUD_OUT1_SEL, 0x00000013},
+ {AUD_OUT1_SHIFT, 0x00000000},
+ {AUD_POLY0_DDS_CONSTANT, 0x0012010c},
+ {AUD_DMD_RA_DDS, 0x00c3e7aa},
+ {AUD_DBX_IN_GAIN, 0x00004734},
+ {AUD_DBX_WBE_GAIN, 0x00004640},
+ {AUD_DBX_SE_GAIN, 0x00008d31},
+ {AUD_DCOC_0_SRC, 0x0000001a},
+ {AUD_IIR1_4_SEL, 0x00000021},
+ {AUD_DCOC_PASS_IN, 0x00000003},
+ {AUD_DCOC_0_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_0_SHIFT_IN1, 0x00000008},
+ {AUD_DCOC_1_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_1_SHIFT_IN1, 0x00000008},
+ {AUD_DN0_FREQ, 0x0000283b},
+ {AUD_DN2_SRC_SEL, 0x00000008},
+ {AUD_DN2_FREQ, 0x00003000},
+ {AUD_DN2_AFC, 0x00000002},
+ {AUD_DN2_SHFT, 0x00000000},
+ {AUD_IIR2_2_SEL, 0x00000020},
+ {AUD_IIR2_2_SHIFT, 0x00000000},
+ {AUD_IIR2_3_SEL, 0x0000001f},
+ {AUD_IIR2_3_SHIFT, 0x00000000},
+ {AUD_CRDC1_SRC_SEL, 0x000003ce},
+ {AUD_CRDC1_SHIFT, 0x00000000},
+ {AUD_CORDIC_SHIFT_1, 0x00000007},
+ {AUD_DCOC_1_SRC, 0x0000001b},
+ {AUD_DCOC1_SHIFT, 0x00000000},
+ {AUD_RDSI_SEL, 0x00000008},
+ {AUD_RDSQ_SEL, 0x00000008},
+ {AUD_RDSI_SHIFT, 0x00000000},
+ {AUD_RDSQ_SHIFT, 0x00000000},
+ {AUD_POLYPH80SCALEFAC, 0x00000003},
+ { /* end of list */ },
+ };
+ static const struct rlist btsc_sap[] = {
+ {AUD_AFE_12DB_EN, 0x00000001},
+ {AUD_DBX_IN_GAIN, 0x00007200},
+ {AUD_DBX_WBE_GAIN, 0x00006200},
+ {AUD_DBX_SE_GAIN, 0x00006200},
+ {AUD_IIR1_1_SEL, 0x00000000},
+ {AUD_IIR1_3_SEL, 0x00000001},
+ {AUD_DN1_SRC_SEL, 0x00000007},
+ {AUD_IIR1_4_SHIFT, 0x00000006},
+ {AUD_IIR2_1_SHIFT, 0x00000000},
+ {AUD_IIR2_2_SHIFT, 0x00000000},
+ {AUD_IIR3_0_SHIFT, 0x00000000},
+ {AUD_IIR3_1_SHIFT, 0x00000000},
+ {AUD_IIR3_0_SEL, 0x0000000d},
+ {AUD_IIR3_1_SEL, 0x0000000e},
+ {AUD_DEEMPH1_SRC_SEL, 0x00000014},
+ {AUD_DEEMPH1_SHIFT, 0x00000000},
+ {AUD_DEEMPH1_G0, 0x00004000},
+ {AUD_DEEMPH1_A0, 0x00000000},
+ {AUD_DEEMPH1_B0, 0x00000000},
+ {AUD_DEEMPH1_A1, 0x00000000},
+ {AUD_DEEMPH1_B1, 0x00000000},
+ {AUD_OUT0_SEL, 0x0000003f},
+ {AUD_OUT1_SEL, 0x0000003f},
+ {AUD_DN1_AFC, 0x00000002},
+ {AUD_DCOC_0_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_0_SHIFT_IN1, 0x00000008},
+ {AUD_DCOC_1_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_1_SHIFT_IN1, 0x00000008},
+ {AUD_IIR1_0_SEL, 0x0000001d},
+ {AUD_IIR1_2_SEL, 0x0000001e},
+ {AUD_IIR2_1_SEL, 0x00000002},
+ {AUD_IIR2_2_SEL, 0x00000004},
+ {AUD_IIR3_2_SEL, 0x0000000f},
+ {AUD_DCOC2_SHIFT, 0x00000001},
+ {AUD_IIR3_2_SHIFT, 0x00000001},
+ {AUD_DEEMPH0_SRC_SEL, 0x00000014},
+ {AUD_CORDIC_SHIFT_1, 0x00000006},
+ {AUD_POLY0_DDS_CONSTANT, 0x000e4db2},
+ {AUD_DMD_RA_DDS, 0x00f696e6},
+ {AUD_IIR2_3_SEL, 0x00000025},
+ {AUD_IIR1_4_SEL, 0x00000021},
+ {AUD_DN1_FREQ, 0x0000c965},
+ {AUD_DCOC_PASS_IN, 0x00000003},
+ {AUD_DCOC_0_SRC, 0x0000001a},
+ {AUD_DCOC_1_SRC, 0x0000001b},
+ {AUD_DCOC1_SHIFT, 0x00000000},
+ {AUD_RDSI_SEL, 0x00000009},
+ {AUD_RDSQ_SEL, 0x00000009},
+ {AUD_RDSI_SHIFT, 0x00000000},
+ {AUD_RDSQ_SHIFT, 0x00000000},
+ {AUD_POLYPH80SCALEFAC, 0x00000003},
+ { /* end of list */ },
+ };
+
+ mode |= EN_FMRADIO_EN_RDS;
+
+ if (sap) {
+ dprintk("%s SAP (status: unknown)\n", __func__);
+ set_audio_start(core, SEL_SAP);
+ set_audio_registers(core, btsc_sap);
+ set_audio_finish(core, mode);
+ } else {
+ dprintk("%s (status: known-good)\n", __func__);
+ set_audio_start(core, SEL_BTSC);
+ set_audio_registers(core, btsc);
+ set_audio_finish(core, mode);
+ }
+}
+
+static void set_audio_standard_NICAM(struct cx88_core *core, u32 mode)
+{
+ static const struct rlist nicam_l[] = {
+ {AUD_AFE_12DB_EN, 0x00000001},
+ {AUD_RATE_ADJ1, 0x00000060},
+ {AUD_RATE_ADJ2, 0x000000F9},
+ {AUD_RATE_ADJ3, 0x000001CC},
+ {AUD_RATE_ADJ4, 0x000002B3},
+ {AUD_RATE_ADJ5, 0x00000726},
+ {AUD_DEEMPHDENOM1_R, 0x0000F3D0},
+ {AUD_DEEMPHDENOM2_R, 0x00000000},
+ {AUD_ERRLOGPERIOD_R, 0x00000064},
+ {AUD_ERRINTRPTTHSHLD1_R, 0x00000FFF},
+ {AUD_ERRINTRPTTHSHLD2_R, 0x0000001F},
+ {AUD_ERRINTRPTTHSHLD3_R, 0x0000000F},
+ {AUD_POLYPH80SCALEFAC, 0x00000003},
+ {AUD_DMD_RA_DDS, 0x00C00000},
+ {AUD_PLL_INT, 0x0000001E},
+ {AUD_PLL_DDS, 0x00000000},
+ {AUD_PLL_FRAC, 0x0000E542},
+ {AUD_START_TIMER, 0x00000000},
+ {AUD_DEEMPHNUMER1_R, 0x000353DE},
+ {AUD_DEEMPHNUMER2_R, 0x000001B1},
+ {AUD_PDF_DDS_CNST_BYTE2, 0x06},
+ {AUD_PDF_DDS_CNST_BYTE1, 0x82},
+ {AUD_PDF_DDS_CNST_BYTE0, 0x12},
+ {AUD_QAM_MODE, 0x05},
+ {AUD_PHACC_FREQ_8MSB, 0x34},
+ {AUD_PHACC_FREQ_8LSB, 0x4C},
+ {AUD_DEEMPHGAIN_R, 0x00006680},
+ {AUD_RATE_THRES_DMD, 0x000000C0},
+ { /* end of list */ },
+ };
+
+ static const struct rlist nicam_bgdki_common[] = {
+ {AUD_AFE_12DB_EN, 0x00000001},
+ {AUD_RATE_ADJ1, 0x00000010},
+ {AUD_RATE_ADJ2, 0x00000040},
+ {AUD_RATE_ADJ3, 0x00000100},
+ {AUD_RATE_ADJ4, 0x00000400},
+ {AUD_RATE_ADJ5, 0x00001000},
+ {AUD_ERRLOGPERIOD_R, 0x00000fff},
+ {AUD_ERRINTRPTTHSHLD1_R, 0x000003ff},
+ {AUD_ERRINTRPTTHSHLD2_R, 0x000000ff},
+ {AUD_ERRINTRPTTHSHLD3_R, 0x0000003f},
+ {AUD_POLYPH80SCALEFAC, 0x00000003},
+ {AUD_DEEMPHGAIN_R, 0x000023c2},
+ {AUD_DEEMPHNUMER1_R, 0x0002a7bc},
+ {AUD_DEEMPHNUMER2_R, 0x0003023e},
+ {AUD_DEEMPHDENOM1_R, 0x0000f3d0},
+ {AUD_DEEMPHDENOM2_R, 0x00000000},
+ {AUD_PDF_DDS_CNST_BYTE2, 0x06},
+ {AUD_PDF_DDS_CNST_BYTE1, 0x82},
+ {AUD_QAM_MODE, 0x05},
+ { /* end of list */ },
+ };
+
+ static const struct rlist nicam_i[] = {
+ {AUD_PDF_DDS_CNST_BYTE0, 0x12},
+ {AUD_PHACC_FREQ_8MSB, 0x3a},
+ {AUD_PHACC_FREQ_8LSB, 0x93},
+ { /* end of list */ },
+ };
+
+ static const struct rlist nicam_default[] = {
+ {AUD_PDF_DDS_CNST_BYTE0, 0x16},
+ {AUD_PHACC_FREQ_8MSB, 0x34},
+ {AUD_PHACC_FREQ_8LSB, 0x4c},
+ { /* end of list */ },
+ };
+
+ set_audio_start(core, SEL_NICAM);
+ switch (core->tvaudio) {
+ case WW_L:
+ dprintk("%s SECAM-L NICAM (status: devel)\n", __func__);
+ set_audio_registers(core, nicam_l);
+ break;
+ case WW_I:
+ dprintk("%s PAL-I NICAM (status: known-good)\n", __func__);
+ set_audio_registers(core, nicam_bgdki_common);
+ set_audio_registers(core, nicam_i);
+ break;
+ case WW_NONE:
+ case WW_BTSC:
+ case WW_BG:
+ case WW_DK:
+ case WW_EIAJ:
+ case WW_I2SPT:
+ case WW_FM:
+ case WW_I2SADC:
+ case WW_M:
+ dprintk("%s PAL-BGDK NICAM (status: known-good)\n", __func__);
+ set_audio_registers(core, nicam_bgdki_common);
+ set_audio_registers(core, nicam_default);
+ break;
+ }
+
+ mode |= EN_DMTRX_LR | EN_DMTRX_BYPASS;
+ set_audio_finish(core, mode);
+}
+
+static void set_audio_standard_A2(struct cx88_core *core, u32 mode)
+{
+ static const struct rlist a2_bgdk_common[] = {
+ {AUD_ERRLOGPERIOD_R, 0x00000064},
+ {AUD_ERRINTRPTTHSHLD1_R, 0x00000fff},
+ {AUD_ERRINTRPTTHSHLD2_R, 0x0000001f},
+ {AUD_ERRINTRPTTHSHLD3_R, 0x0000000f},
+ {AUD_PDF_DDS_CNST_BYTE2, 0x06},
+ {AUD_PDF_DDS_CNST_BYTE1, 0x82},
+ {AUD_PDF_DDS_CNST_BYTE0, 0x12},
+ {AUD_QAM_MODE, 0x05},
+ {AUD_PHACC_FREQ_8MSB, 0x34},
+ {AUD_PHACC_FREQ_8LSB, 0x4c},
+ {AUD_RATE_ADJ1, 0x00000100},
+ {AUD_RATE_ADJ2, 0x00000200},
+ {AUD_RATE_ADJ3, 0x00000300},
+ {AUD_RATE_ADJ4, 0x00000400},
+ {AUD_RATE_ADJ5, 0x00000500},
+ {AUD_THR_FR, 0x00000000},
+ {AAGC_HYST, 0x0000001a},
+ {AUD_PILOT_BQD_1_K0, 0x0000755b},
+ {AUD_PILOT_BQD_1_K1, 0x00551340},
+ {AUD_PILOT_BQD_1_K2, 0x006d30be},
+ {AUD_PILOT_BQD_1_K3, 0xffd394af},
+ {AUD_PILOT_BQD_1_K4, 0x00400000},
+ {AUD_PILOT_BQD_2_K0, 0x00040000},
+ {AUD_PILOT_BQD_2_K1, 0x002a4841},
+ {AUD_PILOT_BQD_2_K2, 0x00400000},
+ {AUD_PILOT_BQD_2_K3, 0x00000000},
+ {AUD_PILOT_BQD_2_K4, 0x00000000},
+ {AUD_MODE_CHG_TIMER, 0x00000040},
+ {AUD_AFE_12DB_EN, 0x00000001},
+ {AUD_CORDIC_SHIFT_0, 0x00000007},
+ {AUD_CORDIC_SHIFT_1, 0x00000007},
+ {AUD_DEEMPH0_G0, 0x00000380},
+ {AUD_DEEMPH1_G0, 0x00000380},
+ {AUD_DCOC_0_SRC, 0x0000001a},
+ {AUD_DCOC0_SHIFT, 0x00000000},
+ {AUD_DCOC_0_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_0_SHIFT_IN1, 0x00000008},
+ {AUD_DCOC_PASS_IN, 0x00000003},
+ {AUD_IIR3_0_SEL, 0x00000021},
+ {AUD_DN2_AFC, 0x00000002},
+ {AUD_DCOC_1_SRC, 0x0000001b},
+ {AUD_DCOC1_SHIFT, 0x00000000},
+ {AUD_DCOC_1_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_1_SHIFT_IN1, 0x00000008},
+ {AUD_IIR3_1_SEL, 0x00000023},
+ {AUD_RDSI_SEL, 0x00000017},
+ {AUD_RDSI_SHIFT, 0x00000000},
+ {AUD_RDSQ_SEL, 0x00000017},
+ {AUD_RDSQ_SHIFT, 0x00000000},
+ {AUD_PLL_INT, 0x0000001e},
+ {AUD_PLL_DDS, 0x00000000},
+ {AUD_PLL_FRAC, 0x0000e542},
+ {AUD_POLYPH80SCALEFAC, 0x00000001},
+ {AUD_START_TIMER, 0x00000000},
+ { /* end of list */ },
+ };
+
+ static const struct rlist a2_bg[] = {
+ {AUD_DMD_RA_DDS, 0x002a4f2f},
+ {AUD_C1_UP_THR, 0x00007000},
+ {AUD_C1_LO_THR, 0x00005400},
+ {AUD_C2_UP_THR, 0x00005400},
+ {AUD_C2_LO_THR, 0x00003000},
+ { /* end of list */ },
+ };
+
+ static const struct rlist a2_dk[] = {
+ {AUD_DMD_RA_DDS, 0x002a4f2f},
+ {AUD_C1_UP_THR, 0x00007000},
+ {AUD_C1_LO_THR, 0x00005400},
+ {AUD_C2_UP_THR, 0x00005400},
+ {AUD_C2_LO_THR, 0x00003000},
+ {AUD_DN0_FREQ, 0x00003a1c},
+ {AUD_DN2_FREQ, 0x0000d2e0},
+ { /* end of list */ },
+ };
+
+ static const struct rlist a1_i[] = {
+ {AUD_ERRLOGPERIOD_R, 0x00000064},
+ {AUD_ERRINTRPTTHSHLD1_R, 0x00000fff},
+ {AUD_ERRINTRPTTHSHLD2_R, 0x0000001f},
+ {AUD_ERRINTRPTTHSHLD3_R, 0x0000000f},
+ {AUD_PDF_DDS_CNST_BYTE2, 0x06},
+ {AUD_PDF_DDS_CNST_BYTE1, 0x82},
+ {AUD_PDF_DDS_CNST_BYTE0, 0x12},
+ {AUD_QAM_MODE, 0x05},
+ {AUD_PHACC_FREQ_8MSB, 0x3a},
+ {AUD_PHACC_FREQ_8LSB, 0x93},
+ {AUD_DMD_RA_DDS, 0x002a4f2f},
+ {AUD_PLL_INT, 0x0000001e},
+ {AUD_PLL_DDS, 0x00000004},
+ {AUD_PLL_FRAC, 0x0000e542},
+ {AUD_RATE_ADJ1, 0x00000100},
+ {AUD_RATE_ADJ2, 0x00000200},
+ {AUD_RATE_ADJ3, 0x00000300},
+ {AUD_RATE_ADJ4, 0x00000400},
+ {AUD_RATE_ADJ5, 0x00000500},
+ {AUD_THR_FR, 0x00000000},
+ {AUD_PILOT_BQD_1_K0, 0x0000755b},
+ {AUD_PILOT_BQD_1_K1, 0x00551340},
+ {AUD_PILOT_BQD_1_K2, 0x006d30be},
+ {AUD_PILOT_BQD_1_K3, 0xffd394af},
+ {AUD_PILOT_BQD_1_K4, 0x00400000},
+ {AUD_PILOT_BQD_2_K0, 0x00040000},
+ {AUD_PILOT_BQD_2_K1, 0x002a4841},
+ {AUD_PILOT_BQD_2_K2, 0x00400000},
+ {AUD_PILOT_BQD_2_K3, 0x00000000},
+ {AUD_PILOT_BQD_2_K4, 0x00000000},
+ {AUD_MODE_CHG_TIMER, 0x00000060},
+ {AUD_AFE_12DB_EN, 0x00000001},
+ {AAGC_HYST, 0x0000000a},
+ {AUD_CORDIC_SHIFT_0, 0x00000007},
+ {AUD_CORDIC_SHIFT_1, 0x00000007},
+ {AUD_C1_UP_THR, 0x00007000},
+ {AUD_C1_LO_THR, 0x00005400},
+ {AUD_C2_UP_THR, 0x00005400},
+ {AUD_C2_LO_THR, 0x00003000},
+ {AUD_DCOC_0_SRC, 0x0000001a},
+ {AUD_DCOC0_SHIFT, 0x00000000},
+ {AUD_DCOC_0_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_0_SHIFT_IN1, 0x00000008},
+ {AUD_DCOC_PASS_IN, 0x00000003},
+ {AUD_IIR3_0_SEL, 0x00000021},
+ {AUD_DN2_AFC, 0x00000002},
+ {AUD_DCOC_1_SRC, 0x0000001b},
+ {AUD_DCOC1_SHIFT, 0x00000000},
+ {AUD_DCOC_1_SHIFT_IN0, 0x0000000a},
+ {AUD_DCOC_1_SHIFT_IN1, 0x00000008},
+ {AUD_IIR3_1_SEL, 0x00000023},
+ {AUD_DN0_FREQ, 0x000035a3},
+ {AUD_DN2_FREQ, 0x000029c7},
+ {AUD_CRDC0_SRC_SEL, 0x00000511},
+ {AUD_IIR1_0_SEL, 0x00000001},
+ {AUD_IIR1_1_SEL, 0x00000000},
+ {AUD_IIR3_2_SEL, 0x00000003},
+ {AUD_IIR3_2_SHIFT, 0x00000000},
+ {AUD_IIR3_0_SEL, 0x00000002},
+ {AUD_IIR2_0_SEL, 0x00000021},
+ {AUD_IIR2_0_SHIFT, 0x00000002},
+ {AUD_DEEMPH0_SRC_SEL, 0x0000000b},
+ {AUD_DEEMPH1_SRC_SEL, 0x0000000b},
+ {AUD_POLYPH80SCALEFAC, 0x00000001},
+ {AUD_START_TIMER, 0x00000000},
+ { /* end of list */ },
+ };
+
+ static const struct rlist am_l[] = {
+ {AUD_ERRLOGPERIOD_R, 0x00000064},
+ {AUD_ERRINTRPTTHSHLD1_R, 0x00000FFF},
+ {AUD_ERRINTRPTTHSHLD2_R, 0x0000001F},
+ {AUD_ERRINTRPTTHSHLD3_R, 0x0000000F},
+ {AUD_PDF_DDS_CNST_BYTE2, 0x48},
+ {AUD_PDF_DDS_CNST_BYTE1, 0x3D},
+ {AUD_QAM_MODE, 0x00},
+ {AUD_PDF_DDS_CNST_BYTE0, 0xf5},
+ {AUD_PHACC_FREQ_8MSB, 0x3a},
+ {AUD_PHACC_FREQ_8LSB, 0x4a},
+ {AUD_DEEMPHGAIN_R, 0x00006680},
+ {AUD_DEEMPHNUMER1_R, 0x000353DE},
+ {AUD_DEEMPHNUMER2_R, 0x000001B1},
+ {AUD_DEEMPHDENOM1_R, 0x0000F3D0},
+ {AUD_DEEMPHDENOM2_R, 0x00000000},
+ {AUD_FM_MODE_ENABLE, 0x00000007},
+ {AUD_POLYPH80SCALEFAC, 0x00000003},
+ {AUD_AFE_12DB_EN, 0x00000001},
+ {AAGC_GAIN, 0x00000000},
+ {AAGC_HYST, 0x00000018},
+ {AAGC_DEF, 0x00000020},
+ {AUD_DN0_FREQ, 0x00000000},
+ {AUD_POLY0_DDS_CONSTANT, 0x000E4DB2},
+ {AUD_DCOC_0_SRC, 0x00000021},
+ {AUD_IIR1_0_SEL, 0x00000000},
+ {AUD_IIR1_0_SHIFT, 0x00000007},
+ {AUD_IIR1_1_SEL, 0x00000002},
+ {AUD_IIR1_1_SHIFT, 0x00000000},
+ {AUD_DCOC_1_SRC, 0x00000003},
+ {AUD_DCOC1_SHIFT, 0x00000000},
+ {AUD_DCOC_PASS_IN, 0x00000000},
+ {AUD_IIR1_2_SEL, 0x00000023},
+ {AUD_IIR1_2_SHIFT, 0x00000000},
+ {AUD_IIR1_3_SEL, 0x00000004},
+ {AUD_IIR1_3_SHIFT, 0x00000007},
+ {AUD_IIR1_4_SEL, 0x00000005},
+ {AUD_IIR1_4_SHIFT, 0x00000007},
+ {AUD_IIR3_0_SEL, 0x00000007},
+ {AUD_IIR3_0_SHIFT, 0x00000000},
+ {AUD_DEEMPH0_SRC_SEL, 0x00000011},
+ {AUD_DEEMPH0_SHIFT, 0x00000000},
+ {AUD_DEEMPH0_G0, 0x00007000},
+ {AUD_DEEMPH0_A0, 0x00000000},
+ {AUD_DEEMPH0_B0, 0x00000000},
+ {AUD_DEEMPH0_A1, 0x00000000},
+ {AUD_DEEMPH0_B1, 0x00000000},
+ {AUD_DEEMPH1_SRC_SEL, 0x00000011},
+ {AUD_DEEMPH1_SHIFT, 0x00000000},
+ {AUD_DEEMPH1_G0, 0x00007000},
+ {AUD_DEEMPH1_A0, 0x00000000},
+ {AUD_DEEMPH1_B0, 0x00000000},
+ {AUD_DEEMPH1_A1, 0x00000000},
+ {AUD_DEEMPH1_B1, 0x00000000},
+ {AUD_OUT0_SEL, 0x0000003F},
+ {AUD_OUT1_SEL, 0x0000003F},
+ {AUD_DMD_RA_DDS, 0x00F5C285},
+ {AUD_PLL_INT, 0x0000001E},
+ {AUD_PLL_DDS, 0x00000000},
+ {AUD_PLL_FRAC, 0x0000E542},
+ {AUD_RATE_ADJ1, 0x00000100},
+ {AUD_RATE_ADJ2, 0x00000200},
+ {AUD_RATE_ADJ3, 0x00000300},
+ {AUD_RATE_ADJ4, 0x00000400},
+ {AUD_RATE_ADJ5, 0x00000500},
+ {AUD_RATE_THRES_DMD, 0x000000C0},
+ { /* end of list */ },
+ };
+
+ static const struct rlist a2_deemph50[] = {
+ {AUD_DEEMPH0_G0, 0x00000380},
+ {AUD_DEEMPH1_G0, 0x00000380},
+ {AUD_DEEMPHGAIN_R, 0x000011e1},
+ {AUD_DEEMPHNUMER1_R, 0x0002a7bc},
+ {AUD_DEEMPHNUMER2_R, 0x0003023c},
+ { /* end of list */ },
+ };
+
+ set_audio_start(core, SEL_A2);
+ switch (core->tvaudio) {
+ case WW_BG:
+ dprintk("%s PAL-BG A1/2 (status: known-good)\n", __func__);
+ set_audio_registers(core, a2_bgdk_common);
+ set_audio_registers(core, a2_bg);
+ set_audio_registers(core, a2_deemph50);
+ break;
+ case WW_DK:
+ dprintk("%s PAL-DK A1/2 (status: known-good)\n", __func__);
+ set_audio_registers(core, a2_bgdk_common);
+ set_audio_registers(core, a2_dk);
+ set_audio_registers(core, a2_deemph50);
+ break;
+ case WW_I:
+ dprintk("%s PAL-I A1 (status: known-good)\n", __func__);
+ set_audio_registers(core, a1_i);
+ set_audio_registers(core, a2_deemph50);
+ break;
+ case WW_L:
+ dprintk("%s AM-L (status: devel)\n", __func__);
+ set_audio_registers(core, am_l);
+ break;
+ case WW_NONE:
+ case WW_BTSC:
+ case WW_EIAJ:
+ case WW_I2SPT:
+ case WW_FM:
+ case WW_I2SADC:
+ case WW_M:
+ dprintk("%s Warning: wrong value\n", __func__);
+ return;
+ }
+
+ mode |= EN_FMRADIO_EN_RDS | EN_DMTRX_SUMDIFF;
+ set_audio_finish(core, mode);
+}
+
+static void set_audio_standard_EIAJ(struct cx88_core *core)
+{
+ static const struct rlist eiaj[] = {
+ /* TODO: eiaj register settings are not there yet ... */
+
+ { /* end of list */ },
+ };
+ dprintk("%s (status: unknown)\n", __func__);
+
+ set_audio_start(core, SEL_EIAJ);
+ set_audio_registers(core, eiaj);
+ set_audio_finish(core, EN_EIAJ_AUTO_STEREO);
+}
+
+static void set_audio_standard_FM(struct cx88_core *core,
+ enum cx88_deemph_type deemph)
+{
+ static const struct rlist fm_deemph_50[] = {
+ {AUD_DEEMPH0_G0, 0x0C45},
+ {AUD_DEEMPH0_A0, 0x6262},
+ {AUD_DEEMPH0_B0, 0x1C29},
+ {AUD_DEEMPH0_A1, 0x3FC66},
+ {AUD_DEEMPH0_B1, 0x399A},
+
+ {AUD_DEEMPH1_G0, 0x0D80},
+ {AUD_DEEMPH1_A0, 0x6262},
+ {AUD_DEEMPH1_B0, 0x1C29},
+ {AUD_DEEMPH1_A1, 0x3FC66},
+ {AUD_DEEMPH1_B1, 0x399A},
+
+ {AUD_POLYPH80SCALEFAC, 0x0003},
+ { /* end of list */ },
+ };
+ static const struct rlist fm_deemph_75[] = {
+ {AUD_DEEMPH0_G0, 0x091B},
+ {AUD_DEEMPH0_A0, 0x6B68},
+ {AUD_DEEMPH0_B0, 0x11EC},
+ {AUD_DEEMPH0_A1, 0x3FC66},
+ {AUD_DEEMPH0_B1, 0x399A},
+
+ {AUD_DEEMPH1_G0, 0x0AA0},
+ {AUD_DEEMPH1_A0, 0x6B68},
+ {AUD_DEEMPH1_B0, 0x11EC},
+ {AUD_DEEMPH1_A1, 0x3FC66},
+ {AUD_DEEMPH1_B1, 0x399A},
+
+ {AUD_POLYPH80SCALEFAC, 0x0003},
+ { /* end of list */ },
+ };
+
+ /*
+ * It is enough to leave default values?
+ *
+ * No, it's not! The deemphasis registers are reset to the 75us
+ * values by default. Analyzing the spectrum of the decoded audio
+ * reveals that "no deemphasis" is the same as 75 us, while the 50 us
+ * setting results in less deemphasis.
+ */
+ static const struct rlist fm_no_deemph[] = {
+ {AUD_POLYPH80SCALEFAC, 0x0003},
+ { /* end of list */ },
+ };
+
+ dprintk("%s (status: unknown)\n", __func__);
+ set_audio_start(core, SEL_FMRADIO);
+
+ switch (deemph) {
+ default:
+ case FM_NO_DEEMPH:
+ set_audio_registers(core, fm_no_deemph);
+ break;
+
+ case FM_DEEMPH_50:
+ set_audio_registers(core, fm_deemph_50);
+ break;
+
+ case FM_DEEMPH_75:
+ set_audio_registers(core, fm_deemph_75);
+ break;
+ }
+
+ set_audio_finish(core, EN_FMRADIO_AUTO_STEREO);
+}
+
+/* ----------------------------------------------------------- */
+
+static int cx88_detect_nicam(struct cx88_core *core)
+{
+ int i, j = 0;
+
+ dprintk("start nicam autodetect.\n");
+
+ for (i = 0; i < 6; i++) {
+ /* if bit1=1 then nicam is detected */
+ j += ((cx_read(AUD_NICAM_STATUS2) & 0x02) >> 1);
+
+ if (j == 1) {
+ dprintk("nicam is detected.\n");
+ return 1;
+ }
+
+ /* wait a little bit for next reading status */
+ usleep_range(10000, 20000);
+ }
+
+ dprintk("nicam is not detected.\n");
+ return 0;
+}
+
+void cx88_set_tvaudio(struct cx88_core *core)
+{
+ switch (core->tvaudio) {
+ case WW_BTSC:
+ set_audio_standard_BTSC(core, 0, EN_BTSC_AUTO_STEREO);
+ break;
+ case WW_BG:
+ case WW_DK:
+ case WW_M:
+ case WW_I:
+ case WW_L:
+ /* prepare all dsp registers */
+ set_audio_standard_A2(core, EN_A2_FORCE_MONO1);
+
+ /*
+ * set nicam mode - otherwise
+ * AUD_NICAM_STATUS2 contains wrong values
+ */
+ set_audio_standard_NICAM(core, EN_NICAM_AUTO_STEREO);
+ if (cx88_detect_nicam(core) == 0) {
+ /* fall back to fm / am mono */
+ set_audio_standard_A2(core, EN_A2_FORCE_MONO1);
+ core->audiomode_current = V4L2_TUNER_MODE_MONO;
+ core->use_nicam = 0;
+ } else {
+ core->use_nicam = 1;
+ }
+ break;
+ case WW_EIAJ:
+ set_audio_standard_EIAJ(core);
+ break;
+ case WW_FM:
+ set_audio_standard_FM(core, radio_deemphasis);
+ break;
+ case WW_I2SADC:
+ set_audio_start(core, 0x01);
+ /*
+ * Slave/Philips/Autobaud
+ * NB on Nova-S bit1 NPhilipsSony appears to be inverted:
+ * 0= Sony, 1=Philips
+ */
+ cx_write(AUD_I2SINPUTCNTL, core->board.i2sinputcntl);
+ /* Switch to "I2S ADC mode" */
+ cx_write(AUD_I2SCNTL, 0x1);
+ set_audio_finish(core, EN_I2SIN_ENABLE);
+ break;
+ case WW_NONE:
+ case WW_I2SPT:
+ pr_info("unknown tv audio mode [%d]\n", core->tvaudio);
+ break;
+ }
+}
+EXPORT_SYMBOL(cx88_set_tvaudio);
+
+void cx88_newstation(struct cx88_core *core)
+{
+ core->audiomode_manual = UNSET;
+ core->last_change = jiffies;
+}
+EXPORT_SYMBOL(cx88_newstation);
+
+void cx88_get_stereo(struct cx88_core *core, struct v4l2_tuner *t)
+{
+ static const char * const m[] = { "stereo", "dual mono",
+ "mono", "sap" };
+ static const char * const p[] = { "no pilot", "pilot c1",
+ "pilot c2", "?" };
+ u32 reg, mode, pilot;
+
+ reg = cx_read(AUD_STATUS);
+ mode = reg & 0x03;
+ pilot = (reg >> 2) & 0x03;
+
+ if (core->astat != reg)
+ dprintk("AUD_STATUS: 0x%x [%s/%s] ctl=%s\n",
+ reg, m[mode], p[pilot],
+ aud_ctl_names[cx_read(AUD_CTL) & 63]);
+ core->astat = reg;
+
+ t->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_SAP |
+ V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2;
+ t->rxsubchans = UNSET;
+ t->audmode = V4L2_TUNER_MODE_MONO;
+
+ switch (mode) {
+ case 0:
+ t->audmode = V4L2_TUNER_MODE_STEREO;
+ break;
+ case 1:
+ t->audmode = V4L2_TUNER_MODE_LANG2;
+ break;
+ case 2:
+ t->audmode = V4L2_TUNER_MODE_MONO;
+ break;
+ case 3:
+ t->audmode = V4L2_TUNER_MODE_SAP;
+ break;
+ }
+
+ switch (core->tvaudio) {
+ case WW_BTSC:
+ case WW_BG:
+ case WW_DK:
+ case WW_M:
+ case WW_EIAJ:
+ if (!core->use_nicam) {
+ t->rxsubchans = cx88_dsp_detect_stereo_sap(core);
+ break;
+ }
+ break;
+ case WW_NONE:
+ case WW_I:
+ case WW_L:
+ case WW_I2SPT:
+ case WW_FM:
+ case WW_I2SADC:
+ /* nothing */
+ break;
+ }
+
+ /* If software stereo detection is not supported... */
+ if (t->rxsubchans == UNSET) {
+ t->rxsubchans = V4L2_TUNER_SUB_MONO;
+ /*
+ * If the hardware itself detected stereo, also return
+ * stereo as an available subchannel
+ */
+ if (t->audmode == V4L2_TUNER_MODE_STEREO)
+ t->rxsubchans |= V4L2_TUNER_SUB_STEREO;
+ }
+}
+EXPORT_SYMBOL(cx88_get_stereo);
+
+
+void cx88_set_stereo(struct cx88_core *core, u32 mode, int manual)
+{
+ u32 ctl = UNSET;
+ u32 mask = UNSET;
+
+ if (manual) {
+ core->audiomode_manual = mode;
+ } else {
+ if (core->audiomode_manual != UNSET)
+ return;
+ }
+ core->audiomode_current = mode;
+
+ switch (core->tvaudio) {
+ case WW_BTSC:
+ switch (mode) {
+ case V4L2_TUNER_MODE_MONO:
+ set_audio_standard_BTSC(core, 0, EN_BTSC_FORCE_MONO);
+ break;
+ case V4L2_TUNER_MODE_LANG1:
+ set_audio_standard_BTSC(core, 0, EN_BTSC_AUTO_STEREO);
+ break;
+ case V4L2_TUNER_MODE_LANG2:
+ set_audio_standard_BTSC(core, 1, EN_BTSC_FORCE_SAP);
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ case V4L2_TUNER_MODE_LANG1_LANG2:
+ set_audio_standard_BTSC(core, 0, EN_BTSC_FORCE_STEREO);
+ break;
+ }
+ break;
+ case WW_BG:
+ case WW_DK:
+ case WW_M:
+ case WW_I:
+ case WW_L:
+ if (core->use_nicam == 1) {
+ switch (mode) {
+ case V4L2_TUNER_MODE_MONO:
+ case V4L2_TUNER_MODE_LANG1:
+ set_audio_standard_NICAM(core,
+ EN_NICAM_FORCE_MONO1);
+ break;
+ case V4L2_TUNER_MODE_LANG2:
+ set_audio_standard_NICAM(core,
+ EN_NICAM_FORCE_MONO2);
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ case V4L2_TUNER_MODE_LANG1_LANG2:
+ set_audio_standard_NICAM(core,
+ EN_NICAM_FORCE_STEREO);
+ break;
+ }
+ } else {
+ if ((core->tvaudio == WW_I) ||
+ (core->tvaudio == WW_L)) {
+ /* fall back to fm / am mono */
+ set_audio_standard_A2(core, EN_A2_FORCE_MONO1);
+ } else {
+ /* TODO: Add A2 autodection */
+ mask = 0x3f;
+ switch (mode) {
+ case V4L2_TUNER_MODE_MONO:
+ case V4L2_TUNER_MODE_LANG1:
+ ctl = EN_A2_FORCE_MONO1;
+ break;
+ case V4L2_TUNER_MODE_LANG2:
+ ctl = EN_A2_FORCE_MONO2;
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ case V4L2_TUNER_MODE_LANG1_LANG2:
+ ctl = EN_A2_FORCE_STEREO;
+ break;
+ }
+ }
+ }
+ break;
+ case WW_FM:
+ switch (mode) {
+ case V4L2_TUNER_MODE_MONO:
+ ctl = EN_FMRADIO_FORCE_MONO;
+ mask = 0x3f;
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ ctl = EN_FMRADIO_AUTO_STEREO;
+ mask = 0x3f;
+ break;
+ }
+ break;
+ case WW_I2SADC:
+ case WW_NONE:
+ case WW_EIAJ:
+ case WW_I2SPT:
+ /* DO NOTHING */
+ break;
+ }
+
+ if (ctl != UNSET) {
+ dprintk("cx88_set_stereo: mask 0x%x, ctl 0x%x [status=0x%x,ctl=0x%x,vol=0x%x]\n",
+ mask, ctl, cx_read(AUD_STATUS),
+ cx_read(AUD_CTL), cx_sread(SHADOW_AUD_VOL_CTL));
+ cx_andor(AUD_CTL, mask, ctl);
+ }
+}
+EXPORT_SYMBOL(cx88_set_stereo);
+
+int cx88_audio_thread(void *data)
+{
+ struct cx88_core *core = data;
+ struct v4l2_tuner t;
+ u32 mode = 0;
+
+ dprintk("cx88: tvaudio thread started\n");
+ set_freezable();
+ for (;;) {
+ msleep_interruptible(1000);
+ if (kthread_should_stop())
+ break;
+ try_to_freeze();
+
+ switch (core->tvaudio) {
+ case WW_BG:
+ case WW_DK:
+ case WW_M:
+ case WW_I:
+ case WW_L:
+ if (core->use_nicam)
+ goto hw_autodetect;
+
+ /* just monitor the audio status for now ... */
+ memset(&t, 0, sizeof(t));
+ cx88_get_stereo(core, &t);
+
+ if (core->audiomode_manual != UNSET)
+ /* manually set, don't do anything. */
+ continue;
+
+ /* monitor signal and set stereo if available */
+ if (t.rxsubchans & V4L2_TUNER_SUB_STEREO)
+ mode = V4L2_TUNER_MODE_STEREO;
+ else
+ mode = V4L2_TUNER_MODE_MONO;
+ if (mode == core->audiomode_current)
+ continue;
+ /* automatically switch to best available mode */
+ cx88_set_stereo(core, mode, 0);
+ break;
+ case WW_NONE:
+ case WW_BTSC:
+ case WW_EIAJ:
+ case WW_I2SPT:
+ case WW_FM:
+ case WW_I2SADC:
+hw_autodetect:
+ /*
+ * stereo autodetection is supported by hardware so
+ * we don't need to do it manually. Do nothing.
+ */
+ break;
+ }
+ }
+
+ dprintk("cx88: tvaudio thread exiting\n");
+ return 0;
+}
+EXPORT_SYMBOL(cx88_audio_thread);
diff --git a/drivers/media/pci/cx88/cx88-vbi.c b/drivers/media/pci/cx88/cx88-vbi.c
new file mode 100644
index 000000000..469aeaa72
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-vbi.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ */
+
+#include "cx88.h"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+static unsigned int vbi_debug;
+module_param(vbi_debug, int, 0644);
+MODULE_PARM_DESC(vbi_debug, "enable debug messages [vbi]");
+
+#define dprintk(level, fmt, arg...) do { \
+ if (vbi_debug >= level) \
+ printk(KERN_DEBUG pr_fmt("%s: vbi:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+/* ------------------------------------------------------------------ */
+
+int cx8800_vbi_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+
+ f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH;
+ f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+ f->fmt.vbi.offset = 244;
+
+ if (dev->core->tvnorm & V4L2_STD_525_60) {
+ /* ntsc */
+ f->fmt.vbi.sampling_rate = 28636363;
+ f->fmt.vbi.start[0] = 10;
+ f->fmt.vbi.start[1] = 273;
+ f->fmt.vbi.count[0] = VBI_LINE_NTSC_COUNT;
+ f->fmt.vbi.count[1] = VBI_LINE_NTSC_COUNT;
+
+ } else if (dev->core->tvnorm & V4L2_STD_625_50) {
+ /* pal */
+ f->fmt.vbi.sampling_rate = 35468950;
+ f->fmt.vbi.start[0] = V4L2_VBI_ITU_625_F1_START + 5;
+ f->fmt.vbi.start[1] = V4L2_VBI_ITU_625_F2_START + 5;
+ f->fmt.vbi.count[0] = VBI_LINE_PAL_COUNT;
+ f->fmt.vbi.count[1] = VBI_LINE_PAL_COUNT;
+ }
+ return 0;
+}
+
+static int cx8800_start_vbi_dma(struct cx8800_dev *dev,
+ struct cx88_dmaqueue *q,
+ struct cx88_buffer *buf)
+{
+ struct cx88_core *core = dev->core;
+
+ /* setup fifo + format */
+ cx88_sram_channel_setup(dev->core, &cx88_sram_channels[SRAM_CH24],
+ VBI_LINE_LENGTH, buf->risc.dma);
+
+ cx_write(MO_VBOS_CONTROL, (1 << 18) | /* comb filter delay fixup */
+ (1 << 15) | /* enable vbi capture */
+ (1 << 11));
+
+ /* reset counter */
+ cx_write(MO_VBI_GPCNTRL, GP_COUNT_CONTROL_RESET);
+ q->count = 0;
+
+ /* enable irqs */
+ cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_VIDINT);
+ cx_set(MO_VID_INTMSK, 0x0f0088);
+
+ /* enable capture */
+ cx_set(VID_CAPTURE_CONTROL, 0x18);
+
+ /* start dma */
+ cx_set(MO_DEV_CNTRL2, (1 << 5));
+ cx_set(MO_VID_DMACNTRL, 0x88);
+
+ return 0;
+}
+
+void cx8800_stop_vbi_dma(struct cx8800_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+
+ /* stop dma */
+ cx_clear(MO_VID_DMACNTRL, 0x88);
+
+ /* disable capture */
+ cx_clear(VID_CAPTURE_CONTROL, 0x18);
+
+ /* disable irqs */
+ cx_clear(MO_PCI_INTMSK, PCI_INT_VIDINT);
+ cx_clear(MO_VID_INTMSK, 0x0f0088);
+}
+
+int cx8800_restart_vbi_queue(struct cx8800_dev *dev,
+ struct cx88_dmaqueue *q)
+{
+ struct cx88_buffer *buf;
+
+ if (list_empty(&q->active))
+ return 0;
+
+ buf = list_entry(q->active.next, struct cx88_buffer, list);
+ dprintk(2, "restart_queue [%p/%d]: restart dma\n",
+ buf, buf->vb.vb2_buf.index);
+ cx8800_start_vbi_dma(dev, q, buf);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct cx8800_dev *dev = q->drv_priv;
+
+ *num_planes = 1;
+ if (dev->core->tvnorm & V4L2_STD_525_60)
+ sizes[0] = VBI_LINE_NTSC_COUNT * VBI_LINE_LENGTH * 2;
+ else
+ sizes[0] = VBI_LINE_PAL_COUNT * VBI_LINE_LENGTH * 2;
+ return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
+ unsigned int lines;
+ unsigned int size;
+
+ if (dev->core->tvnorm & V4L2_STD_525_60)
+ lines = VBI_LINE_NTSC_COUNT;
+ else
+ lines = VBI_LINE_PAL_COUNT;
+ size = lines * VBI_LINE_LENGTH * 2;
+ if (vb2_plane_size(vb, 0) < size)
+ return -EINVAL;
+ vb2_set_plane_payload(vb, 0, size);
+
+ return cx88_risc_buffer(dev->pci, &buf->risc, sgt->sgl,
+ 0, VBI_LINE_LENGTH * lines,
+ VBI_LINE_LENGTH, 0,
+ lines);
+}
+
+static void buffer_finish(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct cx88_riscmem *risc = &buf->risc;
+
+ if (risc->cpu)
+ dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu,
+ risc->dma);
+ memset(risc, 0, sizeof(*risc));
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct cx88_buffer *prev;
+ struct cx88_dmaqueue *q = &dev->vbiq;
+
+ /* add jump to start */
+ buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8);
+ buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC);
+ buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8);
+
+ if (list_empty(&q->active)) {
+ list_add_tail(&buf->list, &q->active);
+ dprintk(2, "[%p/%d] vbi_queue - first active\n",
+ buf, buf->vb.vb2_buf.index);
+
+ } else {
+ buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1);
+ prev = list_entry(q->active.prev, struct cx88_buffer, list);
+ list_add_tail(&buf->list, &q->active);
+ prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+ dprintk(2, "[%p/%d] buffer_queue - append to active\n",
+ buf, buf->vb.vb2_buf.index);
+ }
+}
+
+static int start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct cx8800_dev *dev = q->drv_priv;
+ struct cx88_dmaqueue *dmaq = &dev->vbiq;
+ struct cx88_buffer *buf = list_entry(dmaq->active.next,
+ struct cx88_buffer, list);
+
+ cx8800_start_vbi_dma(dev, dmaq, buf);
+ return 0;
+}
+
+static void stop_streaming(struct vb2_queue *q)
+{
+ struct cx8800_dev *dev = q->drv_priv;
+ struct cx88_core *core = dev->core;
+ struct cx88_dmaqueue *dmaq = &dev->vbiq;
+ unsigned long flags;
+
+ cx_clear(MO_VID_DMACNTRL, 0x11);
+ cx_clear(VID_CAPTURE_CONTROL, 0x06);
+ cx8800_stop_vbi_dma(dev);
+ spin_lock_irqsave(&dev->slock, flags);
+ while (!list_empty(&dmaq->active)) {
+ struct cx88_buffer *buf = list_entry(dmaq->active.next,
+ struct cx88_buffer, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+const struct vb2_ops cx8800_vbi_qops = {
+ .queue_setup = queue_setup,
+ .buf_prepare = buffer_prepare,
+ .buf_finish = buffer_finish,
+ .buf_queue = buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+};
diff --git a/drivers/media/pci/cx88/cx88-video.c b/drivers/media/pci/cx88/cx88-video.c
new file mode 100644
index 000000000..c0ef03ed7
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-video.c
@@ -0,0 +1,1635 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * device driver for Conexant 2388x based TV cards
+ * video4linux video interface
+ *
+ * (c) 2003-04 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
+ *
+ * (c) 2005-2006 Mauro Carvalho Chehab <mchehab@kernel.org>
+ * - Multituner support
+ * - video_ioctl2 conversion
+ * - PAL/M fixes
+ */
+
+#include "cx88.h"
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <asm/div64.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/i2c/wm8775.h>
+
+MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards");
+MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(CX88_VERSION);
+
+/* ------------------------------------------------------------------ */
+
+static unsigned int video_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
+static unsigned int vbi_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
+static unsigned int radio_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
+
+module_param_array(video_nr, int, NULL, 0444);
+module_param_array(vbi_nr, int, NULL, 0444);
+module_param_array(radio_nr, int, NULL, 0444);
+
+MODULE_PARM_DESC(video_nr, "video device numbers");
+MODULE_PARM_DESC(vbi_nr, "vbi device numbers");
+MODULE_PARM_DESC(radio_nr, "radio device numbers");
+
+static unsigned int video_debug;
+module_param(video_debug, int, 0644);
+MODULE_PARM_DESC(video_debug, "enable debug messages [video]");
+
+static unsigned int irq_debug;
+module_param(irq_debug, int, 0644);
+MODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]");
+
+#define dprintk(level, fmt, arg...) do { \
+ if (video_debug >= level) \
+ printk(KERN_DEBUG pr_fmt("%s: video:" fmt), \
+ __func__, ##arg); \
+} while (0)
+
+/* ------------------------------------------------------------------- */
+/* static data */
+
+static const struct cx8800_fmt formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_GREY,
+ .cxformat = ColorFormatY8,
+ .depth = 8,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB555,
+ .cxformat = ColorFormatRGB15,
+ .depth = 16,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB555X,
+ .cxformat = ColorFormatRGB15 | ColorFormatBSWAP,
+ .depth = 16,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .cxformat = ColorFormatRGB16,
+ .depth = 16,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB565X,
+ .cxformat = ColorFormatRGB16 | ColorFormatBSWAP,
+ .depth = 16,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR24,
+ .cxformat = ColorFormatRGB24,
+ .depth = 24,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR32,
+ .cxformat = ColorFormatRGB32,
+ .depth = 32,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .cxformat = ColorFormatRGB32 | ColorFormatBSWAP |
+ ColorFormatWSWAP,
+ .depth = 32,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .cxformat = ColorFormatYUY2,
+ .depth = 16,
+ .flags = FORMAT_FLAGS_PACKED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .cxformat = ColorFormatYUY2 | ColorFormatBSWAP,
+ .depth = 16,
+ .flags = FORMAT_FLAGS_PACKED,
+ },
+};
+
+static const struct cx8800_fmt *format_by_fourcc(unsigned int fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); i++)
+ if (formats[i].fourcc == fourcc)
+ return formats + i;
+ return NULL;
+}
+
+/* ------------------------------------------------------------------- */
+
+struct cx88_ctrl {
+ /* control information */
+ u32 id;
+ s32 minimum;
+ s32 maximum;
+ u32 step;
+ s32 default_value;
+
+ /* control register information */
+ u32 off;
+ u32 reg;
+ u32 sreg;
+ u32 mask;
+ u32 shift;
+};
+
+static const struct cx88_ctrl cx8800_vid_ctls[] = {
+ /* --- video --- */
+ {
+ .id = V4L2_CID_BRIGHTNESS,
+ .minimum = 0x00,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0x7f,
+ .off = 128,
+ .reg = MO_CONTR_BRIGHT,
+ .mask = 0x00ff,
+ .shift = 0,
+ }, {
+ .id = V4L2_CID_CONTRAST,
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0x3f,
+ .off = 0,
+ .reg = MO_CONTR_BRIGHT,
+ .mask = 0xff00,
+ .shift = 8,
+ }, {
+ .id = V4L2_CID_HUE,
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0x7f,
+ .off = 128,
+ .reg = MO_HUE,
+ .mask = 0x00ff,
+ .shift = 0,
+ }, {
+ /* strictly, this only describes only U saturation.
+ * V saturation is handled specially through code.
+ */
+ .id = V4L2_CID_SATURATION,
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0x7f,
+ .off = 0,
+ .reg = MO_UV_SATURATION,
+ .mask = 0x00ff,
+ .shift = 0,
+ }, {
+ .id = V4L2_CID_SHARPNESS,
+ .minimum = 0,
+ .maximum = 4,
+ .step = 1,
+ .default_value = 0x0,
+ .off = 0,
+ /*
+ * NOTE: the value is converted and written to both even
+ * and odd registers in the code
+ */
+ .reg = MO_FILTER_ODD,
+ .mask = 7 << 7,
+ .shift = 7,
+ }, {
+ .id = V4L2_CID_CHROMA_AGC,
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0x1,
+ .reg = MO_INPUT_FORMAT,
+ .mask = 1 << 10,
+ .shift = 10,
+ }, {
+ .id = V4L2_CID_COLOR_KILLER,
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0x1,
+ .reg = MO_INPUT_FORMAT,
+ .mask = 1 << 9,
+ .shift = 9,
+ }, {
+ .id = V4L2_CID_BAND_STOP_FILTER,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0x0,
+ .off = 0,
+ .reg = MO_HTOTAL,
+ .mask = 3 << 11,
+ .shift = 11,
+ }
+};
+
+static const struct cx88_ctrl cx8800_aud_ctls[] = {
+ {
+ /* --- audio --- */
+ .id = V4L2_CID_AUDIO_MUTE,
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .reg = AUD_VOL_CTL,
+ .sreg = SHADOW_AUD_VOL_CTL,
+ .mask = (1 << 6),
+ .shift = 6,
+ }, {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .minimum = 0,
+ .maximum = 0x3f,
+ .step = 1,
+ .default_value = 0x3f,
+ .reg = AUD_VOL_CTL,
+ .sreg = SHADOW_AUD_VOL_CTL,
+ .mask = 0x3f,
+ .shift = 0,
+ }, {
+ .id = V4L2_CID_AUDIO_BALANCE,
+ .minimum = 0,
+ .maximum = 0x7f,
+ .step = 1,
+ .default_value = 0x40,
+ .reg = AUD_BAL_CTL,
+ .sreg = SHADOW_AUD_BAL_CTL,
+ .mask = 0x7f,
+ .shift = 0,
+ }
+};
+
+enum {
+ CX8800_VID_CTLS = ARRAY_SIZE(cx8800_vid_ctls),
+ CX8800_AUD_CTLS = ARRAY_SIZE(cx8800_aud_ctls),
+};
+
+/* ------------------------------------------------------------------ */
+
+int cx88_video_mux(struct cx88_core *core, unsigned int input)
+{
+ /* struct cx88_core *core = dev->core; */
+
+ dprintk(1, "video_mux: %d [vmux=%d,gpio=0x%x,0x%x,0x%x,0x%x]\n",
+ input, INPUT(input).vmux,
+ INPUT(input).gpio0, INPUT(input).gpio1,
+ INPUT(input).gpio2, INPUT(input).gpio3);
+ core->input = input;
+ cx_andor(MO_INPUT_FORMAT, 0x03 << 14, INPUT(input).vmux << 14);
+ cx_write(MO_GP3_IO, INPUT(input).gpio3);
+ cx_write(MO_GP0_IO, INPUT(input).gpio0);
+ cx_write(MO_GP1_IO, INPUT(input).gpio1);
+ cx_write(MO_GP2_IO, INPUT(input).gpio2);
+
+ switch (INPUT(input).type) {
+ case CX88_VMUX_SVIDEO:
+ cx_set(MO_AFECFG_IO, 0x00000001);
+ cx_set(MO_INPUT_FORMAT, 0x00010010);
+ cx_set(MO_FILTER_EVEN, 0x00002020);
+ cx_set(MO_FILTER_ODD, 0x00002020);
+ break;
+ default:
+ cx_clear(MO_AFECFG_IO, 0x00000001);
+ cx_clear(MO_INPUT_FORMAT, 0x00010010);
+ cx_clear(MO_FILTER_EVEN, 0x00002020);
+ cx_clear(MO_FILTER_ODD, 0x00002020);
+ break;
+ }
+
+ /*
+ * if there are audioroutes defined, we have an external
+ * ADC to deal with audio
+ */
+ if (INPUT(input).audioroute) {
+ /*
+ * The wm8775 module has the "2" route hardwired into
+ * the initialization. Some boards may use different
+ * routes for different inputs. HVR-1300 surely does
+ */
+ if (core->sd_wm8775) {
+ call_all(core, audio, s_routing,
+ INPUT(input).audioroute, 0, 0);
+ }
+ /*
+ * cx2388's C-ADC is connected to the tuner only.
+ * When used with S-Video, that ADC is busy dealing with
+ * chroma, so an external must be used for baseband audio
+ */
+ if (INPUT(input).type != CX88_VMUX_TELEVISION &&
+ INPUT(input).type != CX88_VMUX_CABLE) {
+ /* "I2S ADC mode" */
+ core->tvaudio = WW_I2SADC;
+ cx88_set_tvaudio(core);
+ } else {
+ /* Normal mode */
+ cx_write(AUD_I2SCNTL, 0x0);
+ cx_clear(AUD_CTL, EN_I2SIN_ENABLE);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(cx88_video_mux);
+
+/* ------------------------------------------------------------------ */
+
+static int start_video_dma(struct cx8800_dev *dev,
+ struct cx88_dmaqueue *q,
+ struct cx88_buffer *buf)
+{
+ struct cx88_core *core = dev->core;
+
+ /* setup fifo + format */
+ cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21],
+ buf->bpl, buf->risc.dma);
+ cx88_set_scale(core, core->width, core->height, core->field);
+ cx_write(MO_COLOR_CTRL, dev->fmt->cxformat | ColorFormatGamma);
+
+ /* reset counter */
+ cx_write(MO_VIDY_GPCNTRL, GP_COUNT_CONTROL_RESET);
+ q->count = 0;
+
+ /* enable irqs */
+ cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_VIDINT);
+
+ /*
+ * Enables corresponding bits at PCI_INT_STAT:
+ * bits 0 to 4: video, audio, transport stream, VIP, Host
+ * bit 7: timer
+ * bits 8 and 9: DMA complete for: SRC, DST
+ * bits 10 and 11: BERR signal asserted for RISC: RD, WR
+ * bits 12 to 15: BERR signal asserted for: BRDG, SRC, DST, IPB
+ */
+ cx_set(MO_VID_INTMSK, 0x0f0011);
+
+ /* enable capture */
+ cx_set(VID_CAPTURE_CONTROL, 0x06);
+
+ /* start dma */
+ cx_set(MO_DEV_CNTRL2, (1 << 5));
+ cx_set(MO_VID_DMACNTRL, 0x11); /* Planar Y and packed FIFO and RISC enable */
+
+ return 0;
+}
+
+static int __maybe_unused stop_video_dma(struct cx8800_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+
+ /* stop dma */
+ cx_clear(MO_VID_DMACNTRL, 0x11);
+
+ /* disable capture */
+ cx_clear(VID_CAPTURE_CONTROL, 0x06);
+
+ /* disable irqs */
+ cx_clear(MO_PCI_INTMSK, PCI_INT_VIDINT);
+ cx_clear(MO_VID_INTMSK, 0x0f0011);
+ return 0;
+}
+
+static int __maybe_unused restart_video_queue(struct cx8800_dev *dev,
+ struct cx88_dmaqueue *q)
+{
+ struct cx88_buffer *buf;
+
+ if (!list_empty(&q->active)) {
+ buf = list_entry(q->active.next, struct cx88_buffer, list);
+ dprintk(2, "restart_queue [%p/%d]: restart dma\n",
+ buf, buf->vb.vb2_buf.index);
+ start_video_dma(dev, q, buf);
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct cx8800_dev *dev = q->drv_priv;
+ struct cx88_core *core = dev->core;
+
+ *num_planes = 1;
+ sizes[0] = (dev->fmt->depth * core->width * core->height) >> 3;
+ return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+ int ret;
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_core *core = dev->core;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
+
+ buf->bpl = core->width * dev->fmt->depth >> 3;
+
+ if (vb2_plane_size(vb, 0) < core->height * buf->bpl)
+ return -EINVAL;
+ vb2_set_plane_payload(vb, 0, core->height * buf->bpl);
+
+ switch (core->field) {
+ case V4L2_FIELD_TOP:
+ ret = cx88_risc_buffer(dev->pci, &buf->risc,
+ sgt->sgl, 0, UNSET,
+ buf->bpl, 0, core->height);
+ break;
+ case V4L2_FIELD_BOTTOM:
+ ret = cx88_risc_buffer(dev->pci, &buf->risc,
+ sgt->sgl, UNSET, 0,
+ buf->bpl, 0, core->height);
+ break;
+ case V4L2_FIELD_SEQ_TB:
+ ret = cx88_risc_buffer(dev->pci, &buf->risc,
+ sgt->sgl,
+ 0, buf->bpl * (core->height >> 1),
+ buf->bpl, 0,
+ core->height >> 1);
+ break;
+ case V4L2_FIELD_SEQ_BT:
+ ret = cx88_risc_buffer(dev->pci, &buf->risc,
+ sgt->sgl,
+ buf->bpl * (core->height >> 1), 0,
+ buf->bpl, 0,
+ core->height >> 1);
+ break;
+ case V4L2_FIELD_INTERLACED:
+ default:
+ ret = cx88_risc_buffer(dev->pci, &buf->risc,
+ sgt->sgl, 0, buf->bpl,
+ buf->bpl, buf->bpl,
+ core->height >> 1);
+ break;
+ }
+ dprintk(2,
+ "[%p/%d] %s - %dx%d %dbpp 0x%08x - dma=0x%08lx\n",
+ buf, buf->vb.vb2_buf.index, __func__,
+ core->width, core->height, dev->fmt->depth, dev->fmt->fourcc,
+ (unsigned long)buf->risc.dma);
+ return ret;
+}
+
+static void buffer_finish(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct cx88_riscmem *risc = &buf->risc;
+
+ if (risc->cpu)
+ dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu,
+ risc->dma);
+ memset(risc, 0, sizeof(*risc));
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
+ struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
+ struct cx88_buffer *prev;
+ struct cx88_dmaqueue *q = &dev->vidq;
+
+ /* add jump to start */
+ buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8);
+ buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC);
+ buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8);
+
+ if (list_empty(&q->active)) {
+ list_add_tail(&buf->list, &q->active);
+ dprintk(2, "[%p/%d] buffer_queue - first active\n",
+ buf, buf->vb.vb2_buf.index);
+
+ } else {
+ buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1);
+ prev = list_entry(q->active.prev, struct cx88_buffer, list);
+ list_add_tail(&buf->list, &q->active);
+ prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+ dprintk(2, "[%p/%d] buffer_queue - append to active\n",
+ buf, buf->vb.vb2_buf.index);
+ }
+}
+
+static int start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct cx8800_dev *dev = q->drv_priv;
+ struct cx88_dmaqueue *dmaq = &dev->vidq;
+ struct cx88_buffer *buf = list_entry(dmaq->active.next,
+ struct cx88_buffer, list);
+
+ start_video_dma(dev, dmaq, buf);
+ return 0;
+}
+
+static void stop_streaming(struct vb2_queue *q)
+{
+ struct cx8800_dev *dev = q->drv_priv;
+ struct cx88_core *core = dev->core;
+ struct cx88_dmaqueue *dmaq = &dev->vidq;
+ unsigned long flags;
+
+ cx_clear(MO_VID_DMACNTRL, 0x11);
+ cx_clear(VID_CAPTURE_CONTROL, 0x06);
+ spin_lock_irqsave(&dev->slock, flags);
+ while (!list_empty(&dmaq->active)) {
+ struct cx88_buffer *buf = list_entry(dmaq->active.next,
+ struct cx88_buffer, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static const struct vb2_ops cx8800_video_qops = {
+ .queue_setup = queue_setup,
+ .buf_prepare = buffer_prepare,
+ .buf_finish = buffer_finish,
+ .buf_queue = buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+};
+
+/* ------------------------------------------------------------------ */
+
+static int radio_open(struct file *file)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ int ret = v4l2_fh_open(file);
+
+ if (ret)
+ return ret;
+
+ cx_write(MO_GP3_IO, core->board.radio.gpio3);
+ cx_write(MO_GP0_IO, core->board.radio.gpio0);
+ cx_write(MO_GP1_IO, core->board.radio.gpio1);
+ cx_write(MO_GP2_IO, core->board.radio.gpio2);
+ if (core->board.radio.audioroute) {
+ if (core->sd_wm8775) {
+ call_all(core, audio, s_routing,
+ core->board.radio.audioroute, 0, 0);
+ }
+ /* "I2S ADC mode" */
+ core->tvaudio = WW_I2SADC;
+ cx88_set_tvaudio(core);
+ } else {
+ /* FM Mode */
+ core->tvaudio = WW_FM;
+ cx88_set_tvaudio(core);
+ cx88_set_stereo(core, V4L2_TUNER_MODE_STEREO, 1);
+ }
+ call_all(core, tuner, s_radio);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* VIDEO CTRL IOCTLS */
+
+static int cx8800_s_vid_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct cx88_core *core =
+ container_of(ctrl->handler, struct cx88_core, video_hdl);
+ const struct cx88_ctrl *cc = ctrl->priv;
+ u32 value, mask;
+
+ mask = cc->mask;
+ switch (ctrl->id) {
+ case V4L2_CID_SATURATION:
+ /* special v_sat handling */
+
+ value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
+
+ if (core->tvnorm & V4L2_STD_SECAM) {
+ /* For SECAM, both U and V sat should be equal */
+ value = value << 8 | value;
+ } else {
+ /* Keeps U Saturation proportional to V Sat */
+ value = (value * 0x5a) / 0x7f << 8 | value;
+ }
+ mask = 0xffff;
+ break;
+ case V4L2_CID_SHARPNESS:
+ /* 0b000, 0b100, 0b101, 0b110, or 0b111 */
+ value = (ctrl->val < 1 ? 0 : ((ctrl->val + 3) << 7));
+ /* needs to be set for both fields */
+ cx_andor(MO_FILTER_EVEN, mask, value);
+ break;
+ case V4L2_CID_CHROMA_AGC:
+ value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
+ break;
+ default:
+ value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
+ break;
+ }
+ dprintk(1,
+ "set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n",
+ ctrl->id, ctrl->name, ctrl->val, cc->reg, value,
+ mask, cc->sreg ? " [shadowed]" : "");
+ if (cc->sreg)
+ cx_sandor(cc->sreg, cc->reg, mask, value);
+ else
+ cx_andor(cc->reg, mask, value);
+ return 0;
+}
+
+static int cx8800_s_aud_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct cx88_core *core =
+ container_of(ctrl->handler, struct cx88_core, audio_hdl);
+ const struct cx88_ctrl *cc = ctrl->priv;
+ u32 value, mask;
+
+ /* Pass changes onto any WM8775 */
+ if (core->sd_wm8775) {
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ wm8775_s_ctrl(core, ctrl->id, ctrl->val);
+ break;
+ case V4L2_CID_AUDIO_VOLUME:
+ wm8775_s_ctrl(core, ctrl->id, (ctrl->val) ?
+ (0x90 + ctrl->val) << 8 : 0);
+ break;
+ case V4L2_CID_AUDIO_BALANCE:
+ wm8775_s_ctrl(core, ctrl->id, ctrl->val << 9);
+ break;
+ default:
+ break;
+ }
+ }
+
+ mask = cc->mask;
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_BALANCE:
+ value = (ctrl->val < 0x40) ?
+ (0x7f - ctrl->val) : (ctrl->val - 0x40);
+ break;
+ case V4L2_CID_AUDIO_VOLUME:
+ value = 0x3f - (ctrl->val & 0x3f);
+ break;
+ default:
+ value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
+ break;
+ }
+ dprintk(1,
+ "set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n",
+ ctrl->id, ctrl->name, ctrl->val, cc->reg, value,
+ mask, cc->sreg ? " [shadowed]" : "");
+ if (cc->sreg)
+ cx_sandor(cc->sreg, cc->reg, mask, value);
+ else
+ cx_andor(cc->reg, mask, value);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* VIDEO IOCTLS */
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ f->fmt.pix.width = core->width;
+ f->fmt.pix.height = core->height;
+ f->fmt.pix.field = core->field;
+ f->fmt.pix.pixelformat = dev->fmt->fourcc;
+ f->fmt.pix.bytesperline =
+ (f->fmt.pix.width * dev->fmt->depth) >> 3;
+ f->fmt.pix.sizeimage =
+ f->fmt.pix.height * f->fmt.pix.bytesperline;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ const struct cx8800_fmt *fmt;
+ enum v4l2_field field;
+ unsigned int maxw, maxh;
+
+ fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+ if (!fmt)
+ return -EINVAL;
+
+ maxw = norm_maxw(core->tvnorm);
+ maxh = norm_maxh(core->tvnorm);
+
+ field = f->fmt.pix.field;
+
+ switch (field) {
+ case V4L2_FIELD_TOP:
+ case V4L2_FIELD_BOTTOM:
+ case V4L2_FIELD_INTERLACED:
+ case V4L2_FIELD_SEQ_BT:
+ case V4L2_FIELD_SEQ_TB:
+ break;
+ default:
+ field = (f->fmt.pix.height > maxh / 2)
+ ? V4L2_FIELD_INTERLACED
+ : V4L2_FIELD_BOTTOM;
+ break;
+ }
+ if (V4L2_FIELD_HAS_T_OR_B(field))
+ maxh /= 2;
+
+ v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
+ &f->fmt.pix.height, 32, maxh, 0, 0);
+ f->fmt.pix.field = field;
+ f->fmt.pix.bytesperline =
+ (f->fmt.pix.width * fmt->depth) >> 3;
+ f->fmt.pix.sizeimage =
+ f->fmt.pix.height * f->fmt.pix.bytesperline;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ int err = vidioc_try_fmt_vid_cap(file, priv, f);
+
+ if (err != 0)
+ return err;
+ if (vb2_is_busy(&dev->vb2_vidq) || vb2_is_busy(&dev->vb2_vbiq))
+ return -EBUSY;
+ if (core->dvbdev && vb2_is_busy(&core->dvbdev->vb2_mpegq))
+ return -EBUSY;
+ dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+ core->width = f->fmt.pix.width;
+ core->height = f->fmt.pix.height;
+ core->field = f->fmt.pix.field;
+ return 0;
+}
+
+int cx88_querycap(struct file *file, struct cx88_core *core,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->card, core->board.name, sizeof(cap->card));
+ cap->capabilities = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
+ V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE |
+ V4L2_CAP_DEVICE_CAPS;
+ if (core->board.tuner_type != UNSET)
+ cap->capabilities |= V4L2_CAP_TUNER;
+ if (core->board.radio.type == CX88_RADIO)
+ cap->capabilities |= V4L2_CAP_RADIO;
+ return 0;
+}
+EXPORT_SYMBOL(cx88_querycap);
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ strscpy(cap->driver, "cx8800", sizeof(cap->driver));
+ return cx88_querycap(file, core, cap);
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (unlikely(f->index >= ARRAY_SIZE(formats)))
+ return -EINVAL;
+
+ f->pixelformat = formats[f->index].fourcc;
+
+ return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ *tvnorm = core->tvnorm;
+ return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id tvnorms)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ return cx88_set_tvnorm(core, tvnorms);
+}
+
+/* only one input in this sample driver */
+int cx88_enum_input(struct cx88_core *core, struct v4l2_input *i)
+{
+ static const char * const iname[] = {
+ [CX88_VMUX_COMPOSITE1] = "Composite1",
+ [CX88_VMUX_COMPOSITE2] = "Composite2",
+ [CX88_VMUX_COMPOSITE3] = "Composite3",
+ [CX88_VMUX_COMPOSITE4] = "Composite4",
+ [CX88_VMUX_SVIDEO] = "S-Video",
+ [CX88_VMUX_TELEVISION] = "Television",
+ [CX88_VMUX_CABLE] = "Cable TV",
+ [CX88_VMUX_DVB] = "DVB",
+ [CX88_VMUX_DEBUG] = "for debug only",
+ };
+ unsigned int n = i->index;
+
+ if (n >= 4)
+ return -EINVAL;
+ if (!INPUT(n).type)
+ return -EINVAL;
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ strscpy(i->name, iname[INPUT(n).type], sizeof(i->name));
+ if ((INPUT(n).type == CX88_VMUX_TELEVISION) ||
+ (INPUT(n).type == CX88_VMUX_CABLE))
+ i->type = V4L2_INPUT_TYPE_TUNER;
+
+ i->std = CX88_NORMS;
+ return 0;
+}
+EXPORT_SYMBOL(cx88_enum_input);
+
+static int vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ return cx88_enum_input(core, i);
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ *i = core->input;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (i >= 4)
+ return -EINVAL;
+ if (!INPUT(i).type)
+ return -EINVAL;
+
+ cx88_newstation(core);
+ cx88_video_mux(core, i);
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *t)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+ u32 reg;
+
+ if (unlikely(core->board.tuner_type == UNSET))
+ return -EINVAL;
+ if (t->index != 0)
+ return -EINVAL;
+
+ strscpy(t->name, "Television", sizeof(t->name));
+ t->capability = V4L2_TUNER_CAP_NORM;
+ t->rangehigh = 0xffffffffUL;
+ call_all(core, tuner, g_tuner, t);
+
+ cx88_get_stereo(core, t);
+ reg = cx_read(MO_DEVICE_STATUS);
+ t->signal = (reg & (1 << 5)) ? 0xffff : 0x0000;
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *t)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (core->board.tuner_type == UNSET)
+ return -EINVAL;
+ if (t->index != 0)
+ return -EINVAL;
+
+ cx88_set_stereo(core, t->audmode, 1);
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (unlikely(core->board.tuner_type == UNSET))
+ return -EINVAL;
+ if (f->tuner)
+ return -EINVAL;
+
+ f->frequency = core->freq;
+
+ call_all(core, tuner, g_frequency, f);
+
+ return 0;
+}
+
+int cx88_set_freq(struct cx88_core *core,
+ const struct v4l2_frequency *f)
+{
+ struct v4l2_frequency new_freq = *f;
+
+ if (unlikely(core->board.tuner_type == UNSET))
+ return -EINVAL;
+ if (unlikely(f->tuner != 0))
+ return -EINVAL;
+
+ cx88_newstation(core);
+ call_all(core, tuner, s_frequency, f);
+ call_all(core, tuner, g_frequency, &new_freq);
+ core->freq = new_freq.frequency;
+
+ /* When changing channels it is required to reset TVAUDIO */
+ usleep_range(10000, 20000);
+ cx88_set_tvaudio(core);
+
+ return 0;
+}
+EXPORT_SYMBOL(cx88_set_freq);
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *f)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ return cx88_set_freq(core, f);
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ /* cx2388x has a 24-bit register space */
+ reg->val = cx_read(reg->reg & 0xfffffc);
+ reg->size = 4;
+ return 0;
+}
+
+static int vidioc_s_register(struct file *file, void *fh,
+ const struct v4l2_dbg_register *reg)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ cx_write(reg->reg & 0xfffffc, reg->val);
+ return 0;
+}
+#endif
+
+/* ----------------------------------------------------------- */
+/* RADIO ESPECIFIC IOCTLS */
+/* ----------------------------------------------------------- */
+
+static int radio_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *t)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (unlikely(t->index > 0))
+ return -EINVAL;
+
+ strscpy(t->name, "Radio", sizeof(t->name));
+
+ call_all(core, tuner, g_tuner, t);
+ return 0;
+}
+
+static int radio_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *t)
+{
+ struct cx8800_dev *dev = video_drvdata(file);
+ struct cx88_core *core = dev->core;
+
+ if (t->index != 0)
+ return -EINVAL;
+
+ call_all(core, tuner, s_tuner, t);
+ return 0;
+}
+
+/* ----------------------------------------------------------- */
+
+static const char *cx88_vid_irqs[32] = {
+ "y_risci1", "u_risci1", "v_risci1", "vbi_risc1",
+ "y_risci2", "u_risci2", "v_risci2", "vbi_risc2",
+ "y_oflow", "u_oflow", "v_oflow", "vbi_oflow",
+ "y_sync", "u_sync", "v_sync", "vbi_sync",
+ "opc_err", "par_err", "rip_err", "pci_abort",
+};
+
+static void cx8800_vid_irq(struct cx8800_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+ u32 status, mask, count;
+
+ status = cx_read(MO_VID_INTSTAT);
+ mask = cx_read(MO_VID_INTMSK);
+ if (0 == (status & mask))
+ return;
+ cx_write(MO_VID_INTSTAT, status);
+ if (irq_debug || (status & mask & ~0xff))
+ cx88_print_irqbits("irq vid",
+ cx88_vid_irqs, ARRAY_SIZE(cx88_vid_irqs),
+ status, mask);
+
+ /* risc op code error */
+ if (status & (1 << 16)) {
+ pr_warn("video risc op code error\n");
+ cx_clear(MO_VID_DMACNTRL, 0x11);
+ cx_clear(VID_CAPTURE_CONTROL, 0x06);
+ cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]);
+ }
+
+ /* risc1 y */
+ if (status & 0x01) {
+ spin_lock(&dev->slock);
+ count = cx_read(MO_VIDY_GPCNT);
+ cx88_wakeup(core, &dev->vidq, count);
+ spin_unlock(&dev->slock);
+ }
+
+ /* risc1 vbi */
+ if (status & 0x08) {
+ spin_lock(&dev->slock);
+ count = cx_read(MO_VBI_GPCNT);
+ cx88_wakeup(core, &dev->vbiq, count);
+ spin_unlock(&dev->slock);
+ }
+}
+
+static irqreturn_t cx8800_irq(int irq, void *dev_id)
+{
+ struct cx8800_dev *dev = dev_id;
+ struct cx88_core *core = dev->core;
+ u32 status;
+ int loop, handled = 0;
+
+ for (loop = 0; loop < 10; loop++) {
+ status = cx_read(MO_PCI_INTSTAT) &
+ (core->pci_irqmask | PCI_INT_VIDINT);
+ if (status == 0)
+ goto out;
+ cx_write(MO_PCI_INTSTAT, status);
+ handled = 1;
+
+ if (status & core->pci_irqmask)
+ cx88_core_irq(core, status);
+ if (status & PCI_INT_VIDINT)
+ cx8800_vid_irq(dev);
+ }
+ if (loop == 10) {
+ pr_warn("irq loop -- clearing mask\n");
+ cx_write(MO_PCI_INTMSK, 0);
+ }
+
+ out:
+ return IRQ_RETVAL(handled);
+}
+
+/* ----------------------------------------------------------- */
+/* exported stuff */
+
+static const struct v4l2_file_operations video_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_g_std = vidioc_g_std,
+ .vidioc_s_std = vidioc_s_std,
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .vidioc_g_register = vidioc_g_register,
+ .vidioc_s_register = vidioc_s_register,
+#endif
+};
+
+static const struct video_device cx8800_video_template = {
+ .name = "cx8800-video",
+ .fops = &video_fops,
+ .ioctl_ops = &video_ioctl_ops,
+ .tvnorms = CX88_NORMS,
+};
+
+static const struct v4l2_ioctl_ops vbi_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_fmt_vbi_cap = cx8800_vbi_fmt,
+ .vidioc_try_fmt_vbi_cap = cx8800_vbi_fmt,
+ .vidioc_s_fmt_vbi_cap = cx8800_vbi_fmt,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_g_std = vidioc_g_std,
+ .vidioc_s_std = vidioc_s_std,
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .vidioc_g_register = vidioc_g_register,
+ .vidioc_s_register = vidioc_s_register,
+#endif
+};
+
+static const struct video_device cx8800_vbi_template = {
+ .name = "cx8800-vbi",
+ .fops = &video_fops,
+ .ioctl_ops = &vbi_ioctl_ops,
+ .tvnorms = CX88_NORMS,
+};
+
+static const struct v4l2_file_operations radio_fops = {
+ .owner = THIS_MODULE,
+ .open = radio_open,
+ .poll = v4l2_ctrl_poll,
+ .release = v4l2_fh_release,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops radio_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = radio_g_tuner,
+ .vidioc_s_tuner = radio_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .vidioc_g_register = vidioc_g_register,
+ .vidioc_s_register = vidioc_s_register,
+#endif
+};
+
+static const struct video_device cx8800_radio_template = {
+ .name = "cx8800-radio",
+ .fops = &radio_fops,
+ .ioctl_ops = &radio_ioctl_ops,
+};
+
+static const struct v4l2_ctrl_ops cx8800_ctrl_vid_ops = {
+ .s_ctrl = cx8800_s_vid_ctrl,
+};
+
+static const struct v4l2_ctrl_ops cx8800_ctrl_aud_ops = {
+ .s_ctrl = cx8800_s_aud_ctrl,
+};
+
+/* ----------------------------------------------------------- */
+
+static void cx8800_unregister_video(struct cx8800_dev *dev)
+{
+ video_unregister_device(&dev->radio_dev);
+ video_unregister_device(&dev->vbi_dev);
+ video_unregister_device(&dev->video_dev);
+}
+
+static int cx8800_initdev(struct pci_dev *pci_dev,
+ const struct pci_device_id *pci_id)
+{
+ struct cx8800_dev *dev;
+ struct cx88_core *core;
+ struct vb2_queue *q;
+ int err;
+ int i;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* pci init */
+ dev->pci = pci_dev;
+ if (pci_enable_device(pci_dev)) {
+ err = -EIO;
+ goto fail_free;
+ }
+ core = cx88_core_get(dev->pci);
+ if (!core) {
+ err = -EINVAL;
+ goto fail_disable;
+ }
+ dev->core = core;
+
+ /* print pci info */
+ dev->pci_rev = pci_dev->revision;
+ pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat);
+ pr_info("found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n",
+ pci_name(pci_dev), dev->pci_rev, pci_dev->irq,
+ dev->pci_lat,
+ (unsigned long long)pci_resource_start(pci_dev, 0));
+
+ pci_set_master(pci_dev);
+ err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32));
+ if (err) {
+ pr_err("Oops: no 32bit PCI DMA ???\n");
+ goto fail_core;
+ }
+
+ /* initialize driver struct */
+ spin_lock_init(&dev->slock);
+
+ /* init video dma queues */
+ INIT_LIST_HEAD(&dev->vidq.active);
+
+ /* init vbi dma queues */
+ INIT_LIST_HEAD(&dev->vbiq.active);
+
+ /* get irq */
+ err = request_irq(pci_dev->irq, cx8800_irq,
+ IRQF_SHARED, core->name, dev);
+ if (err < 0) {
+ pr_err("can't get IRQ %d\n", pci_dev->irq);
+ goto fail_core;
+ }
+ cx_set(MO_PCI_INTMSK, core->pci_irqmask);
+
+ for (i = 0; i < CX8800_AUD_CTLS; i++) {
+ const struct cx88_ctrl *cc = &cx8800_aud_ctls[i];
+ struct v4l2_ctrl *vc;
+
+ vc = v4l2_ctrl_new_std(&core->audio_hdl, &cx8800_ctrl_aud_ops,
+ cc->id, cc->minimum, cc->maximum,
+ cc->step, cc->default_value);
+ if (!vc) {
+ err = core->audio_hdl.error;
+ goto fail_irq;
+ }
+ vc->priv = (void *)cc;
+ }
+
+ for (i = 0; i < CX8800_VID_CTLS; i++) {
+ const struct cx88_ctrl *cc = &cx8800_vid_ctls[i];
+ struct v4l2_ctrl *vc;
+
+ vc = v4l2_ctrl_new_std(&core->video_hdl, &cx8800_ctrl_vid_ops,
+ cc->id, cc->minimum, cc->maximum,
+ cc->step, cc->default_value);
+ if (!vc) {
+ err = core->video_hdl.error;
+ goto fail_irq;
+ }
+ vc->priv = (void *)cc;
+ if (vc->id == V4L2_CID_CHROMA_AGC)
+ core->chroma_agc = vc;
+ }
+ v4l2_ctrl_add_handler(&core->video_hdl, &core->audio_hdl, NULL, false);
+
+ /* load and configure helper modules */
+
+ if (core->board.audio_chip == CX88_AUDIO_WM8775) {
+ struct i2c_board_info wm8775_info = {
+ .type = "wm8775",
+ .addr = 0x36 >> 1,
+ .platform_data = &core->wm8775_data,
+ };
+ struct v4l2_subdev *sd;
+
+ if (core->boardnr == CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1)
+ core->wm8775_data.is_nova_s = true;
+ else
+ core->wm8775_data.is_nova_s = false;
+
+ sd = v4l2_i2c_new_subdev_board(&core->v4l2_dev, &core->i2c_adap,
+ &wm8775_info, NULL);
+ if (sd) {
+ core->sd_wm8775 = sd;
+ sd->grp_id = WM8775_GID;
+ }
+ }
+
+ if (core->board.audio_chip == CX88_AUDIO_TVAUDIO) {
+ /*
+ * This probes for a tda9874 as is used on some
+ * Pixelview Ultra boards.
+ */
+ v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap,
+ "tvaudio", 0, I2C_ADDRS(0xb0 >> 1));
+ }
+
+ switch (core->boardnr) {
+ case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD:
+ case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: {
+ static const struct i2c_board_info rtc_info = {
+ I2C_BOARD_INFO("isl1208", 0x6f)
+ };
+
+ request_module("rtc-isl1208");
+ core->i2c_rtc = i2c_new_client_device(&core->i2c_adap, &rtc_info);
+ }
+ fallthrough;
+ case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO:
+ case CX88_BOARD_NOTONLYTV_LV3H:
+ request_module("ir-kbd-i2c");
+ }
+
+ /* Sets device info at pci_dev */
+ pci_set_drvdata(pci_dev, dev);
+
+ dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24);
+
+ /* Maintain a reference so cx88-blackbird can query the 8800 device. */
+ core->v4ldev = dev;
+
+ /* initial device configuration */
+ mutex_lock(&core->lock);
+ cx88_set_tvnorm(core, V4L2_STD_NTSC_M);
+ v4l2_ctrl_handler_setup(&core->video_hdl);
+ v4l2_ctrl_handler_setup(&core->audio_hdl);
+ cx88_video_mux(core, 0);
+
+ q = &dev->vb2_vidq;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
+ q->gfp_flags = GFP_DMA32;
+ q->min_buffers_needed = 2;
+ q->drv_priv = dev;
+ q->buf_struct_size = sizeof(struct cx88_buffer);
+ q->ops = &cx8800_video_qops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &core->lock;
+ q->dev = &dev->pci->dev;
+
+ err = vb2_queue_init(q);
+ if (err < 0)
+ goto fail_unreg;
+
+ q = &dev->vb2_vbiq;
+ q->type = V4L2_BUF_TYPE_VBI_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
+ q->gfp_flags = GFP_DMA32;
+ q->min_buffers_needed = 2;
+ q->drv_priv = dev;
+ q->buf_struct_size = sizeof(struct cx88_buffer);
+ q->ops = &cx8800_vbi_qops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &core->lock;
+ q->dev = &dev->pci->dev;
+
+ err = vb2_queue_init(q);
+ if (err < 0)
+ goto fail_unreg;
+
+ /* register v4l devices */
+ cx88_vdev_init(core, dev->pci, &dev->video_dev,
+ &cx8800_video_template, "video");
+ video_set_drvdata(&dev->video_dev, dev);
+ dev->video_dev.ctrl_handler = &core->video_hdl;
+ dev->video_dev.queue = &dev->vb2_vidq;
+ dev->video_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
+ V4L2_CAP_VIDEO_CAPTURE;
+ if (core->board.tuner_type != UNSET)
+ dev->video_dev.device_caps |= V4L2_CAP_TUNER;
+ err = video_register_device(&dev->video_dev, VFL_TYPE_VIDEO,
+ video_nr[core->nr]);
+ if (err < 0) {
+ pr_err("can't register video device\n");
+ goto fail_unreg;
+ }
+ pr_info("registered device %s [v4l2]\n",
+ video_device_node_name(&dev->video_dev));
+
+ cx88_vdev_init(core, dev->pci, &dev->vbi_dev,
+ &cx8800_vbi_template, "vbi");
+ video_set_drvdata(&dev->vbi_dev, dev);
+ dev->vbi_dev.queue = &dev->vb2_vbiq;
+ dev->vbi_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
+ V4L2_CAP_VBI_CAPTURE;
+ if (core->board.tuner_type != UNSET)
+ dev->vbi_dev.device_caps |= V4L2_CAP_TUNER;
+ err = video_register_device(&dev->vbi_dev, VFL_TYPE_VBI,
+ vbi_nr[core->nr]);
+ if (err < 0) {
+ pr_err("can't register vbi device\n");
+ goto fail_unreg;
+ }
+ pr_info("registered device %s\n",
+ video_device_node_name(&dev->vbi_dev));
+
+ if (core->board.radio.type == CX88_RADIO) {
+ cx88_vdev_init(core, dev->pci, &dev->radio_dev,
+ &cx8800_radio_template, "radio");
+ video_set_drvdata(&dev->radio_dev, dev);
+ dev->radio_dev.ctrl_handler = &core->audio_hdl;
+ dev->radio_dev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER;
+ err = video_register_device(&dev->radio_dev, VFL_TYPE_RADIO,
+ radio_nr[core->nr]);
+ if (err < 0) {
+ pr_err("can't register radio device\n");
+ goto fail_unreg;
+ }
+ pr_info("registered device %s\n",
+ video_device_node_name(&dev->radio_dev));
+ }
+
+ /* start tvaudio thread */
+ if (core->board.tuner_type != UNSET) {
+ core->kthread = kthread_run(cx88_audio_thread,
+ core, "cx88 tvaudio");
+ if (IS_ERR(core->kthread)) {
+ err = PTR_ERR(core->kthread);
+ pr_err("failed to create cx88 audio thread, err=%d\n",
+ err);
+ }
+ }
+ mutex_unlock(&core->lock);
+
+ return 0;
+
+fail_unreg:
+ cx8800_unregister_video(dev);
+ mutex_unlock(&core->lock);
+fail_irq:
+ free_irq(pci_dev->irq, dev);
+fail_core:
+ core->v4ldev = NULL;
+ cx88_core_put(core, dev->pci);
+fail_disable:
+ pci_disable_device(pci_dev);
+fail_free:
+ kfree(dev);
+ return err;
+}
+
+static void cx8800_finidev(struct pci_dev *pci_dev)
+{
+ struct cx8800_dev *dev = pci_get_drvdata(pci_dev);
+ struct cx88_core *core = dev->core;
+
+ /* stop thread */
+ if (core->kthread) {
+ kthread_stop(core->kthread);
+ core->kthread = NULL;
+ }
+
+ if (core->ir)
+ cx88_ir_stop(core);
+
+ cx88_shutdown(core); /* FIXME */
+
+ /* unregister stuff */
+
+ free_irq(pci_dev->irq, dev);
+ cx8800_unregister_video(dev);
+ pci_disable_device(pci_dev);
+
+ core->v4ldev = NULL;
+
+ /* free memory */
+ cx88_core_put(core, dev->pci);
+ kfree(dev);
+}
+
+static int __maybe_unused cx8800_suspend(struct device *dev_d)
+{
+ struct cx8800_dev *dev = dev_get_drvdata(dev_d);
+ struct cx88_core *core = dev->core;
+ unsigned long flags;
+
+ /* stop video+vbi capture */
+ spin_lock_irqsave(&dev->slock, flags);
+ if (!list_empty(&dev->vidq.active)) {
+ pr_info("suspend video\n");
+ stop_video_dma(dev);
+ }
+ if (!list_empty(&dev->vbiq.active)) {
+ pr_info("suspend vbi\n");
+ cx8800_stop_vbi_dma(dev);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+
+ if (core->ir)
+ cx88_ir_stop(core);
+ /* FIXME -- shutdown device */
+ cx88_shutdown(core);
+
+ dev->state.disabled = 1;
+ return 0;
+}
+
+static int __maybe_unused cx8800_resume(struct device *dev_d)
+{
+ struct cx8800_dev *dev = dev_get_drvdata(dev_d);
+ struct cx88_core *core = dev->core;
+ unsigned long flags;
+
+ dev->state.disabled = 0;
+
+ /* FIXME: re-initialize hardware */
+ cx88_reset(core);
+ if (core->ir)
+ cx88_ir_start(core);
+
+ cx_set(MO_PCI_INTMSK, core->pci_irqmask);
+
+ /* restart video+vbi capture */
+ spin_lock_irqsave(&dev->slock, flags);
+ if (!list_empty(&dev->vidq.active)) {
+ pr_info("resume video\n");
+ restart_video_queue(dev, &dev->vidq);
+ }
+ if (!list_empty(&dev->vbiq.active)) {
+ pr_info("resume vbi\n");
+ cx8800_restart_vbi_queue(dev, &dev->vbiq);
+ }
+ spin_unlock_irqrestore(&dev->slock, flags);
+
+ return 0;
+}
+
+/* ----------------------------------------------------------- */
+
+static const struct pci_device_id cx8800_pci_tbl[] = {
+ {
+ .vendor = 0x14f1,
+ .device = 0x8800,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ }, {
+ /* --- end of list --- */
+ }
+};
+MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl);
+
+static SIMPLE_DEV_PM_OPS(cx8800_pm_ops, cx8800_suspend, cx8800_resume);
+
+static struct pci_driver cx8800_pci_driver = {
+ .name = "cx8800",
+ .id_table = cx8800_pci_tbl,
+ .probe = cx8800_initdev,
+ .remove = cx8800_finidev,
+ .driver.pm = &cx8800_pm_ops,
+};
+
+module_pci_driver(cx8800_pci_driver);
diff --git a/drivers/media/pci/cx88/cx88-vp3054-i2c.c b/drivers/media/pci/cx88/cx88-vp3054-i2c.c
new file mode 100644
index 000000000..ac7f76dc5
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-vp3054-i2c.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * cx88-vp3054-i2c.c -- support for the secondary I2C bus of the
+ * DNTV Live! DVB-T Pro (VP-3054), wired as:
+ * GPIO[0] -> SCL, GPIO[1] -> SDA
+ *
+ * (c) 2005 Chris Pascoe <c.pascoe@itee.uq.edu.au>
+ */
+
+#include "cx88.h"
+#include "cx88-vp3054-i2c.h"
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/io.h>
+
+MODULE_DESCRIPTION("driver for cx2388x VP3054 design");
+MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
+MODULE_LICENSE("GPL");
+
+/* ----------------------------------------------------------------------- */
+
+static void vp3054_bit_setscl(void *data, int state)
+{
+ struct cx8802_dev *dev = data;
+ struct cx88_core *core = dev->core;
+ struct vp3054_i2c_state *vp3054_i2c = dev->vp3054;
+
+ if (state) {
+ vp3054_i2c->state |= 0x0001; /* SCL high */
+ vp3054_i2c->state &= ~0x0100; /* external pullup */
+ } else {
+ vp3054_i2c->state &= ~0x0001; /* SCL low */
+ vp3054_i2c->state |= 0x0100; /* drive pin */
+ }
+ cx_write(MO_GP0_IO, 0x010000 | vp3054_i2c->state);
+ cx_read(MO_GP0_IO);
+}
+
+static void vp3054_bit_setsda(void *data, int state)
+{
+ struct cx8802_dev *dev = data;
+ struct cx88_core *core = dev->core;
+ struct vp3054_i2c_state *vp3054_i2c = dev->vp3054;
+
+ if (state) {
+ vp3054_i2c->state |= 0x0002; /* SDA high */
+ vp3054_i2c->state &= ~0x0200; /* tristate pin */
+ } else {
+ vp3054_i2c->state &= ~0x0002; /* SDA low */
+ vp3054_i2c->state |= 0x0200; /* drive pin */
+ }
+ cx_write(MO_GP0_IO, 0x020000 | vp3054_i2c->state);
+ cx_read(MO_GP0_IO);
+}
+
+static int vp3054_bit_getscl(void *data)
+{
+ struct cx8802_dev *dev = data;
+ struct cx88_core *core = dev->core;
+ u32 state;
+
+ state = cx_read(MO_GP0_IO);
+ return (state & 0x01) ? 1 : 0;
+}
+
+static int vp3054_bit_getsda(void *data)
+{
+ struct cx8802_dev *dev = data;
+ struct cx88_core *core = dev->core;
+ u32 state;
+
+ state = cx_read(MO_GP0_IO);
+ return (state & 0x02) ? 1 : 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static const struct i2c_algo_bit_data vp3054_i2c_algo_template = {
+ .setsda = vp3054_bit_setsda,
+ .setscl = vp3054_bit_setscl,
+ .getsda = vp3054_bit_getsda,
+ .getscl = vp3054_bit_getscl,
+ .udelay = 16,
+ .timeout = 200,
+};
+
+/* ----------------------------------------------------------------------- */
+
+int vp3054_i2c_probe(struct cx8802_dev *dev)
+{
+ struct cx88_core *core = dev->core;
+ struct vp3054_i2c_state *vp3054_i2c;
+ int rc;
+
+ if (core->boardnr != CX88_BOARD_DNTV_LIVE_DVB_T_PRO)
+ return 0;
+
+ vp3054_i2c = kzalloc(sizeof(*vp3054_i2c), GFP_KERNEL);
+ if (!vp3054_i2c)
+ return -ENOMEM;
+ dev->vp3054 = vp3054_i2c;
+
+ vp3054_i2c->algo = vp3054_i2c_algo_template;
+
+ vp3054_i2c->adap.dev.parent = &dev->pci->dev;
+ strscpy(vp3054_i2c->adap.name, core->name,
+ sizeof(vp3054_i2c->adap.name));
+ vp3054_i2c->adap.owner = THIS_MODULE;
+ vp3054_i2c->algo.data = dev;
+ i2c_set_adapdata(&vp3054_i2c->adap, dev);
+ vp3054_i2c->adap.algo_data = &vp3054_i2c->algo;
+
+ vp3054_bit_setscl(dev, 1);
+ vp3054_bit_setsda(dev, 1);
+
+ rc = i2c_bit_add_bus(&vp3054_i2c->adap);
+ if (rc != 0) {
+ pr_err("vp3054_i2c register FAILED\n");
+
+ kfree(dev->vp3054);
+ dev->vp3054 = NULL;
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL(vp3054_i2c_probe);
+
+void vp3054_i2c_remove(struct cx8802_dev *dev)
+{
+ struct vp3054_i2c_state *vp3054_i2c = dev->vp3054;
+
+ if (!vp3054_i2c ||
+ dev->core->boardnr != CX88_BOARD_DNTV_LIVE_DVB_T_PRO)
+ return;
+
+ i2c_del_adapter(&vp3054_i2c->adap);
+ kfree(vp3054_i2c);
+}
+EXPORT_SYMBOL(vp3054_i2c_remove);
diff --git a/drivers/media/pci/cx88/cx88-vp3054-i2c.h b/drivers/media/pci/cx88/cx88-vp3054-i2c.h
new file mode 100644
index 000000000..ead3d0d63
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88-vp3054-i2c.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * cx88-vp3054-i2c.h -- support for the secondary I2C bus of the
+ * DNTV Live! DVB-T Pro (VP-3054), wired as:
+ * GPIO[0] -> SCL, GPIO[1] -> SDA
+ *
+ * (c) 2005 Chris Pascoe <c.pascoe@itee.uq.edu.au>
+ */
+
+/* ----------------------------------------------------------------------- */
+struct vp3054_i2c_state {
+ struct i2c_adapter adap;
+ struct i2c_algo_bit_data algo;
+ u32 state;
+};
+
+/* ----------------------------------------------------------------------- */
+#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054)
+int vp3054_i2c_probe(struct cx8802_dev *dev);
+void vp3054_i2c_remove(struct cx8802_dev *dev);
+#else
+static inline int vp3054_i2c_probe(struct cx8802_dev *dev)
+{ return 0; }
+static inline void vp3054_i2c_remove(struct cx8802_dev *dev)
+{ }
+#endif
diff --git a/drivers/media/pci/cx88/cx88.h b/drivers/media/pci/cx88/cx88.h
new file mode 100644
index 000000000..2ff3226a5
--- /dev/null
+++ b/drivers/media/pci/cx88/cx88.h
@@ -0,0 +1,731 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * v4l2 device driver for cx2388x based TV cards
+ *
+ * (c) 2003,04 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs]
+ */
+
+#ifndef CX88_H
+#define CX88_H
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/pci.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/videodev2.h>
+#include <linux/kdev_t.h>
+#include <linux/refcount.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-fh.h>
+#include <media/tuner.h>
+#include <media/tveeprom.h>
+#include <media/videobuf2-dma-sg.h>
+#include <media/drv-intf/cx2341x.h>
+#include <media/videobuf2-dvb.h>
+#include <media/i2c/ir-kbd-i2c.h>
+#include <media/i2c/wm8775.h>
+
+#include "cx88-reg.h"
+#include "xc2028.h"
+
+#include <linux/mutex.h>
+
+#define CX88_VERSION "1.0.0"
+
+#define UNSET (-1U)
+
+#define CX88_MAXBOARDS 8
+
+/* Max number of inputs by card */
+#define MAX_CX88_INPUT 8
+
+/* ----------------------------------------------------------- */
+/* defines and enums */
+
+/* Currently unsupported by the driver: PAL/H, NTSC/Kr, SECAM/LC */
+#define CX88_NORMS (V4L2_STD_ALL \
+ & ~V4L2_STD_PAL_H \
+ & ~V4L2_STD_NTSC_M_KR \
+ & ~V4L2_STD_SECAM_LC)
+
+#define FORMAT_FLAGS_PACKED 0x01
+#define FORMAT_FLAGS_PLANAR 0x02
+
+#define VBI_LINE_PAL_COUNT 18
+#define VBI_LINE_NTSC_COUNT 12
+#define VBI_LINE_LENGTH 2048
+
+#define AUD_RDS_LINES 4
+
+/* need "shadow" registers for some write-only ones ... */
+#define SHADOW_AUD_VOL_CTL 1
+#define SHADOW_AUD_BAL_CTL 2
+#define SHADOW_MAX 3
+
+/* FM Radio deemphasis type */
+enum cx88_deemph_type {
+ FM_NO_DEEMPH = 0,
+ FM_DEEMPH_50,
+ FM_DEEMPH_75
+};
+
+enum cx88_board_type {
+ CX88_BOARD_NONE = 0,
+ CX88_MPEG_DVB,
+ CX88_MPEG_BLACKBIRD
+};
+
+enum cx8802_board_access {
+ CX8802_DRVCTL_SHARED = 1,
+ CX8802_DRVCTL_EXCLUSIVE = 2,
+};
+
+/* ----------------------------------------------------------- */
+/* tv norms */
+
+static inline unsigned int norm_maxw(v4l2_std_id norm)
+{
+ return 720;
+}
+
+static inline unsigned int norm_maxh(v4l2_std_id norm)
+{
+ return (norm & V4L2_STD_525_60) ? 480 : 576;
+}
+
+/* ----------------------------------------------------------- */
+/* static data */
+
+struct cx8800_fmt {
+ u32 fourcc; /* v4l2 format id */
+ int depth;
+ int flags;
+ u32 cxformat;
+};
+
+/* ----------------------------------------------------------- */
+/* SRAM memory management data (see cx88-core.c) */
+
+#define SRAM_CH21 0 /* video */
+#define SRAM_CH22 1
+#define SRAM_CH23 2
+#define SRAM_CH24 3 /* vbi */
+#define SRAM_CH25 4 /* audio */
+#define SRAM_CH26 5
+#define SRAM_CH28 6 /* mpeg */
+#define SRAM_CH27 7 /* audio rds */
+/* more */
+
+struct sram_channel {
+ const char *name;
+ u32 cmds_start;
+ u32 ctrl_start;
+ u32 cdt;
+ u32 fifo_start;
+ u32 fifo_size;
+ u32 ptr1_reg;
+ u32 ptr2_reg;
+ u32 cnt1_reg;
+ u32 cnt2_reg;
+};
+
+extern const struct sram_channel cx88_sram_channels[];
+
+/* ----------------------------------------------------------- */
+/* card configuration */
+
+#define CX88_BOARD_NOAUTO UNSET
+#define CX88_BOARD_UNKNOWN 0
+#define CX88_BOARD_HAUPPAUGE 1
+#define CX88_BOARD_GDI 2
+#define CX88_BOARD_PIXELVIEW 3
+#define CX88_BOARD_ATI_WONDER_PRO 4
+#define CX88_BOARD_WINFAST2000XP_EXPERT 5
+#define CX88_BOARD_AVERTV_STUDIO_303 6
+#define CX88_BOARD_MSI_TVANYWHERE_MASTER 7
+#define CX88_BOARD_WINFAST_DV2000 8
+#define CX88_BOARD_LEADTEK_PVR2000 9
+#define CX88_BOARD_IODATA_GVVCP3PCI 10
+#define CX88_BOARD_PROLINK_PLAYTVPVR 11
+#define CX88_BOARD_ASUS_PVR_416 12
+#define CX88_BOARD_MSI_TVANYWHERE 13
+#define CX88_BOARD_KWORLD_DVB_T 14
+#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1 15
+#define CX88_BOARD_KWORLD_LTV883 16
+#define CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q 17
+#define CX88_BOARD_HAUPPAUGE_DVB_T1 18
+#define CX88_BOARD_CONEXANT_DVB_T1 19
+#define CX88_BOARD_PROVIDEO_PV259 20
+#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS 21
+#define CX88_BOARD_PCHDTV_HD3000 22
+#define CX88_BOARD_DNTV_LIVE_DVB_T 23
+#define CX88_BOARD_HAUPPAUGE_ROSLYN 24
+#define CX88_BOARD_DIGITALLOGIC_MEC 25
+#define CX88_BOARD_IODATA_GVBCTV7E 26
+#define CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO 27
+#define CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T 28
+#define CX88_BOARD_ADSTECH_DVB_T_PCI 29
+#define CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1 30
+#define CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD 31
+#define CX88_BOARD_AVERMEDIA_ULTRATV_MC_550 32
+#define CX88_BOARD_KWORLD_VSTREAM_EXPERT_DVD 33
+#define CX88_BOARD_ATI_HDTVWONDER 34
+#define CX88_BOARD_WINFAST_DTV1000 35
+#define CX88_BOARD_AVERTV_303 36
+#define CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1 37
+#define CX88_BOARD_HAUPPAUGE_NOVASE2_S1 38
+#define CX88_BOARD_KWORLD_DVBS_100 39
+#define CX88_BOARD_HAUPPAUGE_HVR1100 40
+#define CX88_BOARD_HAUPPAUGE_HVR1100LP 41
+#define CX88_BOARD_DNTV_LIVE_DVB_T_PRO 42
+#define CX88_BOARD_KWORLD_DVB_T_CX22702 43
+#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL 44
+#define CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT 45
+#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID 46
+#define CX88_BOARD_PCHDTV_HD5500 47
+#define CX88_BOARD_KWORLD_MCE200_DELUXE 48
+#define CX88_BOARD_PIXELVIEW_PLAYTV_P7000 49
+#define CX88_BOARD_NPGTECH_REALTV_TOP10FM 50
+#define CX88_BOARD_WINFAST_DTV2000H 51
+#define CX88_BOARD_GENIATECH_DVBS 52
+#define CX88_BOARD_HAUPPAUGE_HVR3000 53
+#define CX88_BOARD_NORWOOD_MICRO 54
+#define CX88_BOARD_TE_DTV_250_OEM_SWANN 55
+#define CX88_BOARD_HAUPPAUGE_HVR1300 56
+#define CX88_BOARD_ADSTECH_PTV_390 57
+#define CX88_BOARD_PINNACLE_PCTV_HD_800i 58
+#define CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO 59
+#define CX88_BOARD_PINNACLE_HYBRID_PCTV 60
+#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL 61
+#define CX88_BOARD_POWERCOLOR_REAL_ANGEL 62
+#define CX88_BOARD_GENIATECH_X8000_MT 63
+#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO 64
+#define CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD 65
+#define CX88_BOARD_PROLINK_PV_8000GT 66
+#define CX88_BOARD_KWORLD_ATSC_120 67
+#define CX88_BOARD_HAUPPAUGE_HVR4000 68
+#define CX88_BOARD_HAUPPAUGE_HVR4000LITE 69
+#define CX88_BOARD_TEVII_S460 70
+#define CX88_BOARD_OMICOM_SS4_PCI 71
+#define CX88_BOARD_TBS_8920 72
+#define CX88_BOARD_TEVII_S420 73
+#define CX88_BOARD_PROLINK_PV_GLOBAL_XTREME 74
+#define CX88_BOARD_PROF_7300 75
+#define CX88_BOARD_SATTRADE_ST4200 76
+#define CX88_BOARD_TBS_8910 77
+#define CX88_BOARD_PROF_6200 78
+#define CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII 79
+#define CX88_BOARD_HAUPPAUGE_IRONLY 80
+#define CX88_BOARD_WINFAST_DTV1800H 81
+#define CX88_BOARD_WINFAST_DTV2000H_J 82
+#define CX88_BOARD_PROF_7301 83
+#define CX88_BOARD_SAMSUNG_SMT_7020 84
+#define CX88_BOARD_TWINHAN_VP1027_DVBS 85
+#define CX88_BOARD_TEVII_S464 86
+#define CX88_BOARD_WINFAST_DTV2000H_PLUS 87
+#define CX88_BOARD_WINFAST_DTV1800H_XC4000 88
+#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36 89
+#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43 90
+#define CX88_BOARD_NOTONLYTV_LV3H 91
+
+enum cx88_itype {
+ CX88_VMUX_COMPOSITE1 = 1,
+ CX88_VMUX_COMPOSITE2,
+ CX88_VMUX_COMPOSITE3,
+ CX88_VMUX_COMPOSITE4,
+ CX88_VMUX_SVIDEO,
+ CX88_VMUX_TELEVISION,
+ CX88_VMUX_CABLE,
+ CX88_VMUX_DVB,
+ CX88_VMUX_DEBUG,
+ CX88_RADIO,
+};
+
+struct cx88_input {
+ enum cx88_itype type;
+ u32 gpio0, gpio1, gpio2, gpio3;
+ unsigned int vmux:2;
+ unsigned int audioroute:4;
+};
+
+enum cx88_audio_chip {
+ CX88_AUDIO_WM8775 = 1,
+ CX88_AUDIO_TVAUDIO,
+};
+
+struct cx88_board {
+ const char *name;
+ unsigned int tuner_type;
+ unsigned int radio_type;
+ unsigned char tuner_addr;
+ unsigned char radio_addr;
+ int tda9887_conf;
+ struct cx88_input input[MAX_CX88_INPUT];
+ struct cx88_input radio;
+ enum cx88_board_type mpeg;
+ enum cx88_audio_chip audio_chip;
+ int num_frontends;
+
+ /* Used for I2S devices */
+ int i2sinputcntl;
+};
+
+struct cx88_subid {
+ u16 subvendor;
+ u16 subdevice;
+ u32 card;
+};
+
+enum cx88_tvaudio {
+ WW_NONE = 1,
+ WW_BTSC,
+ WW_BG,
+ WW_DK,
+ WW_I,
+ WW_L,
+ WW_EIAJ,
+ WW_I2SPT,
+ WW_FM,
+ WW_I2SADC,
+ WW_M
+};
+
+#define INPUT(nr) (core->board.input[nr])
+
+/* ----------------------------------------------------------- */
+/* device / file handle status */
+
+#define RESOURCE_OVERLAY 1
+#define RESOURCE_VIDEO 2
+#define RESOURCE_VBI 4
+
+#define BUFFER_TIMEOUT msecs_to_jiffies(2000)
+
+struct cx88_riscmem {
+ unsigned int size;
+ __le32 *cpu;
+ __le32 *jmp;
+ dma_addr_t dma;
+};
+
+/* buffer for one video frame */
+struct cx88_buffer {
+ /* common v4l buffer stuff -- must be first */
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+
+ /* cx88 specific */
+ unsigned int bpl;
+ struct cx88_riscmem risc;
+};
+
+struct cx88_dmaqueue {
+ struct list_head active;
+ u32 count;
+};
+
+struct cx8800_dev;
+struct cx8802_dev;
+
+struct cx88_core {
+ struct list_head devlist;
+ refcount_t refcount;
+
+ /* board name */
+ int nr;
+ char name[32];
+ u32 model;
+
+ /* pci stuff */
+ int pci_bus;
+ int pci_slot;
+ u32 __iomem *lmmio;
+ u8 __iomem *bmmio;
+ u32 shadow[SHADOW_MAX];
+ int pci_irqmask;
+
+ /* i2c i/o */
+ struct i2c_adapter i2c_adap;
+ struct i2c_algo_bit_data i2c_algo;
+ struct i2c_client i2c_client;
+ u32 i2c_state, i2c_rc;
+
+ /* config info -- analog */
+ struct v4l2_device v4l2_dev;
+ struct v4l2_ctrl_handler video_hdl;
+ struct v4l2_ctrl *chroma_agc;
+ struct v4l2_ctrl_handler audio_hdl;
+ struct v4l2_subdev *sd_wm8775;
+ struct i2c_client *i2c_rtc;
+ unsigned int boardnr;
+ struct cx88_board board;
+
+ /* Supported V4L _STD_ tuner formats */
+ unsigned int tuner_formats;
+
+ /* config info -- dvb */
+#if IS_ENABLED(CONFIG_VIDEO_CX88_DVB)
+ int (*prev_set_voltage)(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage);
+#endif
+ void (*gate_ctrl)(struct cx88_core *core, int open);
+
+ /* state info */
+ struct task_struct *kthread;
+ v4l2_std_id tvnorm;
+ unsigned int width, height;
+ unsigned int field;
+ enum cx88_tvaudio tvaudio;
+ u32 audiomode_manual;
+ u32 audiomode_current;
+ u32 input;
+ u32 last_analog_input;
+ u32 astat;
+ u32 use_nicam;
+ unsigned long last_change;
+
+ /* IR remote control state */
+ struct cx88_IR *ir;
+
+ /* I2C remote data */
+ struct IR_i2c_init_data init_data;
+ struct wm8775_platform_data wm8775_data;
+
+ struct mutex lock;
+ /* various v4l controls */
+ u32 freq;
+
+ /*
+ * cx88-video needs to access cx8802 for hybrid tuner pll access and
+ * for vb2_is_busy() checks.
+ */
+ struct cx8802_dev *dvbdev;
+ /* cx88-blackbird needs to access cx8800 for vb2_is_busy() checks */
+ struct cx8800_dev *v4ldev;
+ enum cx88_board_type active_type_id;
+ int active_ref;
+ int active_fe_id;
+};
+
+static inline struct cx88_core *to_core(struct v4l2_device *v4l2_dev)
+{
+ return container_of(v4l2_dev, struct cx88_core, v4l2_dev);
+}
+
+#define call_hw(core, grpid, o, f, args...) \
+ do { \
+ if (!core->i2c_rc) { \
+ if (core->gate_ctrl) \
+ core->gate_ctrl(core, 1); \
+ v4l2_device_call_all(&core->v4l2_dev, \
+ grpid, o, f, ##args); \
+ if (core->gate_ctrl) \
+ core->gate_ctrl(core, 0); \
+ } \
+ } while (0)
+
+#define call_all(core, o, f, args...) call_hw(core, 0, o, f, ##args)
+
+#define WM8775_GID (1 << 0)
+
+#define wm8775_s_ctrl(core, id, val) \
+ do { \
+ struct v4l2_ctrl *ctrl_ = \
+ v4l2_ctrl_find(core->sd_wm8775->ctrl_handler, id);\
+ if (ctrl_ && !core->i2c_rc) { \
+ if (core->gate_ctrl) \
+ core->gate_ctrl(core, 1); \
+ v4l2_ctrl_s_ctrl(ctrl_, val); \
+ if (core->gate_ctrl) \
+ core->gate_ctrl(core, 0); \
+ } \
+ } while (0)
+
+#define wm8775_g_ctrl(core, id) \
+ ({ \
+ struct v4l2_ctrl *ctrl_ = \
+ v4l2_ctrl_find(core->sd_wm8775->ctrl_handler, id);\
+ s32 val = 0; \
+ if (ctrl_ && !core->i2c_rc) { \
+ if (core->gate_ctrl) \
+ core->gate_ctrl(core, 1); \
+ val = v4l2_ctrl_g_ctrl(ctrl_); \
+ if (core->gate_ctrl) \
+ core->gate_ctrl(core, 0); \
+ } \
+ val; \
+ })
+
+/* ----------------------------------------------------------- */
+/* function 0: video stuff */
+
+struct cx8800_suspend_state {
+ int disabled;
+};
+
+struct cx8800_dev {
+ struct cx88_core *core;
+ spinlock_t slock;
+
+ /* various device info */
+ unsigned int resources;
+ struct video_device video_dev;
+ struct video_device vbi_dev;
+ struct video_device radio_dev;
+
+ /* pci i/o */
+ struct pci_dev *pci;
+ unsigned char pci_rev, pci_lat;
+
+ const struct cx8800_fmt *fmt;
+
+ /* capture queues */
+ struct cx88_dmaqueue vidq;
+ struct vb2_queue vb2_vidq;
+ struct cx88_dmaqueue vbiq;
+ struct vb2_queue vb2_vbiq;
+
+ /* various v4l controls */
+
+ /* other global state info */
+ struct cx8800_suspend_state state;
+};
+
+/* ----------------------------------------------------------- */
+/* function 1: audio/alsa stuff */
+/* =============> moved to cx88-alsa.c <====================== */
+
+/* ----------------------------------------------------------- */
+/* function 2: mpeg stuff */
+
+struct cx8802_suspend_state {
+ int disabled;
+};
+
+struct cx8802_driver {
+ struct cx88_core *core;
+
+ /* List of drivers attached to device */
+ struct list_head drvlist;
+
+ /* Type of driver and access required */
+ enum cx88_board_type type_id;
+ enum cx8802_board_access hw_access;
+
+ /* MPEG 8802 internal only */
+ int (*suspend)(struct pci_dev *pci_dev, pm_message_t state);
+ int (*resume)(struct pci_dev *pci_dev);
+
+ /* Callers to the following functions must hold core->lock */
+
+ /* MPEG 8802 -> mini driver - Driver probe and configuration */
+ int (*probe)(struct cx8802_driver *drv);
+ int (*remove)(struct cx8802_driver *drv);
+
+ /* MPEG 8802 -> mini driver - Access for hardware control */
+ int (*advise_acquire)(struct cx8802_driver *drv);
+ int (*advise_release)(struct cx8802_driver *drv);
+
+ /* MPEG 8802 <- mini driver - Access for hardware control */
+ int (*request_acquire)(struct cx8802_driver *drv);
+ int (*request_release)(struct cx8802_driver *drv);
+};
+
+struct cx8802_dev {
+ struct cx88_core *core;
+ spinlock_t slock;
+
+ /* pci i/o */
+ struct pci_dev *pci;
+ unsigned char pci_rev, pci_lat;
+
+ /* dma queues */
+ struct cx88_dmaqueue mpegq;
+ struct vb2_queue vb2_mpegq;
+ u32 ts_packet_size;
+ u32 ts_packet_count;
+
+ /* other global state info */
+ struct cx8802_suspend_state state;
+
+ /* for blackbird only */
+ struct list_head devlist;
+#if IS_ENABLED(CONFIG_VIDEO_CX88_BLACKBIRD)
+ struct video_device mpeg_dev;
+ u32 mailbox;
+
+ /* mpeg params */
+ struct cx2341x_handler cxhdl;
+
+#endif
+
+#if IS_ENABLED(CONFIG_VIDEO_CX88_DVB)
+ /* for dvb only */
+ struct vb2_dvb_frontends frontends;
+#endif
+
+#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054)
+ /* For VP3045 secondary I2C bus support */
+ struct vp3054_i2c_state *vp3054;
+#endif
+ /* for switching modulation types */
+ unsigned char ts_gen_cntrl;
+
+ /* List of attached drivers; must hold core->lock to access */
+ struct list_head drvlist;
+
+ struct work_struct request_module_wk;
+};
+
+/* ----------------------------------------------------------- */
+
+#define cx_read(reg) readl(core->lmmio + ((reg) >> 2))
+#define cx_write(reg, value) writel((value), core->lmmio + ((reg) >> 2))
+#define cx_writeb(reg, value) writeb((value), core->bmmio + (reg))
+
+#define cx_andor(reg, mask, value) \
+ writel((readl(core->lmmio + ((reg) >> 2)) & ~(mask)) |\
+ ((value) & (mask)), core->lmmio + ((reg) >> 2))
+#define cx_set(reg, bit) cx_andor((reg), (bit), (bit))
+#define cx_clear(reg, bit) cx_andor((reg), (bit), 0)
+
+#define cx_wait(d) { if (need_resched()) schedule(); else udelay(d); }
+
+/* shadow registers */
+#define cx_sread(sreg) (core->shadow[sreg])
+#define cx_swrite(sreg, reg, value) \
+ (core->shadow[sreg] = value, \
+ writel(core->shadow[sreg], core->lmmio + ((reg) >> 2)))
+#define cx_sandor(sreg, reg, mask, value) \
+ (core->shadow[sreg] = (core->shadow[sreg] & ~(mask)) | \
+ ((value) & (mask)), \
+ writel(core->shadow[sreg], \
+ core->lmmio + ((reg) >> 2)))
+
+/* ----------------------------------------------------------- */
+/* cx88-core.c */
+
+extern unsigned int cx88_core_debug;
+
+void cx88_print_irqbits(const char *tag, const char *strings[],
+ int len, u32 bits, u32 mask);
+
+int cx88_core_irq(struct cx88_core *core, u32 status);
+void cx88_wakeup(struct cx88_core *core,
+ struct cx88_dmaqueue *q, u32 count);
+void cx88_shutdown(struct cx88_core *core);
+int cx88_reset(struct cx88_core *core);
+
+extern int
+cx88_risc_buffer(struct pci_dev *pci, struct cx88_riscmem *risc,
+ struct scatterlist *sglist,
+ unsigned int top_offset, unsigned int bottom_offset,
+ unsigned int bpl, unsigned int padding, unsigned int lines);
+extern int
+cx88_risc_databuffer(struct pci_dev *pci, struct cx88_riscmem *risc,
+ struct scatterlist *sglist, unsigned int bpl,
+ unsigned int lines, unsigned int lpi);
+
+void cx88_risc_disasm(struct cx88_core *core,
+ struct cx88_riscmem *risc);
+int cx88_sram_channel_setup(struct cx88_core *core,
+ const struct sram_channel *ch,
+ unsigned int bpl, u32 risc);
+void cx88_sram_channel_dump(struct cx88_core *core,
+ const struct sram_channel *ch);
+
+int cx88_set_scale(struct cx88_core *core, unsigned int width,
+ unsigned int height, enum v4l2_field field);
+int cx88_set_tvnorm(struct cx88_core *core, v4l2_std_id norm);
+
+void cx88_vdev_init(struct cx88_core *core,
+ struct pci_dev *pci,
+ struct video_device *vfd,
+ const struct video_device *template_,
+ const char *type);
+struct cx88_core *cx88_core_get(struct pci_dev *pci);
+void cx88_core_put(struct cx88_core *core,
+ struct pci_dev *pci);
+
+int cx88_start_audio_dma(struct cx88_core *core);
+int cx88_stop_audio_dma(struct cx88_core *core);
+
+/* ----------------------------------------------------------- */
+/* cx88-vbi.c */
+
+/* Can be used as g_vbi_fmt, try_vbi_fmt and s_vbi_fmt */
+int cx8800_vbi_fmt(struct file *file, void *priv,
+ struct v4l2_format *f);
+
+void cx8800_stop_vbi_dma(struct cx8800_dev *dev);
+int cx8800_restart_vbi_queue(struct cx8800_dev *dev, struct cx88_dmaqueue *q);
+
+extern const struct vb2_ops cx8800_vbi_qops;
+
+/* ----------------------------------------------------------- */
+/* cx88-i2c.c */
+
+int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci);
+
+/* ----------------------------------------------------------- */
+/* cx88-cards.c */
+
+int cx88_tuner_callback(void *dev, int component, int command, int arg);
+int cx88_get_resources(const struct cx88_core *core,
+ struct pci_dev *pci);
+struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr);
+void cx88_setup_xc3028(struct cx88_core *core, struct xc2028_ctrl *ctl);
+
+/* ----------------------------------------------------------- */
+/* cx88-tvaudio.c */
+
+void cx88_set_tvaudio(struct cx88_core *core);
+void cx88_newstation(struct cx88_core *core);
+void cx88_get_stereo(struct cx88_core *core, struct v4l2_tuner *t);
+void cx88_set_stereo(struct cx88_core *core, u32 mode, int manual);
+int cx88_audio_thread(void *data);
+
+int cx8802_register_driver(struct cx8802_driver *drv);
+int cx8802_unregister_driver(struct cx8802_driver *drv);
+
+/* Caller must hold core->lock */
+struct cx8802_driver *cx8802_get_driver(struct cx8802_dev *dev,
+ enum cx88_board_type btype);
+
+/* ----------------------------------------------------------- */
+/* cx88-dsp.c */
+
+s32 cx88_dsp_detect_stereo_sap(struct cx88_core *core);
+
+/* ----------------------------------------------------------- */
+/* cx88-input.c */
+
+int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci);
+int cx88_ir_fini(struct cx88_core *core);
+void cx88_ir_irq(struct cx88_core *core);
+int cx88_ir_start(struct cx88_core *core);
+void cx88_ir_stop(struct cx88_core *core);
+void cx88_i2c_init_ir(struct cx88_core *core);
+
+/* ----------------------------------------------------------- */
+/* cx88-mpeg.c */
+
+int cx8802_buf_prepare(struct vb2_queue *q, struct cx8802_dev *dev,
+ struct cx88_buffer *buf);
+void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf);
+void cx8802_cancel_buffers(struct cx8802_dev *dev);
+int cx8802_start_dma(struct cx8802_dev *dev,
+ struct cx88_dmaqueue *q,
+ struct cx88_buffer *buf);
+
+/* ----------------------------------------------------------- */
+/* cx88-video.c*/
+int cx88_enum_input(struct cx88_core *core, struct v4l2_input *i);
+int cx88_set_freq(struct cx88_core *core, const struct v4l2_frequency *f);
+int cx88_video_mux(struct cx88_core *core, unsigned int input);
+int cx88_querycap(struct file *file, struct cx88_core *core,
+ struct v4l2_capability *cap);
+
+#endif