diff options
author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-10-30 00:20:56 -0700 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-10-30 00:20:56 -0700 |
commit | 53279f36dccffc26ff536003fd6bb97cc21c3b82 (patch) | |
tree | 9d16e497c0e4158c7c054c479bd0e9ff0388d7bb /drivers/media/pci/cx88 | |
parent | a6e8c0a25377e27958b11b20e1927885ae7c9857 (diff) | |
parent | 8f0d8163b50e01f398b14bcd4dc039ac5ab18d64 (diff) |
Merge tag 'v3.7-rc3' into next to sync up with recent USB and MFD changes
Diffstat (limited to 'drivers/media/pci/cx88')
-rw-r--r-- | drivers/media/pci/cx88/Kconfig | 86 | ||||
-rw-r--r-- | drivers/media/pci/cx88/Makefile | 16 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-alsa.c | 975 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-blackbird.c | 1299 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-cards.c | 3811 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-core.c | 1131 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-dsp.c | 322 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-dvb.c | 1778 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-i2c.c | 184 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-input.c | 635 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-mpeg.c | 929 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-reg.h | 836 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-tvaudio.c | 1059 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-vbi.c | 245 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-video.c | 2075 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-vp3054-i2c.c | 159 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88-vp3054-i2c.h | 41 | ||||
-rw-r--r-- | drivers/media/pci/cx88/cx88.h | 748 |
18 files changed, 16329 insertions, 0 deletions
diff --git a/drivers/media/pci/cx88/Kconfig b/drivers/media/pci/cx88/Kconfig new file mode 100644 index 00000000000..d27fccbf03c --- /dev/null +++ b/drivers/media/pci/cx88/Kconfig @@ -0,0 +1,86 @@ +config VIDEO_CX88 + tristate "Conexant 2388x (bt878 successor) support" + depends on VIDEO_DEV && PCI && I2C && RC_CORE + select I2C_ALGOBIT + select VIDEO_BTCX + select VIDEOBUF_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 VIDEOBUF_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 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_VP3054 + tristate "VP-3054 Secondary I2C Bus Support" + default m + 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_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 00000000000..d3679c3ee24 --- /dev/null +++ b/drivers/media/pci/cx88/Makefile @@ -0,0 +1,16 @@ +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 += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/tuners +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/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 00000000000..3aa6856ead3 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-alsa.c @@ -0,0 +1,975 @@ +/* + * + * 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@infradead.org> + * Based on a dummy cx88 module by Gerd Knorr <kraxel@bytesex.org> + * Based on dummy.c by Jaroslav Kysela <perex@perex.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <linux/dma-mapping.h> +#include <linux/pci.h> +#include <linux/slab.h> + +#include <asm/delay.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/wm8775.h> + +#include "cx88.h" +#include "cx88-reg.h" + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg) + +#define dprintk_core(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/1: " fmt, chip->core->name , ## arg) + +/**************************************************************************** + Data type declarations - Can be moded to a header file later + ****************************************************************************/ + +struct cx88_audio_buffer { + unsigned int bpl; + struct btcx_riscmem risc; + struct videobuf_dmabuf dma; +}; + +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 videobuf_dmabuf *dma_risc; + + struct cx88_audio_buffer *buf; + + struct snd_pcm_substream *substream; +}; +typedef struct cx88_audio_dev snd_cx88_card_t; + + + +/**************************************************************************** + 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] = {1, [1 ... (SNDRV_CARDS - 1)] = 1}; + +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@infradead.org>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +MODULE_SUPPORTED_DEVICE("{{Conexant,23881}," + "{{Conexant,23882}," + "{{Conexant,23883}"); +static unsigned int debug; +module_param(debug,int,0644); +MODULE_PARM_DESC(debug,"enable debug messages"); + +/**************************************************************************** + Module specific funtions + ****************************************************************************/ + +/* + * BOARD Specific: Sets audio DMA + */ + +static int _cx88_start_audio_dma(snd_cx88_card_t *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 */ + cx_set(MO_DEV_CNTRL2, (1<<5)); /* Enables Risc Processor */ + cx_set(MO_AUD_DMACNTRL, 0x11); /* audio downstream FIFO and RISC enable */ + + if (debug) + cx88_sram_channel_dump(chip->core, audio_ch); + + return 0; +} + +/* + * BOARD Specific: Resets audio DMA + */ +static int _cx88_stop_audio_dma(snd_cx88_card_t *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(snd_cx88_card_t *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(core->name, "irq aud", + cx88_aud_irqs, ARRAY_SIZE(cx88_aud_irqs), + status, mask); + /* risc op code error */ + if (status & AUD_INT_OPC_ERR) { + printk(KERN_WARNING "%s/1: Audio risc op code error\n",core->name); + 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) +{ + snd_cx88_card_t *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 (0 == status) + 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 (MAX_IRQ_LOOP == loop) { + printk(KERN_ERR + "%s/1: IRQ loop detected, disabling interrupts\n", + core->name); + cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); + } + + out: + return IRQ_RETVAL(handled); +} + + +static int dsp_buffer_free(snd_cx88_card_t *chip) +{ + BUG_ON(!chip->dma_size); + + dprintk(2,"Freeing buffer\n"); + videobuf_dma_unmap(&chip->pci->dev, chip->dma_risc); + videobuf_dma_free(chip->dma_risc); + btcx_riscmem_free(chip->pci,&chip->buf->risc); + kfree(chip->buf); + + chip->dma_risc = NULL; + chip->dma_size = 0; + + 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) +{ + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (!chip) { + printk(KERN_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) +{ + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + struct videobuf_dmabuf *dma; + + 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); + + BUG_ON(!chip->dma_size); + BUG_ON(chip->num_periods & (chip->num_periods-1)); + + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (NULL == buf) + return -ENOMEM; + + buf->bpl = chip->period_size; + + dma = &buf->dma; + videobuf_dma_init(dma); + ret = videobuf_dma_init_kernel(dma, PCI_DMA_FROMDEVICE, + (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT)); + if (ret < 0) + goto error; + + ret = videobuf_dma_map(&chip->pci->dev, dma); + if (ret < 0) + goto error; + + ret = cx88_risc_databuffer(chip->pci, &buf->risc, dma->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); + + chip->buf = buf; + chip->dma_risc = dma; + + substream->runtime->dma_area = chip->dma_risc->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) +{ + + snd_cx88_card_t *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) +{ + snd_cx88_card_t *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) +{ + snd_cx88_card_t *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 struct snd_pcm_ops snd_cx88_pcm_ops = { + .open = snd_cx88_pcm_open, + .close = snd_cx88_close, + .ioctl = snd_pcm_lib_ioctl, + .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 __devinit snd_cx88_pcm(snd_cx88_card_t *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; + strcpy(pcm->name, 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) +{ + snd_cx88_card_t *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) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + int left = value->value.integer.value[0]; + int 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) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core=chip->core; + int left, right, v, b; + int changed = 0; + u32 old; + + if (core->board.audio_chip == V4L2_IDENT_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) +{ + snd_cx88_card_t *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) +{ + snd_cx88_card_t *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->board.audio_chip == V4L2_IDENT_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) +{ + snd_cx88_card_t *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) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + struct v4l2_control client_ctl; + + memset(&client_ctl, 0, sizeof(client_ctl)); + client_ctl.value = 0 != value->value.integer.value[0]; + client_ctl.id = V4L2_CID_AUDIO_LOUDNESS; + call_hw(core, WM8775_GID, core, s_ctrl, &client_ctl); + + return 0; +} + +static 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[] __devinitdata = { + {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(snd_cx88_card_t *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) +{ + snd_cx88_card_t *chip = card->private_data; + + snd_cx88_free(chip); +} + + +/* + * Alsa Constructor - Component probe + */ + +static int devno; +static int __devinit snd_cx88_create(struct snd_card *card, + struct pci_dev *pci, + snd_cx88_card_t **rchip, + struct cx88_core **core_ptr) +{ + snd_cx88_card_t *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 (NULL == core) { + err = -EINVAL; + return err; + } + + if (!pci_dma_supported(pci,DMA_BIT_MASK(32))) { + dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n",core->name); + err = -EIO; + 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 | IRQF_DISABLED, 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); + + snd_card_set_dev(card, &pci->dev); + + *rchip = chip; + *core_ptr = core; + + return 0; +} + +static int __devinit cx88_audio_initdev(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + snd_cx88_card_t *chip; + struct cx88_core *core = NULL; + int err; + + if (devno >= SNDRV_CARDS) + return (-ENODEV); + + if (!enable[devno]) { + ++devno; + return (-ENOENT); + } + + err = snd_card_create(index[devno], id[devno], THIS_MODULE, + sizeof(snd_cx88_card_t), &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->board.audio_chip == V4L2_IDENT_WM8775) + snd_ctl_add(card, snd_ctl_new1(&snd_cx88_alc_switch, chip)); + + strcpy (card->driver, "CX88x"); + sprintf(card->shortname, "Conexant CX%x", pci->device); + sprintf(card->longname, "%s at %#llx", + card->shortname,(unsigned long long)pci_resource_start(pci, 0)); + strcpy (card->mixername, "CX88"); + + 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 __devexit cx88_audio_finidev(struct pci_dev *pci) +{ + struct cx88_audio_dev *card = pci_get_drvdata(pci); + + snd_card_free((void *)card); + + pci_set_drvdata(pci, NULL); + + 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 = __devexit_p(cx88_audio_finidev), +}; + +/**************************************************************************** + LINUX MODULE INIT + ****************************************************************************/ + +/* + * module init + */ +static int __init cx88_audio_init(void) +{ + printk(KERN_INFO "cx2388x alsa driver version %s loaded\n", + CX88_VERSION); + return pci_register_driver(&cx88_audio_pci_driver); +} + +/* + * module remove + */ +static void __exit cx88_audio_fini(void) +{ + pci_unregister_driver(&cx88_audio_pci_driver); +} + +module_init(cx88_audio_init); +module_exit(cx88_audio_fini); diff --git a/drivers/media/pci/cx88/cx88-blackbird.c b/drivers/media/pci/cx88/cx88-blackbird.c new file mode 100644 index 00000000000..62184eb919e --- /dev/null +++ b/drivers/media/pci/cx88/cx88-blackbird.c @@ -0,0 +1,1299 @@ +/* + * + * 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@infradead.org> + * - video_ioctl2 conversion + * + * Includes parts from the ivtv driver <http://sourceforge.net/projects/ivtv/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/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/cx2341x.h> + +#include "cx88.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"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int mpegbufs = 32; +module_param(mpegbufs,int,0644); +MODULE_PARM_DESC(mpegbufs,"number of mpeg buffers, range 2-32"); + +static unsigned int debug; +module_param(debug,int,0644); +MODULE_PARM_DESC(debug,"enable debug messages [blackbird]"); + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-bb: " fmt, dev->core->name , ## arg) + + +/* ------------------------------------------------------------------ */ + +#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 -1; + } + + memory_read(dev->core, dev->mailbox, &flag); + if (flag) { + dprintk(0, "ERROR: Mailbox appears to be in use (%x)\n", flag); + return -1; + } + + 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 */ + memory_write(dev->core, dev->mailbox + 3, IVTV_API_STD_TIMEOUT); /* 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(10); + for (;;) { + memory_read(dev->core, dev->mailbox, &flag); + if (0 != (flag & 4)) + break; + if (time_after(jiffies,timeout)) { + dprintk(0, "ERROR: API Mailbox timeout\n"); + return -1; + } + 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 (4 == signaturecnt) { + dprintk(1, "Mailbox signature found\n"); + return i+1; + } + } + dprintk(0, "Mailbox signature values not found!\n"); + return -1; +} + +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; + u32 *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); + msleep(1); + 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) { + dprintk(0, "ERROR: Hotplug firmware request failed (%s).\n", + CX2341X_FIRM_ENC_FILENAME); + dprintk(0, "Please fix your hotplug setup, the board will " + "not work without firmware loaded!\n"); + return -1; + } + + if (firmware->size != BLACKBIRD_FIRM_IMAGE_SIZE) { + dprintk(0, "ERROR: Firmware size mismatch (have %zd, expected %d)\n", + firmware->size, BLACKBIRD_FIRM_IMAGE_SIZE); + release_firmware(firmware); + return -1; + } + + if (0 != memcmp(firmware->data, magic, 8)) { + dprintk(0, "ERROR: Firmware magic mismatch, wrong file?\n"); + release_firmware(firmware); + return -1; + } + + /* transfer to the chip */ + dprintk(1,"Loading firmware ...\n"); + dataptr = (u32*)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; + } + if (checksum) { + dprintk(0, "ERROR: Firmware load failed (checksum mismatch).\n"); + release_firmware(firmware); + return -1; + } + release_firmware(firmware); + 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); + msleep(1); + + 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) +{ + /* assign frame size */ + blackbird_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0, + dev->height, dev->width); + + dev->cxhdl.width = dev->width; + dev->cxhdl.height = dev->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) { + + dev->mpeg_active = 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; + + retval = blackbird_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */ + 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 file *file, void *priv) +{ + struct cx8802_dev *dev = ((struct cx8802_fh *)priv)->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 + ); + + dev->mpeg_active = 1; + 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); + + dev->mpeg_active = 0; + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int bb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx8802_fh *fh = q->priv_data; + + fh->dev->ts_packet_size = 188 * 4; /* was: 512 */ + fh->dev->ts_packet_count = mpegbufs; /* was: 100 */ + + *size = fh->dev->ts_packet_size * fh->dev->ts_packet_count; + *count = fh->dev->ts_packet_count; + return 0; +} + +static int +bb_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8802_fh *fh = q->priv_data; + return cx8802_buf_prepare(q, fh->dev, (struct cx88_buffer*)vb, field); +} + +static void +bb_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_fh *fh = q->priv_data; + cx8802_buf_queue(fh->dev, (struct cx88_buffer*)vb); +} + +static void +bb_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + cx88_free_buffer(q, (struct cx88_buffer*)vb); +} + +static struct videobuf_queue_ops blackbird_qops = { + .buf_setup = bb_buf_setup, + .buf_prepare = bb_buf_prepare, + .buf_queue = bb_buf_queue, + .buf_release = bb_buf_release, +}; + +/* ------------------------------------------------------------------ */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx8802_dev *dev = ((struct cx8802_fh *)priv)->dev; + struct cx88_core *core = dev->core; + + strcpy(cap->driver, "cx88_blackbird"); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cx88_querycap(file, core, cap); + return 0; +} + +static int vidioc_enum_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + strlcpy(f->description, "MPEG", sizeof(f->description)); + f->pixelformat = V4L2_PIX_FMT_MPEG; + f->flags = V4L2_FMT_FLAG_COMPRESSED; + return 0; +} + +static int vidioc_g_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 4 * mpegbufs; /* 188 * 4 * 1024; */ + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = fh->mpegq.field; + dprintk(1, "VIDIOC_G_FMT: w: %d, h: %d, f: %d\n", + dev->width, dev->height, fh->mpegq.field ); + return 0; +} + +static int vidioc_try_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 4 * mpegbufs; /* 188 * 4 * 1024; */ + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + dprintk(1, "VIDIOC_TRY_FMT: w: %d, h: %d, f: %d\n", + dev->width, dev->height, fh->mpegq.field ); + return 0; +} + +static int vidioc_s_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 4 * mpegbufs; /* 188 * 4 * 1024; */ + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + fh->mpegq.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); + dprintk(1, "VIDIOC_S_FMT: w: %d, h: %d, f: %d\n", + f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.field ); + return 0; +} + +static int vidioc_reqbufs (struct file *file, void *priv, struct v4l2_requestbuffers *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_reqbufs(&fh->mpegq, p)); +} + +static int vidioc_querybuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_querybuf(&fh->mpegq, p)); +} + +static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_qbuf(&fh->mpegq, p)); +} + +static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_dqbuf(&fh->mpegq, p, + file->f_flags & O_NONBLOCK)); +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + if (!dev->mpeg_active) + blackbird_start_codec(file, fh); + return videobuf_streamon(&fh->mpegq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + if (dev->mpeg_active) + blackbird_stop_codec(dev); + return videobuf_streamoff(&fh->mpegq); +} + +static int vidioc_s_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + if (dev->mpeg_active) + blackbird_stop_codec(dev); + + cx88_set_freq (core,f); + blackbird_initialize_codec(dev); + cx88_set_scale(dev->core, dev->width, dev->height, + fh->mpegq.field); + return 0; +} + +static int vidioc_log_status (struct file *file, void *priv) +{ + struct cx8802_dev *dev = ((struct cx8802_fh *)priv)->dev; + 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 cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + return cx88_enum_input (core,i); +} + +static int vidioc_g_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8802_fh *fh = priv; + struct cx88_core *core = fh->dev->core; + + if (unlikely(UNSET == core->board.tuner_type)) + 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 cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + *i = core->input; + return 0; +} + +static int vidioc_s_input (struct file *file, void *priv, unsigned int i) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + if (i >= 4) + return -EINVAL; + if (0 == INPUT(i).type) + return -EINVAL; + + mutex_lock(&core->lock); + cx88_newstation(core); + cx88_video_mux(core,i); + mutex_unlock(&core->lock); + return 0; +} + +static int vidioc_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + u32 reg; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + strcpy(t->name, "Television"); + 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, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + if (UNSET == core->board.tuner_type) + return -EINVAL; + if (0 != t->index) + 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 cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + *tvnorm = core->tvnorm; + return 0; +} + +static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *id) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + mutex_lock(&core->lock); + cx88_set_tvnorm(core,*id); + mutex_unlock(&core->lock); + return 0; +} + +/* FIXME: cx88_ioctl_hook not implemented */ + +static int mpeg_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx8802_dev *dev = video_drvdata(file); + struct cx8802_fh *fh; + struct cx8802_driver *drv = NULL; + int err; + + dprintk( 1, "%s\n", __func__); + + mutex_lock(&dev->core->lock); + + /* 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__); + mutex_unlock(&dev->core->lock); + return -ENODEV; + } + + err = drv->request_acquire(drv); + if (err != 0) { + dprintk(1,"%s: Unable to acquire hardware, %d\n", __func__, err); + mutex_unlock(&dev->core->lock); + return err; + } + + if (!dev->core->mpeg_users && blackbird_initialize_codec(dev) < 0) { + drv->request_release(drv); + mutex_unlock(&dev->core->lock); + return -EINVAL; + } + dprintk(1, "open dev=%s\n", video_device_node_name(vdev)); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) { + drv->request_release(drv); + mutex_unlock(&dev->core->lock); + return -ENOMEM; + } + v4l2_fh_init(&fh->fh, vdev); + file->private_data = fh; + fh->dev = dev; + + videobuf_queue_sg_init(&fh->mpegq, &blackbird_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx88_buffer), + fh, NULL); + + /* FIXME: locking against other video device */ + cx88_set_scale(dev->core, dev->width, dev->height, + fh->mpegq.field); + + dev->core->mpeg_users++; + mutex_unlock(&dev->core->lock); + v4l2_fh_add(&fh->fh); + return 0; +} + +static int mpeg_release(struct file *file) +{ + struct cx8802_fh *fh = file->private_data; + struct cx8802_dev *dev = fh->dev; + struct cx8802_driver *drv = NULL; + + mutex_lock(&dev->core->lock); + + if (dev->mpeg_active && dev->core->mpeg_users == 1) + blackbird_stop_codec(dev); + + cx8802_cancel_buffers(fh->dev); + /* stop mpeg capture */ + videobuf_stop(&fh->mpegq); + + videobuf_mmap_free(&fh->mpegq); + + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + file->private_data = NULL; + kfree(fh); + + /* Make sure we release the hardware */ + drv = cx8802_get_driver(dev, CX88_MPEG_BLACKBIRD); + WARN_ON(!drv); + if (drv) + drv->request_release(drv); + + dev->core->mpeg_users--; + + mutex_unlock(&dev->core->lock); + + return 0; +} + +static ssize_t +mpeg_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct cx8802_fh *fh = file->private_data; + struct cx8802_dev *dev = fh->dev; + + if (!dev->mpeg_active) + blackbird_start_codec(file, fh); + + return videobuf_read_stream(&fh->mpegq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static unsigned int +mpeg_poll(struct file *file, struct poll_table_struct *wait) +{ + unsigned long req_events = poll_requested_events(wait); + struct cx8802_fh *fh = file->private_data; + struct cx8802_dev *dev = fh->dev; + + if (!dev->mpeg_active && (req_events & (POLLIN | POLLRDNORM))) + blackbird_start_codec(file, fh); + + return v4l2_ctrl_poll(file, wait) | videobuf_poll_stream(file, &fh->mpegq, wait); +} + +static int +mpeg_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct cx8802_fh *fh = file->private_data; + + return videobuf_mmap_mapper(&fh->mpegq, vma); +} + +static const struct v4l2_file_operations mpeg_fops = +{ + .owner = THIS_MODULE, + .open = mpeg_open, + .release = mpeg_release, + .read = mpeg_read, + .poll = mpeg_poll, + .mmap = mpeg_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 = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_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 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) +{ + if (dev->mpeg_dev) { + if (video_is_registered(dev->mpeg_dev)) + video_unregister_device(dev->mpeg_dev); + else + video_device_release(dev->mpeg_dev); + dev->mpeg_dev = NULL; + } +} + +static int blackbird_register_video(struct cx8802_dev *dev) +{ + int err; + + dev->mpeg_dev = cx88_vdev_init(dev->core,dev->pci, + &cx8802_mpeg_template,"mpeg"); + dev->mpeg_dev->ctrl_handler = &dev->cxhdl.hdl; + video_set_drvdata(dev->mpeg_dev, dev); + err = video_register_device(dev->mpeg_dev,VFL_TYPE_GRABBER, -1); + if (err < 0) { + printk(KERN_INFO "%s/2: can't register mpeg device\n", + dev->core->name); + return err; + } + printk(KERN_INFO "%s/2: registered device %s [mpeg]\n", + dev->core->name, 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; + 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->width = 720; + if (core->tvnorm & V4L2_STD_525_60) { + dev->height = 480; + } else { + dev->height = 576; + } + dev->cxhdl.port = CX2341X_PORT_STREAMING; + dev->cxhdl.width = dev->width; + dev->cxhdl.height = dev->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); + + /* blackbird stuff */ + printk("%s/2: cx23416 based mpeg encoder (blackbird reference design)\n", + core->name); + 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, dev->height == 576); + cx2341x_handler_setup(&dev->cxhdl); + 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) +{ + printk(KERN_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); + +module_param_named(video_debug,cx8802_mpeg_template.debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages [video]"); diff --git a/drivers/media/pci/cx88/cx88-cards.c b/drivers/media/pci/cx88/cx88-cards.c new file mode 100644 index 00000000000..0c255248cbc --- /dev/null +++ b/drivers/media/pci/cx88/cx88-cards.c @@ -0,0 +1,3811 @@ +/* + * + * device driver for Conexant 2388x based TV cards + * card-specific stuff. + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include "cx88.h" +#include "tea5767.h" +#include "xc4000.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 info_printk(core, fmt, arg...) \ + printk(KERN_INFO "%s: " fmt, core->name , ## arg) + +#define warn_printk(core, fmt, arg...) \ + printk(KERN_WARNING "%s: " fmt, core->name , ## arg) + +#define err_printk(core, fmt, arg...) \ + printk(KERN_ERR "%s: " fmt, core->name , ## arg) + + +/* ------------------------------------------------------------------ */ +/* 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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, /* 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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, /* 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 = TUNER_ABSENT, + .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 = V4L2_IDENT_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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .audio_chip = V4L2_IDENT_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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .audio_chip = V4L2_IDENT_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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, /* 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 = TUNER_ABSENT, + .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 = V4L2_IDENT_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 = V4L2_IDENT_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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .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] = { + .name = "PowerColor RA330", /* Long names may confuse LIRC. */ + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_DEBUG, + .vmux = 3, /* Due to the way the cx88 driver is written, */ + .gpio0 = 0x00ff, /* there is no way to deactivate audio pass- */ + .gpio1 = 0xf39d, /* through without this entry. Furthermore, if */ + .gpio3 = 0x0000, /* the TV mux entry is first, you get audio */ + }, { /* from the tuner on boot for a little while. */ + .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_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 = V4L2_IDENT_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 = TUNER_ABSENT, + .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 = TUNER_ABSENT, + .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, + }, { + .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) { + warn_printk(core, "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; + } + + info_printk(core, "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(&core->i2c_client, &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; + + /* 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: + warn_printk(core, "warning: unknown hauppauge model #%d\n", + tv.model); + break; + } + + info_printk(core, "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 = TUNER_ABSENT, + .name = "NTSC_M" }, + [ 0x02 ] = { .id = TUNER_ABSENT, + .name = "PAL_B" }, + [ 0x03 ] = { .id = TUNER_ABSENT, + .name = "PAL_I" }, + [ 0x04 ] = { .id = TUNER_ABSENT, + .name = "PAL_D" }, + [ 0x05 ] = { .id = TUNER_ABSENT, + .name = "SECAM" }, + + [ 0x10 ] = { .id = TUNER_ABSENT, + .fm = 1, + .name = "TEMIC_4049" }, + [ 0x11 ] = { .id = TUNER_TEMIC_4136FY5, + .name = "TEMIC_4136" }, + [ 0x12 ] = { .id = TUNER_ABSENT, + .name = "TEMIC_4146" }, + + [ 0x20 ] = { .id = TUNER_PHILIPS_FQ1216ME, + .fm = 1, + .name = "PHILIPS_FQ1216_MK3" }, + [ 0x21 ] = { .id = TUNER_ABSENT, .fm = 1, + .name = "PHILIPS_FQ1236_MK3" }, + [ 0x22 ] = { .id = TUNER_ABSENT, + .name = "PHILIPS_FI1236_MK3" }, + [ 0x23 ] = { .id = TUNER_ABSENT, + .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; + + info_printk(core, "GDI: tuner=%s\n", name ? name : "unknown"); + if (NULL == 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) { + warn_printk(core, "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_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: + info_printk(core, "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 */ + info_printk(core, "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 sillicon 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; + } else { + err_printk(core, "xc5000: unknown tuner " + "callback command.\n"); + return -EINVAL; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + if (command == 0) { /* This is the reset command from xc5000 */ + cx_clear(MO_GP0_IO, 0x00000010); + msleep(10); + cx_set(MO_GP0_IO, 0x00000010); + return 0; + } else { + printk(KERN_ERR + "xc5000: unknown tuner callback command.\n"); + return -EINVAL; + } + break; + } + 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) { + printk(KERN_ERR "cx88: Error - i2c private data undefined.\n"); + return -EINVAL; + } + + core = i2c_algo->data; + + if (!core) { + printk(KERN_ERR "cx88: Error - device struct undefined.\n"); + return -EINVAL; + } + + if (component != DVB_FRONTEND_COMPONENT_TUNER) + return -EINVAL; + + switch (core->board.tuner_type) { + case TUNER_XC2028: + info_printk(core, "Calling XC2028/3028 callback\n"); + return cx88_xc2028_tuner_callback(core, command, arg); + case TUNER_XC4000: + info_printk(core, "Calling XC4000 callback\n"); + return cx88_xc4000_tuner_callback(core, command, arg); + case TUNER_XC5000: + info_printk(core, "Calling XC5000 callback\n"); + return cx88_xc5000_tuner_callback(core, command, arg); + } + err_printk(core, "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 (0 == pci->subsystem_vendor && + 0 == pci->subsystem_device) { + printk(KERN_ERR + "%s: Your board has no valid PCI Subsystem ID and thus can't\n" + "%s: be autodetected. Please pass card=<n> insmod option to\n" + "%s: workaround that. Redirect complaints to the vendor of\n" + "%s: the TV card. Best regards,\n" + "%s: -- tux\n", + core->name,core->name,core->name,core->name,core->name); + } else { + printk(KERN_ERR + "%s: Your board isn't known (yet) to the driver. You can\n" + "%s: try to pick one of the existing card configs via\n" + "%s: card=<n> insmod option. Updating to the latest\n" + "%s: version might help as well.\n", + core->name,core->name,core->name,core->name); + } + err_printk(core, "Here is a list of valid choices for the card=<n> " + "insmod option:\n"); + for (i = 0; i < ARRAY_SIZE(cx88_boards); i++) + printk(KERN_ERR "%s: card=%d -> %s\n", + core->name, 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); + mdelay(50); + cx_write(MO_GP2_IO, 0xef5); + mdelay(50); + cx_write(MO_GP2_IO, 0xcf7); + msleep(10); + 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_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); + msleep(1); + 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_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 (0 == 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 (0 == core->i2c_rc) + hauppauge_eeprom(core, eeprom+8); + break; + case CX88_BOARD_GDI: + if (0 == core->i2c_rc) + gdi_eeprom(core, eeprom); + break; + case CX88_BOARD_LEADTEK_PVR2000: + case CX88_BOARD_WINFAST_DV2000: + case CX88_BOARD_WINFAST2000XP_EXPERT: + if (0 == 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 (0 == 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); + msleep(1); + 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); + msleep(1); + cx_set(MO_GP0_IO, 0x00000101); + if (0 == 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); + msleep(1); + 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 (0 == 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 (2 != i2c_master_send(&core->i2c_client, + buffer[i],2)) + warn_printk(core, "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); + msleep(10); + 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 != TUNER_ABSENT) { + 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; + info_printk(core, "Asking xc2028/3028 to load firmware %s\n", + ctl.fname); + call_all(core, tuner, s_config, &xc2028_cfg); + } + call_all(core, core, s_power, 0); +} + +/* ------------------------------------------------------------------ */ + +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) { + printk(KERN_INFO "%s: quirk: PCIPCI_TRITON -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_NATOMA) { + printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VIAETBF) { + printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VSFX) { + printk(KERN_INFO "%s: quirk: PCIPCI_VSFX -- set VSFX\n", + name); + ctrl |= CX88X_EN_VSFX; + } +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n", + name); + lat = 0x0A; + } +#endif + + /* check insmod options */ + if (UNSET != latency) + 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 (UNSET != lat) { + printk(KERN_INFO "%s: setting pci latency timer to %d\n", + name, 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; + printk(KERN_ERR + "%s/%d: Can't get MMIO memory @ 0x%llx, subsystem: %04x:%04x\n", + core->name, 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 == NULL) + return NULL; + + atomic_inc(&core->refcount); + 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); + + strcpy(core->v4l2_dev.name, core->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 (0 != cx88_get_resources(core, pci)) { + 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 == NULL) { + 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; UNSET == core->boardnr && 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 (UNSET == core->boardnr) { + core->boardnr = CX88_BOARD_UNKNOWN; + cx88_card_list(core, pci); + } + + memcpy(&core->board, &cx88_boards[core->boardnr], sizeof(core->board)); + + if (!core->board.num_frontends && (core->board.mpeg & CX88_MPEG_DVB)) + core->board.num_frontends = 1; + + info_printk(core, "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]; + + info_printk(core, "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 (TUNER_ABSENT != core->board.tuner_type) { + /* 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 00000000000..c97b174be3a --- /dev/null +++ b/drivers/media/pci/cx88/cx88-core.c @@ -0,0 +1,1131 @@ +/* + * + * 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@infradead.org> + * - Multituner support + * - video_ioctl2 conversion + * - PAL/M fixes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/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 "cx88.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"); + +/* ------------------------------------------------------------------ */ + +static unsigned int core_debug; +module_param(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 dprintk(level,fmt, arg...) if (core_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, core->name , ## arg) + +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) +{ + struct scatterlist *sg; + unsigned int line,todo,sol; + + /* 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++; + } + 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++; + 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++; + } + *(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 btcx_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; + int rc; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + 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 += 2; + if ((rc = btcx_riscmem_alloc(pci,risc,instructions*8)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + if (UNSET != top_offset) + rp = cx88_risc_field(rp, sglist, top_offset, 0, + bpl, padding, lines, 0); + if (UNSET != bottom_offset) + rp = cx88_risc_field(rp, sglist, bottom_offset, 0x200, + bpl, padding, lines, 0); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof (*risc->cpu) > risc->size); + return 0; +} + +int cx88_risc_databuffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines, unsigned int lpi) +{ + u32 instructions; + __le32 *rp; + int rc; + + /* 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 += 1; + if ((rc = btcx_riscmem_alloc(pci,risc,instructions*8)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + rp = cx88_risc_field(rp, sglist, 0, NO_SYNC_LINE, bpl, 0, lines, lpi); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof (*risc->cpu) > risc->size); + return 0; +} + +int cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value) +{ + __le32 *rp; + int rc; + + if ((rc = btcx_riscmem_alloc(pci, risc, 4*16)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(RISC_WRITECR | RISC_IRQ2 | RISC_IMM); + *(rp++) = cpu_to_le32(reg); + *(rp++) = cpu_to_le32(value); + *(rp++) = cpu_to_le32(mask); + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = cpu_to_le32(risc->dma); + return 0; +} + +void +cx88_free_buffer(struct videobuf_queue *q, struct cx88_buffer *buf) +{ + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + + BUG_ON(in_interrupt()); + videobuf_waiton(q, &buf->vb, 0, 0); + videobuf_dma_unmap(q->dev, dma); + videobuf_dma_free(dma); + btcx_riscmem_free(to_pci_dev(q->dev), &buf->risc); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +/* ------------------------------------------------------------------ */ +/* 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, + }, +}; + +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; + BUG_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; +} + +/* ------------------------------------------------------------------ */ +/* 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; + + printk("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))) + printk(" %s",bits[i]); + printk(" 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; + + printk("%s: %s - dma channel status dump\n", + core->name,ch->name); + for (i = 0; i < ARRAY_SIZE(name); i++) + printk("%s: cmds: %-12s: 0x%08x\n", + core->name,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)); + printk("%s: risc%d: ", core->name, i); + if (--n) + printk("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); + printk("%s: iq %x: ", core->name, i); + n = cx88_risc_decode(risc); + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i+j)); + printk("%s: iq %x: 0x%08x [ arg #%d ]\n", + core->name, i+j, risc, j); + } + } + + printk("%s: fifo: 0x%08x -> 0x%x\n", + core->name, ch->fifo_start, ch->fifo_start+ch->fifo_size); + printk("%s: ctrl: 0x%08x -> 0x%x\n", + core->name, ch->ctrl_start, ch->ctrl_start+6*16); + printk("%s: ptr1_reg: 0x%08x\n", + core->name,cx_read(ch->ptr1_reg)); + printk("%s: ptr2_reg: 0x%08x\n", + core->name,cx_read(ch->ptr2_reg)); + printk("%s: cnt1_reg: 0x%08x\n", + core->name,cx_read(ch->cnt1_reg)); + printk("%s: cnt2_reg: 0x%08x\n", + core->name,cx_read(ch->cnt2_reg)); +} + +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 *name, const char *tag, const char *strings[], + int len, u32 bits, u32 mask) +{ + unsigned int i; + + printk(KERN_DEBUG "%s: %s [0x%x]", name, tag, bits); + for (i = 0; i < len; i++) { + if (!(bits & (1 << i))) + continue; + if (strings[i]) + printk(" %s", strings[i]); + else + printk(" %d", i); + if (!(mask & (1 << i))) + continue; + printk("*"); + } + printk("\n"); +} + +/* ------------------------------------------------------------------ */ + +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(core->name, "irq pci", + cx88_pci_irqs, ARRAY_SIZE(cx88_pci_irqs), + status, core->pci_irqmask); + return handled; +} + +void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count) +{ + struct cx88_buffer *buf; + int bc; + + for (bc = 0;; bc++) { + if (list_empty(&q->active)) + break; + buf = list_entry(q->active.next, + struct cx88_buffer, vb.queue); + /* count comes from the hw and is is 16bit wide -- + * this trick handles wrap-arounds correctly for + * up to 32767 buffers in flight... */ + if ((s16) (count - buf->count) < 0) + break; + do_gettimeofday(&buf->vb.ts); + dprintk(2,"[%p/%d] wakeup reg=%d buf=%d\n",buf,buf->vb.i, + count, buf->count); + buf->vb.state = VIDEOBUF_DONE; + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + } + if (list_empty(&q->active)) { + del_timer(&q->timeout); + } else { + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + } + if (bc != 1) + dprintk(2, "%s: %d buffers handled (should be 1)\n", + __func__, bc); +} + +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); +} + +int cx88_reset(struct cx88_core *core) +{ + dprintk(1,"%s\n",__func__); + 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); + msleep(10); + cx_write(MO_SRST_IO, 1); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static unsigned int inline norm_swidth(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 754 : 922; +} + +static unsigned int inline norm_hdelay(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 135 : 186; +} + +static unsigned int inline norm_vdelay(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 0x24 : 0x18; +} + +static unsigned int inline 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 unsigned int inline 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 unsigned int inline 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 (V4L2_FIELD_INTERLACED == field) + 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; +} + +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) { + printk("%s/0: pll out of range\n",core->name); + 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"); + msleep(10); + } + 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 (CX88_VMUX_TELEVISION != INPUT(core->input).type && + CX88_VMUX_CABLE != INPUT(core->input).type) + 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 & norm) || + (V4L2_STD_PAL_M & norm)) { + core->tvaudio = WW_BTSC; + + } else if (V4L2_STD_NTSC_M_JP & norm) { + core->tvaudio = WW_EIAJ; + + } else { + printk("%s/0: tvaudio support needs work for this tv norm [%s], sorry\n", + core->name, 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; + + 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, core, 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; +} + +/* ------------------------------------------------------------------ */ + +struct video_device *cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + const struct video_device *template_, + const char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template_; + vfd->v4l2_dev = &core->v4l2_dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + core->name, type, core->board.name); + set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); + return vfd; +} + +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 (0 != cx88_get_resources(core, pci)) { + mutex_unlock(&devlist); + return NULL; + } + atomic_inc(&core->refcount); + mutex_unlock(&devlist); + return core; + } + + core = cx88_core_create(pci, cx88_devcount); + if (NULL != core) { + cx88_devcount++; + list_add_tail(&core->devlist, &cx88_devlist); + } + + mutex_unlock(&devlist); + return core; +} + +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 (!atomic_dec_and_test(&core->refcount)) + return; + + mutex_lock(&devlist); + cx88_ir_fini(core); + if (0 == core->i2c_rc) { + if (core->i2c_rtc) + 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_print_irqbits); + +EXPORT_SYMBOL(cx88_core_irq); +EXPORT_SYMBOL(cx88_wakeup); +EXPORT_SYMBOL(cx88_reset); +EXPORT_SYMBOL(cx88_shutdown); + +EXPORT_SYMBOL(cx88_risc_buffer); +EXPORT_SYMBOL(cx88_risc_databuffer); +EXPORT_SYMBOL(cx88_risc_stopper); +EXPORT_SYMBOL(cx88_free_buffer); + +EXPORT_SYMBOL(cx88_sram_channels); +EXPORT_SYMBOL(cx88_sram_channel_setup); +EXPORT_SYMBOL(cx88_sram_channel_dump); + +EXPORT_SYMBOL(cx88_set_tvnorm); +EXPORT_SYMBOL(cx88_set_scale); + +EXPORT_SYMBOL(cx88_vdev_init); +EXPORT_SYMBOL(cx88_core_get); +EXPORT_SYMBOL(cx88_core_put); + +EXPORT_SYMBOL(cx88_ir_start); +EXPORT_SYMBOL(cx88_ir_stop); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + * kate: eol "unix"; indent-width 3; remove-trailing-space on; replace-trailing-space-save on; tab-width 8; replace-tabs off; space-indent off; mixed-indent off + */ diff --git a/drivers/media/pci/cx88/cx88-dsp.c b/drivers/media/pci/cx88/cx88-dsp.c new file mode 100644 index 00000000000..a9907265ff6 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-dsp.c @@ -0,0 +1,322 @@ +/* + * + * Stereo and SAP detection for cx88 + * + * Copyright (c) 2009 Marton Balint <cus@fazekas.hu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <asm/div64.h> + +#include "cx88.h" +#include "cx88-reg.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 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...) if (dsp_debug >= level) \ + printk(KERN_DEBUG "%s/0: " fmt, core->name , ## arg) + +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: + printk(KERN_WARNING "%s/0: unsupported audio mode %d for %s\n", + core->name, 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(sizeof(s16)*sample_count, 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; + } + + if (dsp_debug >= 2) { + dprintk(2, "RDS samples dump: "); + for (i = 0; i < sample_count; i++) + printk("%hd ", samples[i]); + printk(".\n"); + } + + 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 (UNSET != ret) + 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 00000000000..666f83b2f3c --- /dev/null +++ b/drivers/media/pci/cx88/cx88-dvb.c @@ -0,0 +1,1778 @@ +/* + * + * 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] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kthread.h> +#include <linux/file.h> +#include <linux/suspend.h> + +#include "cx88.h" +#include "dvb-pll.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 "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...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-dvb: " fmt, core->name, ## arg) + +/* ------------------------------------------------------------------ */ + +static int dvb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx8802_dev *dev = q->priv_data; + + dev->ts_packet_size = 188 * 4; + dev->ts_packet_count = dvb_buf_tscnt; + + *size = dev->ts_packet_size * dev->ts_packet_count; + *count = dvb_buf_tscnt; + return 0; +} + +static int dvb_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct cx8802_dev *dev = q->priv_data; + return cx8802_buf_prepare(q, dev, (struct cx88_buffer*)vb,field); +} + +static void dvb_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_dev *dev = q->priv_data; + cx8802_buf_queue(dev, (struct cx88_buffer*)vb); +} + +static void dvb_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + cx88_free_buffer(q, (struct cx88_buffer*)vb); +} + +static const struct videobuf_queue_ops dvb_qops = { + .buf_setup = dvb_buf_setup, + .buf_prepare = dvb_buf_prepare, + .buf_queue = dvb_buf_queue, + .buf_release = dvb_buf_release, +}; + +/* ------------------------------------------------------------------ */ + +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 = videobuf_dvb_find_frontend(&dev->frontends, fe); + if (!fe_id) { + printk(KERN_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 videobuf_dvb_frontends *f; + struct videobuf_dvb_frontend *fe; + + if (!core->dvbdev) + return; + + f = &core->dvbdev->frontends; + + if (!f) + return; + + if (f->gate <= 1) /* undefined or fe0 */ + fe = videobuf_dvb_get_frontend(f, 1); + else + fe = videobuf_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 struct mb86a16_config twinhan_vp1027 = { + .demod_address = 0x08, +}; + +#if defined(CONFIG_VIDEO_CX88_VP3054) || (defined(CONFIG_VIDEO_CX88_VP3054_MODULE) && defined(MODULE)) +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_address = 0x0e, + .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_address = 0x0e, + .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_address = 0x59, + .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, + fe_sec_voltage_t 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, + fe_sec_voltage_t 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, + fe_sec_voltage_t 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, + fe_sec_voltage_t 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_NONCONTINOUS_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_CONTINOUS_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_CONTINOUS_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_CONTINOUS_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 videobuf_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 = videobuf_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + if (!fe0->dvb.frontend) { + printk(KERN_ERR "%s/2: dvb frontend not attached. " + "Can't attach xc3028\n", + dev->core->name); + 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) { + printk(KERN_ERR "%s/2: xc3028 attach failed\n", + dev->core->name); + dvb_frontend_detach(fe0->dvb.frontend); + dvb_unregister_frontend(fe0->dvb.frontend); + fe0->dvb.frontend = NULL; + return -EINVAL; + } + + printk(KERN_INFO "%s/2: xc3028 attached\n", + dev->core->name); + + return 0; +} + +static int attach_xc4000(struct cx8802_dev *dev, struct xc4000_config *cfg) +{ + struct dvb_frontend *fe; + struct videobuf_dvb_frontend *fe0 = NULL; + + /* Get the first frontend */ + fe0 = videobuf_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + if (!fe0->dvb.frontend) { + printk(KERN_ERR "%s/2: dvb frontend not attached. " + "Can't attach xc4000\n", + dev->core->name); + return -EINVAL; + } + + fe = dvb_attach(xc4000_attach, fe0->dvb.frontend, &dev->core->i2c_adap, + cfg); + if (!fe) { + printk(KERN_ERR "%s/2: xc4000 attach failed\n", + dev->core->name); + dvb_frontend_detach(fe0->dvb.frontend); + dvb_unregister_frontend(fe0->dvb.frontend); + fe0->dvb.frontend = NULL; + return -EINVAL; + } + + printk(KERN_INFO "%s/2: xc4000 attached\n", dev->core->name); + + 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); + msleep(10); + /* Take the cx24116 out of reset */ + cx_write(MO_SRST_IO, 1); + msleep(10); + + 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 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 videobuf_dvb_frontend *fe = NULL; + int i; + + mutex_init(&dev->frontends.lock); + INIT_LIST_HEAD(&dev->frontends.felist); + + if (!core->board.num_frontends) + return -ENODEV; + + printk(KERN_INFO "%s() allocating %d frontend(s)\n", __func__, + core->board.num_frontends); + for (i = 1; i <= core->board.num_frontends; i++) { + fe = videobuf_dvb_alloc_frontend(&dev->frontends, i); + if (!fe) { + printk(KERN_ERR "%s() failed to alloc\n", __func__); + videobuf_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, + fe_sec_tone_mode_t 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, + fe_sec_voltage_t 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 videobuf_dvb_frontend *fe0, *fe1 = NULL; + int mfe_shared = 0; /* bus not shared by default */ + int res = -EINVAL; + + if (0 != core->i2c_rc) { + printk(KERN_ERR "%s/2: no i2c-bus available, cannot attach dvb drivers\n", core->name); + goto frontend_detach; + } + + /* Get the first frontend */ + fe0 = videobuf_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 != NULL) { + 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 != NULL) { + 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 != NULL) { + 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 != NULL) { + 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)) + goto frontend_detach; + } + /* MFE frontend 2 */ + fe1 = videobuf_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 != NULL) { + 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 != NULL) { + 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 != NULL) { + 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 != NULL) { + 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 != NULL) { + 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 != NULL) { + 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 defined(CONFIG_VIDEO_CX88_VP3054) || (defined(CONFIG_VIDEO_CX88_VP3054_MODULE) && defined(MODULE)) + /* 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 != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } +#else + printk(KERN_ERR "%s/2: built without vp3054 support\n", + core->name); +#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 != NULL) { + 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 == NULL) + 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 != NULL) { + 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); + mdelay(100); + cx_set(MO_GP0_IO, 1); + mdelay(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, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + 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); + mdelay(100); + cx_set(MO_GP0_IO, 9); + mdelay(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_3_gold, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + 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); + mdelay(100); + cx_set(MO_GP0_IO, 1); + mdelay(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_5_gold, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + 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); + mdelay(100); + cx_set(MO_GP0_IO, 1); + mdelay(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &pchdtv_hd5500, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + 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 != NULL) { + 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) { + if (!dvb_attach(isl6421_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x08, ISL6421_DCL, 0x00)) + 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 != NULL) { + 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 != NULL) { + 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 != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + 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 != NULL) { + 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)) + goto frontend_detach; + } + /* MFE frontend 2 */ + fe1 = videobuf_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)) + 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 != NULL) { + 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 != NULL) { + 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 != NULL) + 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 != NULL) + 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 != NULL) + 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 != NULL) { + 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); + mdelay(100); + cx_set(MO_GP0_IO, 0x01); + mdelay(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: + printk(KERN_ERR "%s/2: The frontend of your DVB/ATSC card isn't supported yet\n", + core->name); + break; + } + + if ( (NULL == fe0->dvb.frontend) || (fe1 && NULL == fe1->dvb.frontend) ) { + printk(KERN_ERR + "%s/2: frontend initialization failed\n", + core->name); + 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 analog decoder in standby to keep it quiet */ + call_all(core, core, s_power, 0); + + /* register everything */ + res = videobuf_dvb_register_bus(&dev->frontends, THIS_MODULE, dev, + &dev->pci->dev, adapter_nr, mfe_shared); + if (res) + goto frontend_detach; + return res; + +frontend_detach: + core->gate_ctrl = NULL; + videobuf_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 videobuf_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 (0 != err) + goto fail_core; + + /* dvb stuff */ + printk(KERN_INFO "%s/2: cx2388x based DVB/ATSC card\n", core->name); + dev->ts_gen_cntrl = 0x0c; + + err = cx8802_alloc_frontends(dev); + if (err) + goto fail_core; + + err = -ENODEV; + for (i = 1; i <= core->board.num_frontends; i++) { + fe = videobuf_dvb_get_frontend(&core->dvbdev->frontends, i); + if (fe == NULL) { + printk(KERN_ERR "%s() failed to get frontend(%d)\n", + __func__, i); + goto fail_probe; + } + videobuf_queue_sg_init(&fe->dvb.dvbq, &dvb_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_TOP, + sizeof(struct cx88_buffer), + dev, NULL); + /* init struct videobuf_dvb */ + fe->dvb.name = dev->core->name; + } + + err = dvb_register(dev); + if (err) + /* frontends/adapter de-allocated in dvb_register */ + printk(KERN_ERR "%s/2: dvb_register failed (err = %d)\n", + core->name, err); + return err; +fail_probe: + videobuf_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__); + + videobuf_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) +{ + printk(KERN_INFO "cx88/2: 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 00000000000..de0f1af74e4 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-i2c.c @@ -0,0 +1,184 @@ + +/* + + 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@infradead.org> + - Multituner support and i2c address binding + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> + +#include <asm/io.h> + +#include "cx88.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...) if (i2c_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, core->name , ## arg) + +/* ----------------------------------------------------------------------- */ + +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; + printk("%s: i2c scan: found device @ 0x%x [%s]\n", + name, 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; + + memcpy(&core->i2c_algo, &cx8800_i2c_algo_template, + sizeof(core->i2c_algo)); + + + core->i2c_adap.dev.parent = &pci->dev; + strlcpy(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; + strlcpy(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 (0 == core->i2c_rc) { + 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: + printk("%s: i2c init: enabling analog demod on HVR1300/3000/4000 tuner\n", + core->name); + i2c_transfer(core->i2c_client.adapter, &tuner_msg, 1); + break; + default: + break; + } + if (i2c_scan) + do_i2c_scan(core->name,&core->i2c_client); + } else + printk("%s: i2c register FAILED\n", core->name); + + 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 00000000000..ebf448c48ca --- /dev/null +++ b/drivers/media/pci/cx88/cx88-input.c @@ -0,0 +1,635 @@ +/* + * + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/hrtimer.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include "cx88.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 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...) if (ir_debug) \ + printk(KERN_DEBUG "%s IR: " fmt , ir->core->name , ##arg) + +#define dprintk(fmt, arg...) if (ir_debug) \ + printk(KERN_DEBUG "cx88 IR: " fmt , ##arg) + +/* ---------------------------------------------------------------------- */ + +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, data, 0); + + } else if (ir->mask_keydown) { + /* bit set on keydown */ + if (gpio & ir->mask_keydown) + rc_keydown_notimeout(ir->dev, 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, data, 0); + else + rc_keyup(ir->dev); + + } else { + /* can't distinguish keydown/up :-/ */ + rc_keydown_notimeout(ir->dev, data, 0); + rc_keyup(ir->dev); + } +} + +static enum hrtimer_restart cx88_ir_work(struct hrtimer *timer) +{ + unsigned long 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 %ld\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; +} + +void cx88_ir_stop(struct cx88_core *core) +{ + if (core->ir->users) + __cx88_ir_stop(core); +} + +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_type = RC_TYPE_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(); + 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; + 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; + rc_type = RC_TYPE_NEC; + 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->input_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->scanmask = hardware_mask; + + if (ir->sampling) { + dev->driver_type = RC_DRIVER_IR_RAW; + dev->timeout = 10 * 1000 * 1000; /* 10 ms */ + } else { + dev->driver_type = RC_DRIVER_SCANCODE; + dev->allowed_protos = rc_type; + } + + 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 (NULL == 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 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; + + init_ir_raw_event(&ev); + 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 * (NSEC_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, u32 *ir_key, u32 *ir_raw) +{ + 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); + + *ir_key = code & 0xff; + *ir_raw = code; + return 1; +} + +void cx88_i2c_init_ir(struct cx88_core *core) +{ + struct i2c_board_info info; + const unsigned short default_addr_list[] = { + 0x18, 0x6b, 0x71, + I2C_CLIENT_END + }; + 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 (0 != core->i2c_rc) + return; + + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(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_TYPE_UNKNOWN; + core->init_data.get_key = get_key_pvr2000; + core->init_data.ir_codes = RC_MAP_EMPTY; + break; + } + + /* + * We can't call i2c_new_probed_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 XVR */ + core->init_data.name = "cx88 Hauppauge XVR remote"; + core->init_data.ir_codes = RC_MAP_HAUPPAUGE; + core->init_data.type = RC_TYPE_RC5; + 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_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 00000000000..d154bc19735 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-mpeg.c @@ -0,0 +1,929 @@ +/* + * + * 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <asm/delay.h> + +#include "cx88.h" + +/* ------------------------------------------------------------------ */ + +MODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards"); +MODULE_AUTHOR("Jelle Foks <jelle@foks.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...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-mpeg: " fmt, dev->core->name, ## arg) + +#define mpeg_dbg(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-mpeg: " fmt, core->name, ## arg) + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct cx8802_dev *dev=container_of(work, struct cx8802_dev, request_module_wk); + + if (dev->core->board.mpeg & CX88_MPEG_DVB) + request_module("cx88-dvb"); + if (dev->core->board.mpeg & CX88_MPEG_BLACKBIRD) + request_module("cx88-blackbird"); +} + +static void request_modules(struct cx8802_dev *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +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); +/* ------------------------------------------------------------------ */ + +static int cx8802_start_dma(struct cx8802_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + dprintk(1, "cx8802_start_dma w: %d, h: %d, f: %d\n", + buf->vb.width, buf->vb.height, buf->vb.field); + + /* setup fifo + format */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], + dev->ts_packet_size, buf->risc.dma); + + /* write TS length to chip */ + cx_write(MO_TS_LNGTH, buf->vb.width); + + /* FIXME: this needs a review. + * also: move to cx88-blackbird + cx88-dvb source files? */ + + dprintk( 1, "core->active_type_id = 0x%08x\n", core->active_type_id); + + if ( (core->active_type_id == CX88_MPEG_DVB) && + (core->board.mpeg & CX88_MPEG_DVB) ) { + + dprintk( 1, "cx8802_start_dma doing .dvb\n"); + /* negedge driven & software reset */ + cx_write(TS_GEN_CNTRL, 0x0040 | dev->ts_gen_cntrl); + udelay(100); + cx_write(MO_PINMUX_IO, 0x00); + cx_write(TS_HW_SOP_CNTRL, 0x47<<16|188<<4|0x01); + switch (core->boardnr) { + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q: + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T: + case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: + case CX88_BOARD_PCHDTV_HD5500: + cx_write(TS_SOP_STAT, 1<<13); + break; + case CX88_BOARD_SAMSUNG_SMT_7020: + cx_write(TS_SOP_STAT, 0x00); + break; + case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: + case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: + cx_write(MO_PINMUX_IO, 0x88); /* Enable MPEG parallel IO and video signal pins */ + udelay(100); + break; + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* 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 */ + + cx_write(TS_GEN_CNTRL, 0x46); /* punctured clock TS & posedge driven & software reset */ + udelay(100); + + cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */ + cx_write(TS_VALERR_CNTRL, 0x2000); + + cx_write(TS_GEN_CNTRL, 0x06); /* punctured clock TS & posedge driven */ + udelay(100); + } else { + printk( "%s() Failed. Unsupported value in .mpeg (0x%08x)\n", __func__, + core->board.mpeg ); + return -EINVAL; + } + + /* reset counter */ + cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET); + q->count = 1; + + /* enable irqs */ + dprintk( 1, "setting the interrupt mask\n" ); + cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_TSINT); + cx_set(MO_TS_INTMSK, 0x1f0011); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1<<5)); + cx_set(MO_TS_DMACNTRL, 0x11); + return 0; +} + +static int cx8802_stop_dma(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + dprintk( 1, "cx8802_stop_dma\n" ); + + /* stop dma */ + cx_clear(MO_TS_DMACNTRL, 0x11); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, PCI_INT_TSINT); + cx_clear(MO_TS_INTMSK, 0x1f0011); + + /* Reset the controller */ + cx_write(TS_GEN_CNTRL, 0xcd); + return 0; +} + +static int cx8802_restart_queue(struct cx8802_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf; + + dprintk( 1, "cx8802_restart_queue\n" ); + if (list_empty(&q->active)) + { + struct cx88_buffer *prev; + prev = NULL; + + dprintk(1, "cx8802_restart_queue: queue is empty\n" ); + + for (;;) { + if (list_empty(&q->queued)) + return 0; + buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue); + if (NULL == prev) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + cx8802_start_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(1,"[%p/%d] restart_queue - first active\n", + buf,buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(1,"[%p/%d] restart_queue - move to active\n", + buf,buf->vb.i); + } else { + return 0; + } + prev = buf; + } + return 0; + } + + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + cx8802_start_dma(dev, q, buf); + list_for_each_entry(buf, &q->active, vb.queue) + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +/* ------------------------------------------------------------------ */ + +int cx8802_buf_prepare(struct videobuf_queue *q, struct cx8802_dev *dev, + struct cx88_buffer *buf, enum v4l2_field field) +{ + int size = dev->ts_packet_size * dev->ts_packet_count; + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + int rc; + + dprintk(1, "%s: %p\n", __func__, buf); + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + buf->vb.width = dev->ts_packet_size; + buf->vb.height = dev->ts_packet_count; + buf->vb.size = size; + buf->vb.field = field /*V4L2_FIELD_TOP*/; + + if (0 != (rc = videobuf_iolock(q,&buf->vb,NULL))) + goto fail; + cx88_risc_databuffer(dev->pci, &buf->risc, + dma->sglist, + buf->vb.width, buf->vb.height, 0); + } + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + + fail: + cx88_free_buffer(q,buf); + return rc; +} + +void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf) +{ + struct cx88_buffer *prev; + struct cx88_dmaqueue *cx88q = &dev->mpegq; + + dprintk( 1, "cx8802_buf_queue\n" ); + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(cx88q->stopper.dma); + + if (list_empty(&cx88q->active)) { + dprintk( 1, "queue is empty - first active\n" ); + list_add_tail(&buf->vb.queue,&cx88q->active); + cx8802_start_dma(dev, cx88q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = cx88q->count++; + mod_timer(&cx88q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(1,"[%p/%d] %s - first active\n", + buf, buf->vb.i, __func__); + + } else { + dprintk( 1, "queue is not empty - append to active\n" ); + prev = list_entry(cx88q->active.prev, struct cx88_buffer, vb.queue); + list_add_tail(&buf->vb.queue,&cx88q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = cx88q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk( 1, "[%p/%d] %s - append to active\n", + buf, buf->vb.i, __func__); + } +} + +/* ----------------------------------------------------------- */ + +static void do_cancel_buffers(struct cx8802_dev *dev, const char *reason, int restart) +{ + struct cx88_dmaqueue *q = &dev->mpegq; + struct cx88_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + dprintk(1,"[%p/%d] %s - dma=0x%08lx\n", + buf, buf->vb.i, reason, (unsigned long)buf->risc.dma); + } + if (restart) + { + dprintk(1, "restarting queue\n" ); + cx8802_restart_queue(dev,q); + } + spin_unlock_irqrestore(&dev->slock,flags); +} + +void cx8802_cancel_buffers(struct cx8802_dev *dev) +{ + struct cx88_dmaqueue *q = &dev->mpegq; + + dprintk( 1, "cx8802_cancel_buffers" ); + del_timer_sync(&q->timeout); + cx8802_stop_dma(dev); + do_cancel_buffers(dev,"cancel",0); +} + +static void cx8802_timeout(unsigned long data) +{ + struct cx8802_dev *dev = (struct cx8802_dev*)data; + + dprintk(1, "%s\n",__func__); + + if (debug) + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); + cx8802_stop_dma(dev); + do_cancel_buffers(dev,"timeout",1); +} + +static 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, "cx8802_mpeg_irq\n" ); + status = cx_read(MO_TS_INTSTAT); + mask = cx_read(MO_TS_INTMSK); + if (0 == (status & mask)) + return; + + cx_write(MO_TS_INTSTAT, status); + + if (debug || (status & mask & ~0xff)) + cx88_print_irqbits(core->name, "irq mpeg ", + cx88_mpeg_irqs, ARRAY_SIZE(cx88_mpeg_irqs), + status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + printk(KERN_WARNING "%s: mpeg risc op code error\n",core->name); + cx_clear(MO_TS_DMACNTRL, 0x11); + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); + } + + /* risc1 y */ + if (status & 0x01) { + dprintk( 1, "wake up\n" ); + spin_lock(&dev->slock); + count = cx_read(MO_TS_GPCNT); + cx88_wakeup(dev->core, &dev->mpegq, count); + spin_unlock(&dev->slock); + } + + /* risc2 y */ + if (status & 0x10) { + spin_lock(&dev->slock); + cx8802_restart_queue(dev,&dev->mpegq); + spin_unlock(&dev->slock); + } + + /* other general errors */ + if (status & 0x1f0100) { + dprintk( 0, "general errors: 0x%08x\n", status & 0x1f0100 ); + spin_lock(&dev->slock); + cx8802_stop_dma(dev); + cx8802_restart_queue(dev,&dev->mpegq); + spin_unlock(&dev->slock); + } +} + +#define MAX_IRQ_LOOP 10 + +static irqreturn_t cx8802_irq(int irq, void *dev_id) +{ + struct cx8802_dev *dev = dev_id; + struct cx88_core *core = dev->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { + status = cx_read(MO_PCI_INTSTAT) & + (core->pci_irqmask | PCI_INT_TSINT); + if (0 == status) + goto out; + dprintk( 1, "cx8802_irq\n" ); + dprintk( 1, " loop: %d/%d\n", loop, MAX_IRQ_LOOP ); + dprintk( 1, " status: %d\n", status ); + handled = 1; + cx_write(MO_PCI_INTSTAT, status); + + if (status & core->pci_irqmask) + cx88_core_irq(core,status); + if (status & PCI_INT_TSINT) + cx8802_mpeg_irq(dev); + } + if (MAX_IRQ_LOOP == loop) { + dprintk( 0, "clearing mask\n" ); + printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n", + core->name); + cx_write(MO_PCI_INTMSK,0); + } + + out: + return IRQ_RETVAL(handled); +} + +static int cx8802_init_common(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + int err; + + /* pci init */ + if (pci_enable_device(dev->pci)) + return -EIO; + pci_set_master(dev->pci); + if (!pci_dma_supported(dev->pci,DMA_BIT_MASK(32))) { + printk("%s/2: Oops: no 32bit PCI DMA ???\n",dev->core->name); + return -EIO; + } + + dev->pci_rev = dev->pci->revision; + pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s/2: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%llx\n", dev->core->name, + pci_name(dev->pci), dev->pci_rev, dev->pci->irq, + dev->pci_lat,(unsigned long long)pci_resource_start(dev->pci,0)); + + /* initialize driver struct */ + spin_lock_init(&dev->slock); + + /* init dma queue */ + INIT_LIST_HEAD(&dev->mpegq.active); + INIT_LIST_HEAD(&dev->mpegq.queued); + dev->mpegq.timeout.function = cx8802_timeout; + dev->mpegq.timeout.data = (unsigned long)dev; + init_timer(&dev->mpegq.timeout); + cx88_risc_stopper(dev->pci,&dev->mpegq.stopper, + MO_TS_DMACNTRL,0x11,0x00); + + /* get irq */ + err = request_irq(dev->pci->irq, cx8802_irq, + IRQF_SHARED | IRQF_DISABLED, dev->core->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + dev->core->name, dev->pci->irq); + return err; + } + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + /* everything worked */ + pci_set_drvdata(dev->pci,dev); + return 0; +} + +static void cx8802_fini_common(struct cx8802_dev *dev) +{ + dprintk( 2, "cx8802_fini_common\n" ); + cx8802_stop_dma(dev); + pci_disable_device(dev->pci); + + /* unregister stuff */ + free_irq(dev->pci->irq, dev); + pci_set_drvdata(dev->pci, NULL); + + /* free memory */ + btcx_riscmem_free(dev->pci,&dev->mpegq.stopper); +} + +/* ----------------------------------------------------------- */ + +static int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + /* stop mpeg dma */ + spin_lock(&dev->slock); + if (!list_empty(&dev->mpegq.active)) { + dprintk( 2, "suspend\n" ); + printk("%s: suspend mpeg\n", core->name); + cx8802_stop_dma(dev); + del_timer(&dev->mpegq.timeout); + } + spin_unlock(&dev->slock); + + /* FIXME -- shutdown device */ + cx88_shutdown(dev->core); + + pci_save_state(pci_dev); + if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { + pci_disable_device(pci_dev); + dev->state.disabled = 1; + } + return 0; +} + +static int cx8802_resume_common(struct pci_dev *pci_dev) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + int err; + + if (dev->state.disabled) { + err=pci_enable_device(pci_dev); + if (err) { + printk(KERN_ERR "%s: can't enable device\n", + dev->core->name); + return err; + } + dev->state.disabled = 0; + } + err=pci_set_power_state(pci_dev, PCI_D0); + if (err) { + printk(KERN_ERR "%s: can't enable device\n", + dev->core->name); + pci_disable_device(pci_dev); + dev->state.disabled = 1; + + return err; + } + pci_restore_state(pci_dev); + + /* FIXME: re-initialize hardware */ + cx88_reset(dev->core); + + /* restart video+vbi capture */ + spin_lock(&dev->slock); + if (!list_empty(&dev->mpegq.active)) { + printk("%s: resume mpeg\n", core->name); + cx8802_restart_queue(dev,&dev->mpegq); + } + spin_unlock(&dev->slock); + + return 0; +} + +struct cx8802_driver * cx8802_get_driver(struct cx8802_dev *dev, enum cx88_board_type btype) +{ + struct cx8802_driver *d; + + list_for_each_entry(d, &dev->drvlist, drvlist) + if (d->type_id == btype) + return d; + + return NULL; +} + +/* Driver asked for hardware access. */ +static int cx8802_request_acquire(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + 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 < (sizeof(core->board.input) / sizeof(struct cx88_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); + } + + mpeg_dbg(1,"%s() Post acquire GPIO=%x\n", __func__, cx_read(MO_GP0_IO)); + } + + return 0; +} + +/* Driver asked to release hardware. */ +static int cx8802_request_release(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + + 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; + mpeg_dbg(1,"%s() Post release GPIO=%x\n", __func__, cx_read(MO_GP0_IO)); + } + + return 0; +} + +static int cx8802_check_driver(struct cx8802_driver *drv) +{ + if (drv == NULL) + return -ENODEV; + + if ((drv->type_id != CX88_MPEG_DVB) && + (drv->type_id != CX88_MPEG_BLACKBIRD)) + return -EINVAL; + + if ((drv->hw_access != CX8802_DRVCTL_SHARED) && + (drv->hw_access != CX8802_DRVCTL_EXCLUSIVE)) + return -EINVAL; + + if ((drv->probe == NULL) || + (drv->remove == NULL) || + (drv->advise_acquire == NULL) || + (drv->advise_release == NULL)) + return -EINVAL; + + return 0; +} + +int cx8802_register_driver(struct cx8802_driver *drv) +{ + struct cx8802_dev *dev; + struct cx8802_driver *driver; + int err, i = 0; + + printk(KERN_INFO + "cx88/2: registering cx8802 driver, type: %s access: %s\n", + drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird", + drv->hw_access == CX8802_DRVCTL_SHARED ? "shared" : "exclusive"); + + if ((err = cx8802_check_driver(drv)) != 0) { + printk(KERN_ERR "cx88/2: cx8802_driver is invalid\n"); + return err; + } + + mutex_lock(&cx8802_mutex); + + list_for_each_entry(dev, &cx8802_devlist, devlist) { + printk(KERN_INFO + "%s/2: subsystem: %04x:%04x, board: %s [card=%d]\n", + dev->core->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device, dev->core->board.name, + dev->core->boardnr); + + /* Bring up a new struct for each driver instance */ + driver = kzalloc(sizeof(*drv),GFP_KERNEL); + if (driver == NULL) { + 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 { + printk(KERN_ERR + "%s/2: cx8802 probe failed, err = %d\n", + dev->core->name, err); + } + mutex_unlock(&drv->core->lock); + } + + err = i ? 0 : -ENODEV; +out: + mutex_unlock(&cx8802_mutex); + return err; +} + +int cx8802_unregister_driver(struct cx8802_driver *drv) +{ + struct cx8802_dev *dev; + struct cx8802_driver *d, *dtmp; + int err = 0; + + printk(KERN_INFO + "cx88/2: unregistering cx8802 driver, type: %s access: %s\n", + drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird", + drv->hw_access == CX8802_DRVCTL_SHARED ? "shared" : "exclusive"); + + mutex_lock(&cx8802_mutex); + + list_for_each_entry(dev, &cx8802_devlist, devlist) { + printk(KERN_INFO + "%s/2: subsystem: %04x:%04x, board: %s [card=%d]\n", + dev->core->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device, dev->core->board.name, + dev->core->boardnr); + + 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 + printk(KERN_ERR "%s/2: cx8802 driver remove " + "failed (%d)\n", dev->core->name, err); + } + + mutex_unlock(&dev->core->lock); + } + + mutex_unlock(&cx8802_mutex); + + return err; +} + +/* ----------------------------------------------------------- */ +static int __devinit cx8802_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8802_dev *dev; + struct cx88_core *core; + int err; + + /* general setup */ + core = cx88_core_get(pci_dev); + if (NULL == core) + return -EINVAL; + + printk("%s/2: cx2388x 8802 Driver Manager\n", core->name); + + err = -ENODEV; + if (!core->board.mpeg) + goto fail_core; + + err = -ENOMEM; + dev = kzalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == 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_free; + + 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_free: + kfree(dev); + fail_core: + core->dvbdev = NULL; + cx88_core_put(core,pci_dev); + return err; +} + +static void __devexit cx8802_remove(struct pci_dev *pci_dev) +{ + struct cx8802_dev *dev; + + dev = pci_get_drvdata(pci_dev); + + dprintk( 1, "%s\n", __func__); + + flush_request_modules(dev); + + mutex_lock(&dev->core->lock); + + if (!list_empty(&dev->drvlist)) { + struct cx8802_driver *drv, *tmp; + int err; + + printk(KERN_WARNING "%s/2: Trying to remove cx8802 driver " + "while cx8802 sub-drivers still loaded?!\n", + dev->core->name); + + list_for_each_entry_safe(drv, tmp, &dev->drvlist, drvlist) { + err = drv->remove(drv); + if (err == 0) { + list_del(&drv->drvlist); + } else + printk(KERN_ERR "%s/2: cx8802 driver remove " + "failed (%d)\n", dev->core->name, 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 = __devexit_p(cx8802_remove), +}; + +static int __init cx8802_init(void) +{ + printk(KERN_INFO "cx88/2: cx2388x MPEG-TS Driver Manager version %s loaded\n", + CX88_VERSION); + return pci_register_driver(&cx8802_pci_driver); +} + +static void __exit cx8802_fini(void) +{ + pci_unregister_driver(&cx8802_pci_driver); +} + +module_init(cx8802_init); +module_exit(cx8802_fini); +EXPORT_SYMBOL(cx8802_buf_prepare); +EXPORT_SYMBOL(cx8802_buf_queue); +EXPORT_SYMBOL(cx8802_cancel_buffers); + +EXPORT_SYMBOL(cx8802_register_driver); +EXPORT_SYMBOL(cx8802_unregister_driver); +EXPORT_SYMBOL(cx8802_get_driver); +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + * kate: eol "unix"; indent-width 3; remove-trailing-space on; replace-trailing-space-save on; tab-width 8; replace-tabs off; space-indent off; mixed-indent off + */ diff --git a/drivers/media/pci/cx88/cx88-reg.h b/drivers/media/pci/cx88/cx88-reg.h new file mode 100644 index 00000000000..2ec52d1cdea --- /dev/null +++ b/drivers/media/pci/cx88/cx88-reg.h @@ -0,0 +1,836 @@ +/* + + 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> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#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 + +typedef enum +{ + SOURCE_TUNER = 0, + SOURCE_COMPOSITE, + SOURCE_SVIDEO, + SOURCE_OTHER1, + SOURCE_OTHER2, + SOURCE_COMPVIASVIDEO, + SOURCE_CCIR656 +} VIDEOSOURCETYPE; + +#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 00000000000..424fd97495d --- /dev/null +++ b/drivers/media/pci/cx88/cx88-tvaudio.c @@ -0,0 +1,1059 @@ +/* + + 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 ... + + ----------------------------------------------------------------------- + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/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> + +#include "cx88.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...) if (audio_debug) \ + printk(KERN_DEBUG "%s/0: " fmt, core->name , ## arg) + +/* ----------------------------------------------------------- */ + +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; + break; + } + + 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 */ + msleep(10); + } + + 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 (0 == cx88_detect_nicam(core)) { + /* 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: + printk("%s/0: unknown tv audio mode [%d]\n", + core->name, core->tvaudio); + break; + } + return; +} + +void cx88_newstation(struct cx88_core *core) +{ + core->audiomode_manual = UNSET; + core->last_change = jiffies; +} + +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 (UNSET == t->rxsubchans) { + t->rxsubchans = V4L2_TUNER_SUB_MONO; + /* If the hardware itself detected stereo, also return + stereo as an available subchannel */ + if (V4L2_TUNER_MODE_STEREO == t->audmode) + t->rxsubchans |= V4L2_TUNER_SUB_STEREO; + } + return; +} + +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 (UNSET != core->audiomode_manual) + 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 (1 == core->use_nicam) { + 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 (UNSET != ctl) { + 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); + } + return; +} + +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 (UNSET != core->audiomode_manual) + /* 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_set_tvaudio); +EXPORT_SYMBOL(cx88_newstation); +EXPORT_SYMBOL(cx88_set_stereo); +EXPORT_SYMBOL(cx88_get_stereo); +EXPORT_SYMBOL(cx88_audio_thread); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + * kate: eol "unix"; indent-width 3; remove-trailing-space on; replace-trailing-space-save on; tab-width 8; replace-tabs off; space-indent off; mixed-indent off + */ diff --git a/drivers/media/pci/cx88/cx88-vbi.c b/drivers/media/pci/cx88/cx88-vbi.c new file mode 100644 index 00000000000..f8f8389c036 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-vbi.c @@ -0,0 +1,245 @@ +/* + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> + +#include "cx88.h" + +static unsigned int vbibufs = 4; +module_param(vbibufs,int,0644); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32"); + +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...) if (vbi_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, dev->core->name , ## arg) + +/* ------------------------------------------------------------------ */ + +int cx8800_vbi_fmt (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + + f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + f->fmt.vbi.count[0] = VBI_LINE_COUNT; + f->fmt.vbi.count[1] = VBI_LINE_COUNT; + + 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; + + } else if (dev->core->tvnorm & V4L2_STD_625_50) { + /* pal */ + f->fmt.vbi.sampling_rate = 35468950; + f->fmt.vbi.start[0] = 7 -1; + f->fmt.vbi.start[1] = 319 -1; + } + 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], + buf->vb.width, 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 = 1; + + /* 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; +} + +int 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); + return 0; +} + +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, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + cx8800_start_vbi_dma(dev, q, buf); + list_for_each_entry(buf, &q->active, vb.queue) + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +void cx8800_vbi_timeout(unsigned long data) +{ + struct cx8800_dev *dev = (struct cx8800_dev*)data; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vbiq; + struct cx88_buffer *buf; + unsigned long flags; + + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH24]); + + cx_clear(MO_VID_DMACNTRL, 0x88); + cx_clear(VID_CAPTURE_CONTROL, 0x18); + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + printk("%s/0: [%p/%d] timeout - dma=0x%08lx\n", dev->core->name, + buf, buf->vb.i, (unsigned long)buf->risc.dma); + } + cx8800_restart_vbi_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +/* ------------------------------------------------------------------ */ + +static int +vbi_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + *size = VBI_LINE_COUNT * VBI_LINE_LENGTH * 2; + if (0 == *count) + *count = vbibufs; + if (*count < 2) + *count = 2; + if (*count > 32) + *count = 32; + return 0; +} + +static int +vbi_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + unsigned int size; + int rc; + + size = VBI_LINE_COUNT * VBI_LINE_LENGTH * 2; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + buf->vb.width = VBI_LINE_LENGTH; + buf->vb.height = VBI_LINE_COUNT; + buf->vb.size = size; + buf->vb.field = V4L2_FIELD_SEQ_TB; + + if (0 != (rc = videobuf_iolock(q,&buf->vb,NULL))) + goto fail; + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, buf->vb.width * buf->vb.height, + buf->vb.width, 0, + buf->vb.height); + } + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + + fail: + cx88_free_buffer(q,buf); + return rc; +} + +static void +vbi_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx88_buffer *prev; + struct cx8800_fh *fh = vq->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_dmaqueue *q = &dev->vbiq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + cx8800_start_vbi_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] vbi_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + } +} + +static void vbi_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + + cx88_free_buffer(q,buf); +} + +const struct videobuf_queue_ops cx8800_vbi_qops = { + .buf_setup = vbi_setup, + .buf_prepare = vbi_prepare, + .buf_queue = vbi_queue, + .buf_release = vbi_release, +}; + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/pci/cx88/cx88-video.c b/drivers/media/pci/cx88/cx88-video.c new file mode 100644 index 00000000000..05171457bf2 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-video.c @@ -0,0 +1,2075 @@ +/* + * + * 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@infradead.org> + * - Multituner support + * - video_ioctl2 conversion + * - PAL/M fixes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/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 "cx88.h" +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/wm8775.h> + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); +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]"); + +static unsigned int vid_limit = 16; +module_param(vid_limit,int,0644); +MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes"); + +#define dprintk(level,fmt, arg...) if (video_debug >= level) \ + printk(KERN_DEBUG "%s/0: " fmt, core->name , ## arg) + +/* ------------------------------------------------------------------- */ +/* static data */ + +static const struct cx8800_fmt formats[] = { + { + .name = "8 bpp, gray", + .fourcc = V4L2_PIX_FMT_GREY, + .cxformat = ColorFormatY8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .cxformat = ColorFormatRGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .cxformat = ColorFormatRGB15 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .cxformat = ColorFormatRGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .cxformat = ColorFormatRGB16 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .cxformat = ColorFormatRGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .cxformat = ColorFormatRGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .cxformat = ColorFormatRGB32 | ColorFormatBSWAP | ColorFormatWSWAP, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .cxformat = ColorFormatYUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, UYVY", + .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), +}; + +/* ------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bit) +{ + struct cx88_core *core = dev->core; + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + mutex_lock(&core->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + mutex_unlock(&core->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk(1,"res: get %d\n",bit); + mutex_unlock(&core->lock); + return 1; +} + +static +int res_check(struct cx8800_fh *fh, unsigned int bit) +{ + return (fh->resources & bit); +} + +static +int res_locked(struct cx8800_dev *dev, unsigned int bit) +{ + return (dev->resources & bit); +} + +static +void res_free(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bits) +{ + struct cx88_core *core = dev->core; + BUG_ON((fh->resources & bits) != bits); + + mutex_lock(&core->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk(1,"res: put %d\n",bits); + mutex_unlock(&core->lock); +} + +/* ------------------------------------------------------------------ */ + +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->board.audio_chip && + core->board.audio_chip == V4L2_IDENT_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, buf->vb.width, buf->vb.height, buf->vb.field); + cx_write(MO_COLOR_CTRL, buf->fmt->cxformat | ColorFormatGamma); + + /* reset counter */ + cx_write(MO_VIDY_GPCNTRL,GP_COUNT_CONTROL_RESET); + q->count = 1; + + /* 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; +} + +#ifdef CONFIG_PM +static int 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; +} +#endif + +static int restart_video_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_core *core = dev->core; + struct cx88_buffer *buf, *prev; + + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + start_video_dma(dev, q, buf); + list_for_each_entry(buf, &q->active, vb.queue) + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&q->queued)) + return 0; + buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue); + if (NULL == prev) { + list_move_tail(&buf->vb.queue, &q->active); + start_video_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] restart_queue - first active\n", + buf,buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_move_tail(&buf->vb.queue, &q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] restart_queue - move to active\n", + buf,buf->vb.i); + } else { + return 0; + } + prev = buf; + } +} + +/* ------------------------------------------------------------------ */ + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + + *size = dev->fmt->depth * dev->width * dev->height >> 3; + if (0 == *count) + *count = 32; + if (*size * *count > vid_limit * 1024 * 1024) + *count = (vid_limit * 1024 * 1024) / *size; + return 0; +} + +static int +buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + int rc, init_buffer = 0; + + BUG_ON(NULL == dev->fmt); + if (dev->width < 48 || dev->width > norm_maxw(core->tvnorm) || + dev->height < 32 || dev->height > norm_maxh(core->tvnorm)) + return -EINVAL; + buf->vb.size = (dev->width * dev->height * dev->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != dev->fmt || + buf->vb.width != dev->width || + buf->vb.height != dev->height || + buf->vb.field != field) { + buf->fmt = dev->fmt; + buf->vb.width = dev->width; + buf->vb.height = dev->height; + buf->vb.field = field; + init_buffer = 1; + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + init_buffer = 1; + if (0 != (rc = videobuf_iolock(q,&buf->vb,NULL))) + goto fail; + } + + if (init_buffer) { + buf->bpl = buf->vb.width * buf->fmt->depth >> 3; + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, 0, UNSET, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, UNSET, 0, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, 0, buf->bpl, + buf->bpl, buf->bpl, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, buf->bpl * (buf->vb.height >> 1), + buf->bpl, 0, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + buf->bpl * (buf->vb.height >> 1), 0, + buf->bpl, 0, + buf->vb.height >> 1); + break; + default: + BUG(); + } + } + dprintk(2,"[%p/%d] buffer_prepare - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + buf, buf->vb.i, + dev->width, dev->height, dev->fmt->depth, dev->fmt->name, + (unsigned long)buf->risc.dma); + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + + fail: + cx88_free_buffer(q,buf); + return rc; +} + +static void +buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx88_buffer *prev; + struct cx8800_fh *fh = vq->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vidq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + + } else if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + start_video_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + + } else { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + + cx88_free_buffer(q,buf); +} + +static const struct videobuf_queue_ops cx8800_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + + +/* ------------------------------------------------------------------ */ + +static struct videobuf_queue *get_queue(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = file->private_data; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + return &fh->vidq; + case VFL_TYPE_VBI: + return &fh->vbiq; + default: + BUG(); + return NULL; + } +} + +static int get_resource(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + return RESOURCE_VIDEO; + case VFL_TYPE_VBI: + return RESOURCE_VBI; + default: + BUG(); + return 0; + } +} + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + struct cx8800_fh *fh; + enum v4l2_buf_type type = 0; + int radio = 0; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + case VFL_TYPE_VBI: + type = V4L2_BUF_TYPE_VBI_CAPTURE; + break; + case VFL_TYPE_RADIO: + radio = 1; + break; + } + + dprintk(1, "open dev=%s radio=%d type=%s\n", + video_device_node_name(vdev), radio, v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (unlikely(!fh)) + return -ENOMEM; + + v4l2_fh_init(&fh->fh, vdev); + file->private_data = fh; + fh->dev = dev; + + mutex_lock(&core->lock); + + videobuf_queue_sg_init(&fh->vidq, &cx8800_video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx88_buffer), + fh, NULL); + videobuf_queue_sg_init(&fh->vbiq, &cx8800_vbi_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct cx88_buffer), + fh, NULL); + + if (vdev->vfl_type == VFL_TYPE_RADIO) { + dprintk(1,"video_open: setting radio device\n"); + 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->board.audio_chip && + core->board.audio_chip == V4L2_IDENT_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); + } + + core->users++; + mutex_unlock(&core->lock); + v4l2_fh_add(&fh->fh); + + return 0; +} + +static ssize_t +video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = file->private_data; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(&fh->vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); + case VFL_TYPE_VBI: + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(&fh->vbiq, data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = file->private_data; + struct cx88_buffer *buf; + unsigned int rc = v4l2_ctrl_poll(file, wait); + + if (vdev->vfl_type == VFL_TYPE_VBI) { + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return rc | POLLERR; + return rc | videobuf_poll_stream(file, &fh->vbiq, wait); + } + mutex_lock(&fh->vidq.vb_lock); + if (res_check(fh,RESOURCE_VIDEO)) { + /* streaming capture */ + if (list_empty(&fh->vidq.stream)) + goto done; + buf = list_entry(fh->vidq.stream.next,struct cx88_buffer,vb.stream); + } else { + /* read() capture */ + buf = (struct cx88_buffer*)fh->vidq.read_buf; + if (NULL == buf) + goto done; + } + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + rc |= POLLIN|POLLRDNORM; +done: + mutex_unlock(&fh->vidq.vb_lock); + return rc; +} + +static int video_release(struct file *file) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + /* FIXME */ + res_free(dev,fh,RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_queue_cancel(&fh->vidq); + res_free(dev,fh,RESOURCE_VIDEO); + } + if (fh->vidq.read_buf) { + buffer_release(&fh->vidq,fh->vidq.read_buf); + kfree(fh->vidq.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + videobuf_stop(&fh->vbiq); + res_free(dev,fh,RESOURCE_VBI); + } + + videobuf_mmap_free(&fh->vidq); + videobuf_mmap_free(&fh->vbiq); + + mutex_lock(&dev->core->lock); + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + file->private_data = NULL; + kfree(fh); + + dev->core->users--; + if (!dev->core->users) + call_all(dev->core, core, s_power, 0); + mutex_unlock(&dev->core->lock); + + return 0; +} + +static int +video_mmap(struct file *file, struct vm_area_struct * vma) +{ + return videobuf_mmap_mapper(get_queue(file), vma); +} + +/* ------------------------------------------------------------------ */ +/* 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->board.audio_chip == V4L2_IDENT_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_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = fh->vidq.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 cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + const struct cx8800_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = norm_maxw(core->tvnorm); + maxh = norm_maxh(core->tvnorm); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2, + &f->fmt.pix.height, 32, maxh, 0, 0); + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + int err = vidioc_try_fmt_vid_cap (file,priv,f); + + if (0 != err) + return err; + dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + fh->vidq.field = f->fmt.pix.field; + return 0; +} + +void cx88_querycap(struct file *file, struct cx88_core *core, + struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + + strlcpy(cap->card, core->board.name, sizeof(cap->card)); + cap->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + if (UNSET != core->board.tuner_type) + cap->device_caps |= V4L2_CAP_TUNER; + switch (vdev->vfl_type) { + case VFL_TYPE_RADIO: + cap->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + break; + case VFL_TYPE_GRABBER: + cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE; + break; + case VFL_TYPE_VBI: + cap->device_caps |= V4L2_CAP_VBI_CAPTURE; + break; + } + cap->capabilities = cap->device_caps | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VBI_CAPTURE | V4L2_CAP_DEVICE_CAPS; + if (core->board.radio.type == CX88_RADIO) + cap->capabilities |= V4L2_CAP_RADIO; +} +EXPORT_SYMBOL(cx88_querycap); + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx8800_dev *dev = ((struct cx8800_fh *)priv)->dev; + struct cx88_core *core = dev->core; + + strcpy(cap->driver, "cx8800"); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cx88_querycap(file, core, cap); + return 0; +} + +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; + + strlcpy(f->description,formats[f->index].name,sizeof(f->description)); + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int vidioc_reqbufs (struct file *file, void *priv, struct v4l2_requestbuffers *p) +{ + return videobuf_reqbufs(get_queue(file), p); +} + +static int vidioc_querybuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + return videobuf_querybuf(get_queue(file), p); +} + +static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + return videobuf_qbuf(get_queue(file), p); +} + +static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + return videobuf_dqbuf(get_queue(file), p, + file->f_flags & O_NONBLOCK); +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + + if ((vdev->vfl_type == VFL_TYPE_GRABBER && i != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (vdev->vfl_type == VFL_TYPE_VBI && i != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + if (unlikely(!res_get(dev, fh, get_resource(file)))) + return -EBUSY; + return videobuf_streamon(get_queue(file)); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + int err, res; + + if ((vdev->vfl_type == VFL_TYPE_GRABBER && i != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (vdev->vfl_type == VFL_TYPE_VBI && i != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + res = get_resource(file); + err = videobuf_streamoff(get_queue(file)); + if (err < 0) + return err; + res_free(dev,fh,res); + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + *tvnorm = core->tvnorm; + return 0; +} + +static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *tvnorms) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + mutex_lock(&core->lock); + cx88_set_tvnorm(core,*tvnorms); + mutex_unlock(&core->lock); + + return 0; +} + +/* 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 (0 == INPUT(n).type) + return -EINVAL; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,iname[INPUT(n).type]); + if ((CX88_VMUX_TELEVISION == INPUT(n).type) || + (CX88_VMUX_CABLE == INPUT(n).type)) { + 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 cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + return cx88_enum_input (core,i); +} + +static int vidioc_g_input (struct file *file, void *priv, unsigned int *i) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + *i = core->input; + return 0; +} + +static int vidioc_s_input (struct file *file, void *priv, unsigned int i) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (i >= 4) + return -EINVAL; + if (0 == INPUT(i).type) + return -EINVAL; + + mutex_lock(&core->lock); + cx88_newstation(core); + cx88_video_mux(core,i); + mutex_unlock(&core->lock); + return 0; +} + +static int vidioc_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + u32 reg; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + strcpy(t->name, "Television"); + 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, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (UNSET == core->board.tuner_type) + return -EINVAL; + if (0 != t->index) + 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_fh *fh = priv; + struct cx88_core *core = fh->dev->core; + + if (unlikely(UNSET == core->board.tuner_type)) + 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, + struct v4l2_frequency *f) +{ + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + + mutex_lock(&core->lock); + cx88_newstation(core); + call_all(core, tuner, s_frequency, f); + call_all(core, tuner, g_frequency, f); + core->freq = f->frequency; + + /* When changing channels it is required to reset TVAUDIO */ + msleep (10); + cx88_set_tvaudio(core); + + mutex_unlock(&core->lock); + + return 0; +} +EXPORT_SYMBOL(cx88_set_freq); + +static int vidioc_s_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8800_fh *fh = priv; + struct cx88_core *core = fh->dev->core; + + return cx88_set_freq(core, f); +} + +static int vidioc_g_chip_ident(struct file *file, void *priv, + struct v4l2_dbg_chip_ident *chip) +{ + if (!v4l2_chip_match_host(&chip->match)) + return -EINVAL; + chip->revision = 0; + chip->ident = V4L2_IDENT_UNKNOWN; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register (struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + /* cx2388x has a 24-bit register space */ + reg->val = cx_read(reg->reg & 0xffffff); + reg->size = 4; + return 0; +} + +static int vidioc_s_register (struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + cx_write(reg->reg & 0xffffff, reg->val); + return 0; +} +#endif + +/* ----------------------------------------------------------- */ +/* RADIO ESPECIFIC IOCTLS */ +/* ----------------------------------------------------------- */ + +static int radio_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (unlikely(t->index > 0)) + return -EINVAL; + + strcpy(t->name, "Radio"); + + call_all(core, tuner, g_tuner, t); + return 0; +} + +/* FIXME: Should add a standard for radio */ + +static int radio_s_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (0 != t->index) + return -EINVAL; + if (t->audmode > V4L2_TUNER_MODE_STEREO) + t->audmode = V4L2_TUNER_MODE_STEREO; + + call_all(core, tuner, s_tuner, t); + + return 0; +} + +/* ----------------------------------------------------------- */ + +static void cx8800_vid_timeout(unsigned long data) +{ + struct cx8800_dev *dev = (struct cx8800_dev*)data; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vidq; + struct cx88_buffer *buf; + unsigned long flags; + + cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]); + + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + printk("%s/0: [%p/%d] timeout - dma=0x%08lx\n", core->name, + buf, buf->vb.i, (unsigned long)buf->risc.dma); + } + restart_video_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +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(core->name, "irq vid", + cx88_vid_irqs, ARRAY_SIZE(cx88_vid_irqs), + status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + printk(KERN_WARNING "%s/0: video risc op code error\n",core->name); + 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); + } + + /* risc2 y */ + if (status & 0x10) { + dprintk(2,"stopper video\n"); + spin_lock(&dev->slock); + restart_video_queue(dev,&dev->vidq); + spin_unlock(&dev->slock); + } + + /* risc2 vbi */ + if (status & 0x80) { + dprintk(2,"stopper vbi\n"); + spin_lock(&dev->slock); + cx8800_restart_vbi_queue(dev,&dev->vbiq); + 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 (0 == status) + 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 (10 == loop) { + printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n", + core->name); + cx_write(MO_PCI_INTMSK,0); + } + + out: + return IRQ_RETVAL(handled); +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static const struct v4l2_file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_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 = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_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 = vidioc_streamon, + .vidioc_streamoff = vidioc_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, + .vidioc_g_chip_ident = vidioc_g_chip_ident, +#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 = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_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 = vidioc_streamon, + .vidioc_streamoff = vidioc_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_g_chip_ident = vidioc_g_chip_ident, +#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 = video_open, + .poll = v4l2_ctrl_poll, + .release = video_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, + .vidioc_g_chip_ident = vidioc_g_chip_ident, +#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) +{ + if (dev->radio_dev) { + if (video_is_registered(dev->radio_dev)) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } + if (dev->vbi_dev) { + if (video_is_registered(dev->vbi_dev)) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->video_dev) { + if (video_is_registered(dev->video_dev)) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } +} + +static int __devinit cx8800_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8800_dev *dev; + struct cx88_core *core; + int err; + int i; + + dev = kzalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == 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 (NULL == core) { + err = -EINVAL; + goto fail_free; + } + dev->core = core; + + /* print pci info */ + dev->pci_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s/0: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%llx\n", core->name, + 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); + if (!pci_dma_supported(pci_dev,DMA_BIT_MASK(32))) { + printk("%s/0: Oops: no 32bit PCI DMA ???\n",core->name); + err = -EIO; + goto fail_core; + } + + /* initialize driver struct */ + spin_lock_init(&dev->slock); + core->tvnorm = V4L2_STD_NTSC_M; + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + INIT_LIST_HEAD(&dev->vidq.queued); + dev->vidq.timeout.function = cx8800_vid_timeout; + dev->vidq.timeout.data = (unsigned long)dev; + init_timer(&dev->vidq.timeout); + cx88_risc_stopper(dev->pci,&dev->vidq.stopper, + MO_VID_DMACNTRL,0x11,0x00); + + /* init vbi dma queues */ + INIT_LIST_HEAD(&dev->vbiq.active); + INIT_LIST_HEAD(&dev->vbiq.queued); + dev->vbiq.timeout.function = cx8800_vbi_timeout; + dev->vbiq.timeout.data = (unsigned long)dev; + init_timer(&dev->vbiq.timeout); + cx88_risc_stopper(dev->pci,&dev->vbiq.stopper, + MO_VID_DMACNTRL,0x88,0x00); + + /* get irq */ + err = request_irq(pci_dev->irq, cx8800_irq, + IRQF_SHARED | IRQF_DISABLED, core->name, dev); + if (err < 0) { + printk(KERN_ERR "%s/0: can't get IRQ %d\n", + core->name,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 == NULL) { + err = core->audio_hdl.error; + goto fail_core; + } + 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 == NULL) { + err = core->video_hdl.error; + goto fail_core; + } + 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); + + /* load and configure helper modules */ + + if (core->board.audio_chip == V4L2_IDENT_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 != NULL) { + core->sd_wm8775 = sd; + sd->grp_id = WM8775_GID; + } + } + + if (core->board.audio_chip == V4L2_IDENT_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_device(&core->i2c_adap, &rtc_info); + } + /* break intentionally omitted */ + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + request_module("ir-kbd-i2c"); + } + + /* Sets device info at pci_dev */ + pci_set_drvdata(pci_dev, dev); + + dev->width = 320; + dev->height = 240; + dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + + /* initial device configuration */ + mutex_lock(&core->lock); + cx88_set_tvnorm(core, core->tvnorm); + v4l2_ctrl_handler_setup(&core->video_hdl); + v4l2_ctrl_handler_setup(&core->audio_hdl); + cx88_video_mux(core, 0); + + /* register v4l devices */ + dev->video_dev = cx88_vdev_init(core,dev->pci, + &cx8800_video_template,"video"); + video_set_drvdata(dev->video_dev, dev); + dev->video_dev->ctrl_handler = &core->video_hdl; + err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER, + video_nr[core->nr]); + if (err < 0) { + printk(KERN_ERR "%s/0: can't register video device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device %s [v4l2]\n", + core->name, video_device_node_name(dev->video_dev)); + + dev->vbi_dev = cx88_vdev_init(core,dev->pci,&cx8800_vbi_template,"vbi"); + video_set_drvdata(dev->vbi_dev, dev); + err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, + vbi_nr[core->nr]); + if (err < 0) { + printk(KERN_ERR "%s/0: can't register vbi device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device %s\n", + core->name, video_device_node_name(dev->vbi_dev)); + + if (core->board.radio.type == CX88_RADIO) { + dev->radio_dev = cx88_vdev_init(core,dev->pci, + &cx8800_radio_template,"radio"); + video_set_drvdata(dev->radio_dev, dev); + dev->radio_dev->ctrl_handler = &core->audio_hdl; + err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, + radio_nr[core->nr]); + if (err < 0) { + printk(KERN_ERR "%s/0: can't register radio device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device %s\n", + core->name, video_device_node_name(dev->radio_dev)); + } + + /* start tvaudio thread */ + if (core->board.tuner_type != TUNER_ABSENT) { + core->kthread = kthread_run(cx88_audio_thread, core, "cx88 tvaudio"); + if (IS_ERR(core->kthread)) { + err = PTR_ERR(core->kthread); + printk(KERN_ERR "%s/0: failed to create cx88 audio thread, err=%d\n", + core->name, err); + } + } + mutex_unlock(&core->lock); + + return 0; + +fail_unreg: + cx8800_unregister_video(dev); + free_irq(pci_dev->irq, dev); + mutex_unlock(&core->lock); +fail_core: + cx88_core_put(core,dev->pci); +fail_free: + kfree(dev); + return err; +} + +static void __devexit 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 */ + pci_disable_device(pci_dev); + + /* unregister stuff */ + + free_irq(pci_dev->irq, dev); + cx8800_unregister_video(dev); + pci_set_drvdata(pci_dev, NULL); + + /* free memory */ + btcx_riscmem_free(dev->pci,&dev->vidq.stopper); + cx88_core_put(core,dev->pci); + kfree(dev); +} + +#ifdef CONFIG_PM +static int cx8800_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + /* stop video+vbi capture */ + spin_lock(&dev->slock); + if (!list_empty(&dev->vidq.active)) { + printk("%s/0: suspend video\n", core->name); + stop_video_dma(dev); + del_timer(&dev->vidq.timeout); + } + if (!list_empty(&dev->vbiq.active)) { + printk("%s/0: suspend vbi\n", core->name); + cx8800_stop_vbi_dma(dev); + del_timer(&dev->vbiq.timeout); + } + spin_unlock(&dev->slock); + + if (core->ir) + cx88_ir_stop(core); + /* FIXME -- shutdown device */ + cx88_shutdown(core); + + pci_save_state(pci_dev); + if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { + pci_disable_device(pci_dev); + dev->state.disabled = 1; + } + return 0; +} + +static int cx8800_resume(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + int err; + + if (dev->state.disabled) { + err=pci_enable_device(pci_dev); + if (err) { + printk(KERN_ERR "%s/0: can't enable device\n", + core->name); + return err; + } + + dev->state.disabled = 0; + } + err= pci_set_power_state(pci_dev, PCI_D0); + if (err) { + printk(KERN_ERR "%s/0: can't set power state\n", core->name); + pci_disable_device(pci_dev); + dev->state.disabled = 1; + + return err; + } + pci_restore_state(pci_dev); + + /* 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(&dev->slock); + if (!list_empty(&dev->vidq.active)) { + printk("%s/0: resume video\n", core->name); + restart_video_queue(dev,&dev->vidq); + } + if (!list_empty(&dev->vbiq.active)) { + printk("%s/0: resume vbi\n", core->name); + cx8800_restart_vbi_queue(dev,&dev->vbiq); + } + spin_unlock(&dev->slock); + + return 0; +} +#endif + +/* ----------------------------------------------------------- */ + +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 struct pci_driver cx8800_pci_driver = { + .name = "cx8800", + .id_table = cx8800_pci_tbl, + .probe = cx8800_initdev, + .remove = __devexit_p(cx8800_finidev), +#ifdef CONFIG_PM + .suspend = cx8800_suspend, + .resume = cx8800_resume, +#endif +}; + +static int __init cx8800_init(void) +{ + printk(KERN_INFO "cx88/0: cx2388x v4l2 driver version %s loaded\n", + CX88_VERSION); + return pci_register_driver(&cx8800_pci_driver); +} + +static void __exit cx8800_fini(void) +{ + pci_unregister_driver(&cx8800_pci_driver); +} + +module_init(cx8800_init); +module_exit(cx8800_fini); 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 00000000000..d77f8ecab9d --- /dev/null +++ b/drivers/media/pci/cx88/cx88-vp3054-i2c.c @@ -0,0 +1,159 @@ +/* + + 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> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> + +#include <asm/io.h> + +#include "cx88.h" +#include "cx88-vp3054-i2c.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 == NULL) + return -ENOMEM; + dev->vp3054 = vp3054_i2c; + + memcpy(&vp3054_i2c->algo, &vp3054_i2c_algo_template, + sizeof(vp3054_i2c->algo)); + + vp3054_i2c->adap.dev.parent = &dev->pci->dev; + strlcpy(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 (0 != rc) { + printk("%s: vp3054_i2c register FAILED\n", core->name); + + kfree(dev->vp3054); + dev->vp3054 = NULL; + } + + return rc; +} + +void vp3054_i2c_remove(struct cx8802_dev *dev) +{ + struct vp3054_i2c_state *vp3054_i2c = dev->vp3054; + + if (vp3054_i2c == NULL || + dev->core->boardnr != CX88_BOARD_DNTV_LIVE_DVB_T_PRO) + return; + + i2c_del_adapter(&vp3054_i2c->adap); + kfree(vp3054_i2c); +} + +EXPORT_SYMBOL(vp3054_i2c_probe); +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 00000000000..be99c931dc3 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-vp3054-i2c.h @@ -0,0 +1,41 @@ +/* + + 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> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* ----------------------------------------------------------------------- */ +struct vp3054_i2c_state { + struct i2c_adapter adap; + struct i2c_algo_bit_data algo; + u32 state; +}; + +/* ----------------------------------------------------------------------- */ +#if defined(CONFIG_VIDEO_CX88_VP3054) || (defined(CONFIG_VIDEO_CX88_VP3054_MODULE) && defined(MODULE)) +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 00000000000..44ffc8b3d45 --- /dev/null +++ b/drivers/media/pci/cx88/cx88.h @@ -0,0 +1,748 @@ +/* + * + * v4l2 device driver for cx2388x based TV cards + * + * (c) 2003,04 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev2.h> +#include <linux/kdev_t.h> + +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> +#include <media/tuner.h> +#include <media/tveeprom.h> +#include <media/videobuf-dma-sg.h> +#include <media/v4l2-chip-ident.h> +#include <media/cx2341x.h> +#include <media/videobuf-dvb.h> +#include <media/ir-kbd-i2c.h> +#include <media/wm8775.h> + +#include "btcx-risc.h" +#include "cx88-reg.h" +#include "tuner-xc2028.h" + +#include <linux/mutex.h> + +#define CX88_VERSION "0.0.9" + +#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_COUNT 17 +#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 unsigned int inline norm_maxw(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 720 : 768; +} + + +static unsigned int inline norm_maxh(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 576 : 480; +} + +/* ----------------------------------------------------------- */ +/* static data */ + +struct cx8800_fmt { + const char *name; + 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 + +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; +}; + +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; + unsigned int 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) + +/* buffer for one video frame */ +struct cx88_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* cx88 specific */ + unsigned int bpl; + struct btcx_riscmem risc; + const struct cx8800_fmt *fmt; + u32 count; +}; + +struct cx88_dmaqueue { + struct list_head active; + struct list_head queued; + struct timer_list timeout; + struct btcx_riscmem stopper; + u32 count; +}; + +struct cx88_core { + struct list_head devlist; + atomic_t refcount; + + /* board name */ + int nr; + char name[32]; + + /* 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 defined(CONFIG_VIDEO_CX88_DVB) || defined(CONFIG_VIDEO_CX88_DVB_MODULE) + int (*prev_set_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage); +#endif + void (*gate_ctrl)(struct cx88_core *core, int open); + + /* state info */ + struct task_struct *kthread; + v4l2_std_id tvnorm; + 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; + int users; + int mpeg_users; + + /* cx88-video needs to access cx8802 for hybrid tuner pll access. */ + struct cx8802_dev *dvbdev; + 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; \ + }) + +struct cx8800_dev; +struct cx8802_dev; + +/* ----------------------------------------------------------- */ +/* function 0: video stuff */ + +struct cx8800_fh { + struct v4l2_fh fh; + struct cx8800_dev *dev; + unsigned int resources; + + /* video capture */ + struct videobuf_queue vidq; + + /* vbi capture */ + struct videobuf_queue vbiq; +}; + +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; + unsigned int width, height; + + /* capture queues */ + struct cx88_dmaqueue vidq; + struct cx88_dmaqueue 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_fh { + struct v4l2_fh fh; + struct cx8802_dev *dev; + struct videobuf_queue mpegq; +}; + +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; + 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 defined(CONFIG_VIDEO_CX88_BLACKBIRD) || \ + defined(CONFIG_VIDEO_CX88_BLACKBIRD_MODULE) + struct video_device *mpeg_dev; + u32 mailbox; + int width; + int height; + unsigned char mpeg_active; /* nonzero if mpeg encoder is active */ + + /* mpeg params */ + struct cx2341x_handler cxhdl; +#endif + +#if defined(CONFIG_VIDEO_CX88_DVB) || defined(CONFIG_VIDEO_CX88_DVB_MODULE) + /* for dvb only */ + struct videobuf_dvb_frontends frontends; +#endif + +#if defined(CONFIG_VIDEO_CX88_VP3054) || \ + defined(CONFIG_VIDEO_CX88_VP3054_MODULE) + /* 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 void cx88_print_irqbits(const char *name, const char *tag, const char *strings[], + int len, u32 bits, u32 mask); + +extern int cx88_core_irq(struct cx88_core *core, u32 status); +extern void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count); +extern void cx88_shutdown(struct cx88_core *core); +extern int cx88_reset(struct cx88_core *core); + +extern int +cx88_risc_buffer(struct pci_dev *pci, struct btcx_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 btcx_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines, unsigned int lpi); +extern int +cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value); +extern void +cx88_free_buffer(struct videobuf_queue *q, struct cx88_buffer *buf); + +extern void cx88_risc_disasm(struct cx88_core *core, + struct btcx_riscmem *risc); +extern int cx88_sram_channel_setup(struct cx88_core *core, + const struct sram_channel *ch, + unsigned int bpl, u32 risc); +extern void cx88_sram_channel_dump(struct cx88_core *core, + const struct sram_channel *ch); + +extern int cx88_set_scale(struct cx88_core *core, unsigned int width, + unsigned int height, enum v4l2_field field); +extern int cx88_set_tvnorm(struct cx88_core *core, v4l2_std_id norm); + +extern struct video_device *cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + const struct video_device *template_, + const char *type); +extern struct cx88_core* cx88_core_get(struct pci_dev *pci); +extern void cx88_core_put(struct cx88_core *core, + struct pci_dev *pci); + +extern int cx88_start_audio_dma(struct cx88_core *core); +extern 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); + +/* +int cx8800_start_vbi_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf); +*/ +int cx8800_stop_vbi_dma(struct cx8800_dev *dev); +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q); +void cx8800_vbi_timeout(unsigned long data); + +extern const struct videobuf_queue_ops cx8800_vbi_qops; + +/* ----------------------------------------------------------- */ +/* cx88-i2c.c */ + +extern int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci); + + +/* ----------------------------------------------------------- */ +/* cx88-cards.c */ + +extern int cx88_tuner_callback(void *dev, int component, int command, int arg); +extern int cx88_get_resources(const struct cx88_core *core, + struct pci_dev *pci); +extern struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr); +extern 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); +extern void cx88_i2c_init_ir(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-mpeg.c */ + +int cx8802_buf_prepare(struct videobuf_queue *q,struct cx8802_dev *dev, + struct cx88_buffer *buf, enum v4l2_field field); +void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf); +void cx8802_cancel_buffers(struct cx8802_dev *dev); + +/* ----------------------------------------------------------- */ +/* cx88-video.c*/ +int cx88_enum_input (struct cx88_core *core,struct v4l2_input *i); +int cx88_set_freq (struct cx88_core *core,struct v4l2_frequency *f); +int cx88_video_mux(struct cx88_core *core, unsigned int input); +void cx88_querycap(struct file *file, struct cx88_core *core, + struct v4l2_capability *cap); |