diff options
Diffstat (limited to 'drivers/media/pci/cx88')
| -rw-r--r-- | drivers/media/pci/cx88/Kconfig | 93 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/Makefile | 17 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-alsa.c | 950 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-blackbird.c | 1300 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-cards.c | 3815 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-core.c | 1140 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-dsp.c | 322 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-dvb.c | 1795 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-i2c.c | 183 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-input.c | 635 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-mpeg.c | 920 | ||||
| -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 | 2041 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-vp3054-i2c.c | 158 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88-vp3054-i2c.h | 41 | ||||
| -rw-r--r-- | drivers/media/pci/cx88/cx88.h | 753 | 
18 files changed, 16303 insertions, 0 deletions
diff --git a/drivers/media/pci/cx88/Kconfig b/drivers/media/pci/cx88/Kconfig new file mode 100644 index 00000000000..a63a9ad163b --- /dev/null +++ b/drivers/media/pci/cx88/Kconfig @@ -0,0 +1,93 @@ +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 DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT +	select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT +	select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT +	---help--- +	  This adds support for DVB/ATSC cards based on the +	  Conexant 2388x chip. + +	  To compile this driver as a module, choose M here: the +	  module will be called cx88-dvb. + +config VIDEO_CX88_ENABLE_VP3054 +	bool "VP-3054 Secondary I2C Bus Support" +	default y +	depends on VIDEO_CX88_DVB && DVB_MT352 +	---help--- +	  This adds DVB-T support for cards based on the +	  Conexant 2388x chip and the MT352 demodulator, +	  which also require support for the VP-3054 +	  Secondary I2C bus, such at DNTV Live! DVB-T Pro. + +config VIDEO_CX88_VP3054 +	tristate +	depends on VIDEO_CX88_DVB && VIDEO_CX88_ENABLE_VP3054 +	default y + +config VIDEO_CX88_MPEG +	tristate +	depends on VIDEO_CX88_DVB || VIDEO_CX88_BLACKBIRD +	default y diff --git a/drivers/media/pci/cx88/Makefile b/drivers/media/pci/cx88/Makefile new file mode 100644 index 00000000000..8619c1becee --- /dev/null +++ b/drivers/media/pci/cx88/Makefile @@ -0,0 +1,17 @@ +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/common +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..a72579a9f67 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-alsa.c @@ -0,0 +1,950 @@ +/* + * + *  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...) do {				\ +	if (debug + 1 > level)						\ +		printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg);\ +} while(0) + +#define dprintk_core(level, fmt, arg...) do {				\ +	if (debug + 1 > level)						\ +		printk(KERN_DEBUG "%s/1: " fmt, chip->core->name , ## arg);\ +} while(0) + +/**************************************************************************** +	Data type declarations - Can be moded to a header file later + ****************************************************************************/ + +struct cx88_audio_buffer { +	unsigned int               bpl; +	struct 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 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->sd_wm8775) +		snd_cx88_wm8775_volume_put(kcontrol, value); + +	left = value->value.integer.value[0] & 0x3f; +	right = value->value.integer.value[1] & 0x3f; +	b = right - left; +	if (b < 0) { +		v = 0x3f - left; +		b = (-b) | 0x40; +	} else { +		v = 0x3f - right; +	} +	/* Do we really know this will always be called with IRQs on? */ +	spin_lock_irq(&chip->reg_lock); +	old = cx_read(AUD_VOL_CTL); +	if (v != (old & 0x3f)) { +		cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v); +		changed = 1; +	} +	if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) { +		cx_write(AUD_BAL_CTL, b); +		changed = 1; +	} +	spin_unlock_irq(&chip->reg_lock); + +	return changed; +} + +static const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0); + +static const struct snd_kcontrol_new snd_cx88_volume = { +	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, +	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | +		  SNDRV_CTL_ELEM_ACCESS_TLV_READ, +	.name = "Analog-TV Volume", +	.info = snd_cx88_volume_info, +	.get = snd_cx88_volume_get, +	.put = snd_cx88_volume_put, +	.tlv.p = snd_cx88_db_scale, +}; + +static int snd_cx88_switch_get(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_value *value) +{ +	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->sd_wm8775 && ((1<<6) == bit)) +			wm8775_s_ctrl(core, V4L2_CID_AUDIO_MUTE, 0 != (vol & bit)); +		ret = 1; +	} +	spin_unlock_irq(&chip->reg_lock); +	return ret; +} + +static const struct snd_kcontrol_new snd_cx88_dac_switch = { +	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name = "Audio-Out Switch", +	.info = snd_ctl_boolean_mono_info, +	.get = snd_cx88_switch_get, +	.put = snd_cx88_switch_put, +	.private_value = (1<<8), +}; + +static const struct snd_kcontrol_new snd_cx88_source_switch = { +	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name = "Analog-TV Switch", +	.info = snd_ctl_boolean_mono_info, +	.get = snd_cx88_switch_get, +	.put = snd_cx88_switch_put, +	.private_value = (1<<6), +}; + +static int snd_cx88_alc_get(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_value *value) +{ +	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[] = { +	{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 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, chip->core->name, chip); +	if (err < 0) { +		dprintk(0, "%s: can't get IRQ %d\n", +		       chip->core->name, chip->pci->irq); +		return err; +	} + +	/* print pci info */ +	pci_read_config_byte(pci, PCI_LATENCY_TIMER, &pci_lat); + +	dprintk(1,"ALSA %s/%i: found at %s, rev: %d, irq: %d, " +	       "latency: %d, mmio: 0x%llx\n", core->name, devno, +	       pci_name(pci), pci->revision, pci->irq, +	       pci_lat, (unsigned long long)pci_resource_start(pci,0)); + +	chip->irq = pci->irq; +	synchronize_irq(chip->irq); + +	*rchip = chip; +	*core_ptr = core; + +	return 0; +} + +static int cx88_audio_initdev(struct pci_dev *pci, +			      const struct pci_device_id *pci_id) +{ +	struct snd_card  *card; +	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_new(&pci->dev, 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->sd_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 cx88_audio_finidev(struct pci_dev *pci) +{ +	struct snd_card *card = pci_get_drvdata(pci); + +	snd_card_free(card); + +	devno--; +} + +/* + * PCI driver definition + */ + +static struct pci_driver cx88_audio_pci_driver = { +	.name     = "cx88_audio", +	.id_table = cx88_audio_pci_tbl, +	.probe    = cx88_audio_initdev, +	.remove   = cx88_audio_finidev, +}; + +module_pci_driver(cx88_audio_pci_driver); diff --git a/drivers/media/pci/cx88/cx88-blackbird.c b/drivers/media/pci/cx88/cx88-blackbird.c new file mode 100644 index 00000000000..150bb76e783 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-blackbird.c @@ -0,0 +1,1300 @@ +/* + * + *  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...) do {				      \ +	if (debug + 1 > level)						      \ +		printk(KERN_DEBUG "%s/2-bb: " fmt, dev->core->name , ## arg); \ +} while(0) + +/* ------------------------------------------------------------------ */ + +#define BLACKBIRD_FIRM_IMAGE_SIZE 376836 + +/* defines below are from ivtv-driver.h */ + +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF + +/* Firmware API commands */ +#define IVTV_API_STD_TIMEOUT 500 + +enum blackbird_capture_type { +	BLACKBIRD_MPEG_CAPTURE, +	BLACKBIRD_RAW_CAPTURE, +	BLACKBIRD_RAW_PASSTHRU_CAPTURE +}; +enum blackbird_capture_bits { +	BLACKBIRD_RAW_BITS_NONE             = 0x00, +	BLACKBIRD_RAW_BITS_YUV_CAPTURE      = 0x01, +	BLACKBIRD_RAW_BITS_PCM_CAPTURE      = 0x02, +	BLACKBIRD_RAW_BITS_VBI_CAPTURE      = 0x04, +	BLACKBIRD_RAW_BITS_PASSTHRU_CAPTURE = 0x08, +	BLACKBIRD_RAW_BITS_TO_HOST_CAPTURE  = 0x10 +}; +enum blackbird_capture_end { +	BLACKBIRD_END_AT_GOP, /* stop at the end of gop, generate irq */ +	BLACKBIRD_END_NOW, /* stop immediately, no irq */ +}; +enum blackbird_framerate { +	BLACKBIRD_FRAMERATE_NTSC_30, /* NTSC: 30fps */ +	BLACKBIRD_FRAMERATE_PAL_25   /* PAL: 25fps */ +}; +enum blackbird_stream_port { +	BLACKBIRD_OUTPUT_PORT_MEMORY, +	BLACKBIRD_OUTPUT_PORT_STREAMING, +	BLACKBIRD_OUTPUT_PORT_SERIAL +}; +enum blackbird_data_xfer_status { +	BLACKBIRD_MORE_BUFFERS_FOLLOW, +	BLACKBIRD_LAST_BUFFER, +}; +enum blackbird_picture_mask { +	BLACKBIRD_PICTURE_MASK_NONE, +	BLACKBIRD_PICTURE_MASK_I_FRAMES, +	BLACKBIRD_PICTURE_MASK_I_P_FRAMES = 0x3, +	BLACKBIRD_PICTURE_MASK_ALL_FRAMES = 0x7, +}; +enum blackbird_vbi_mode_bits { +	BLACKBIRD_VBI_BITS_SLICED, +	BLACKBIRD_VBI_BITS_RAW, +}; +enum blackbird_vbi_insertion_bits { +	BLACKBIRD_VBI_BITS_INSERT_IN_XTENSION_USR_DATA, +	BLACKBIRD_VBI_BITS_INSERT_IN_PRIVATE_PACKETS = 0x1 << 1, +	BLACKBIRD_VBI_BITS_SEPARATE_STREAM = 0x2 << 1, +	BLACKBIRD_VBI_BITS_SEPARATE_STREAM_USR_DATA = 0x4 << 1, +	BLACKBIRD_VBI_BITS_SEPARATE_STREAM_PRV_DATA = 0x5 << 1, +}; +enum blackbird_dma_unit { +	BLACKBIRD_DMA_BYTES, +	BLACKBIRD_DMA_FRAMES, +}; +enum blackbird_dma_transfer_status_bits { +	BLACKBIRD_DMA_TRANSFER_BITS_DONE = 0x01, +	BLACKBIRD_DMA_TRANSFER_BITS_ERROR = 0x04, +	BLACKBIRD_DMA_TRANSFER_BITS_LL_ERROR = 0x10, +}; +enum blackbird_pause { +	BLACKBIRD_PAUSE_ENCODING, +	BLACKBIRD_RESUME_ENCODING, +}; +enum blackbird_copyright { +	BLACKBIRD_COPYRIGHT_OFF, +	BLACKBIRD_COPYRIGHT_ON, +}; +enum blackbird_notification_type { +	BLACKBIRD_NOTIFICATION_REFRESH, +}; +enum blackbird_notification_status { +	BLACKBIRD_NOTIFICATION_OFF, +	BLACKBIRD_NOTIFICATION_ON, +}; +enum blackbird_notification_mailbox { +	BLACKBIRD_NOTIFICATION_NO_MAILBOX = -1, +}; +enum blackbird_field1_lines { +	BLACKBIRD_FIELD1_SAA7114 = 0x00EF, /* 239 */ +	BLACKBIRD_FIELD1_SAA7115 = 0x00F0, /* 240 */ +	BLACKBIRD_FIELD1_MICRONAS = 0x0105, /* 261 */ +}; +enum blackbird_field2_lines { +	BLACKBIRD_FIELD2_SAA7114 = 0x00EF, /* 239 */ +	BLACKBIRD_FIELD2_SAA7115 = 0x00F0, /* 240 */ +	BLACKBIRD_FIELD2_MICRONAS = 0x0106, /* 262 */ +}; +enum blackbird_custom_data_type { +	BLACKBIRD_CUSTOM_EXTENSION_USR_DATA, +	BLACKBIRD_CUSTOM_PRIVATE_PACKET, +}; +enum blackbird_mute { +	BLACKBIRD_UNMUTE, +	BLACKBIRD_MUTE, +}; +enum blackbird_mute_video_mask { +	BLACKBIRD_MUTE_VIDEO_V_MASK = 0x0000FF00, +	BLACKBIRD_MUTE_VIDEO_U_MASK = 0x00FF0000, +	BLACKBIRD_MUTE_VIDEO_Y_MASK = 0xFF000000, +}; +enum blackbird_mute_video_shift { +	BLACKBIRD_MUTE_VIDEO_V_SHIFT = 8, +	BLACKBIRD_MUTE_VIDEO_U_SHIFT = 16, +	BLACKBIRD_MUTE_VIDEO_Y_SHIFT = 24, +}; + +/* Registers */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_SPU (0x9050 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_HW_BLOCKS (0x9054 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_VPU (0x9058 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_APU (0xA064 /*| IVTV_REG_OFFSET*/) + +/* ------------------------------------------------------------------ */ + +static void host_setup(struct cx88_core *core) +{ +	/* toggle reset of the host */ +	cx_write(MO_GPHST_SOFT_RST, 1); +	udelay(100); +	cx_write(MO_GPHST_SOFT_RST, 0); +	udelay(100); + +	/* host port setup */ +	cx_write(MO_GPHST_WSC, 0x44444444U); +	cx_write(MO_GPHST_XFR, 0); +	cx_write(MO_GPHST_WDTH, 15); +	cx_write(MO_GPHST_HDSHK, 0); +	cx_write(MO_GPHST_MUX16, 0x44448888U); +	cx_write(MO_GPHST_MODE, 0); +} + +/* ------------------------------------------------------------------ */ + +#define P1_MDATA0 0x390000 +#define P1_MDATA1 0x390001 +#define P1_MDATA2 0x390002 +#define P1_MDATA3 0x390003 +#define P1_MADDR2 0x390004 +#define P1_MADDR1 0x390005 +#define P1_MADDR0 0x390006 +#define P1_RDATA0 0x390008 +#define P1_RDATA1 0x390009 +#define P1_RDATA2 0x39000A +#define P1_RDATA3 0x39000B +#define P1_RADDR0 0x39000C +#define P1_RADDR1 0x39000D +#define P1_RRDWR  0x39000E + +static int wait_ready_gpio0_bit1(struct cx88_core *core, u32 state) +{ +	unsigned long timeout = jiffies + msecs_to_jiffies(1); +	u32 gpio0,need; + +	need = state ? 2 : 0; +	for (;;) { +		gpio0 = cx_read(MO_GP0_IO) & 2; +		if (need == gpio0) +			return 0; +		if (time_after(jiffies,timeout)) +			return -1; +		udelay(1); +	} +} + +static int memory_write(struct cx88_core *core, u32 address, u32 value) +{ +	/* Warning: address is dword address (4 bytes) */ +	cx_writeb(P1_MDATA0, (unsigned int)value); +	cx_writeb(P1_MDATA1, (unsigned int)(value >> 8)); +	cx_writeb(P1_MDATA2, (unsigned int)(value >> 16)); +	cx_writeb(P1_MDATA3, (unsigned int)(value >> 24)); +	cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) | 0x40); +	cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); +	cx_writeb(P1_MADDR0, (unsigned int)address); +	cx_read(P1_MDATA0); +	cx_read(P1_MADDR0); + +	return wait_ready_gpio0_bit1(core,1); +} + +static int memory_read(struct cx88_core *core, u32 address, u32 *value) +{ +	int retval; +	u32 val; + +	/* Warning: address is dword address (4 bytes) */ +	cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) & ~0xC0); +	cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); +	cx_writeb(P1_MADDR0, (unsigned int)address); +	cx_read(P1_MADDR0); + +	retval = wait_ready_gpio0_bit1(core,1); + +	cx_writeb(P1_MDATA3, 0); +	val     = (unsigned char)cx_read(P1_MDATA3) << 24; +	cx_writeb(P1_MDATA2, 0); +	val    |= (unsigned char)cx_read(P1_MDATA2) << 16; +	cx_writeb(P1_MDATA1, 0); +	val    |= (unsigned char)cx_read(P1_MDATA1) << 8; +	cx_writeb(P1_MDATA0, 0); +	val    |= (unsigned char)cx_read(P1_MDATA0); + +	*value  = val; +	return retval; +} + +static int register_write(struct cx88_core *core, u32 address, u32 value) +{ +	cx_writeb(P1_RDATA0, (unsigned int)value); +	cx_writeb(P1_RDATA1, (unsigned int)(value >> 8)); +	cx_writeb(P1_RDATA2, (unsigned int)(value >> 16)); +	cx_writeb(P1_RDATA3, (unsigned int)(value >> 24)); +	cx_writeb(P1_RADDR0, (unsigned int)address); +	cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); +	cx_writeb(P1_RRDWR, 1); +	cx_read(P1_RDATA0); +	cx_read(P1_RADDR0); + +	return wait_ready_gpio0_bit1(core,1); +} + + +static int register_read(struct cx88_core *core, u32 address, u32 *value) +{ +	int retval; +	u32 val; + +	cx_writeb(P1_RADDR0, (unsigned int)address); +	cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); +	cx_writeb(P1_RRDWR, 0); +	cx_read(P1_RADDR0); + +	retval  = wait_ready_gpio0_bit1(core,1); +	val     = (unsigned char)cx_read(P1_RDATA0); +	val    |= (unsigned char)cx_read(P1_RDATA1) << 8; +	val    |= (unsigned char)cx_read(P1_RDATA2) << 16; +	val    |= (unsigned char)cx_read(P1_RDATA3) << 24; + +	*value  = val; +	return retval; +} + +/* ------------------------------------------------------------------ */ + +static int blackbird_mbox_func(void *priv, u32 command, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]) +{ +	struct cx8802_dev *dev = priv; +	unsigned long timeout; +	u32 value, flag, retval; +	int i; + +	dprintk(1,"%s: 0x%X\n", __func__, command); + +	/* this may not be 100% safe if we can't read any memory location +	   without side effects */ +	memory_read(dev->core, dev->mailbox - 4, &value); +	if (value != 0x12345678) { +		dprintk(0, "Firmware and/or mailbox pointer not initialized or corrupted\n"); +		return -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, +				const 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, +				const 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..e18a7ace08b --- /dev/null +++ b/drivers/media/pci/cx88/cx88-cards.c @@ -0,0 +1,3815 @@ +/* + * + * 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) + +#define dprintk(level,fmt, arg...)	do {				\ +	if (cx88_core_debug >= level)					\ +		printk(KERN_DEBUG "%s: " fmt, core->name , ## arg);	\ +	} while(0) + + +/* ------------------------------------------------------------------ */ +/* board config info                                                  */ + +/* If radio_type !=UNSET, radio_addr should be specified + */ + +static const struct cx88_board cx88_boards[] = { +	[CX88_BOARD_UNKNOWN] = { +		.name		= "UNKNOWN/GENERIC", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 0, +		},{ +			.type   = CX88_VMUX_COMPOSITE2, +			.vmux   = 1, +		},{ +			.type   = CX88_VMUX_COMPOSITE3, +			.vmux   = 2, +		},{ +			.type   = CX88_VMUX_COMPOSITE4, +			.vmux   = 3, +		}}, +	}, +	[CX88_BOARD_HAUPPAUGE] = { +		.name		= "Hauppauge WinTV 34xxx models", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0xff00,  // internal decoder +		},{ +			.type   = CX88_VMUX_DEBUG, +			.vmux   = 0, +			.gpio0  = 0xff01,  // mono from tuner chip +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0xff02, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0xff02, +		}}, +		.radio = { +			.type   = CX88_RADIO, +			.gpio0  = 0xff01, +		}, +	}, +	[CX88_BOARD_GDI] = { +		.name		= "GDI Black Gold", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +		}}, +	}, +	[CX88_BOARD_PIXELVIEW] = { +		.name           = "PixelView", +		.tuner_type     = TUNER_PHILIPS_PAL, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0xff00,  // internal decoder +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +		}}, +		.radio = { +			 .type  = CX88_RADIO, +			 .gpio0 = 0xff10, +		}, +	}, +	[CX88_BOARD_ATI_WONDER_PRO] = { +		.name           = "ATI TV Wonder Pro", +		.tuner_type     = TUNER_PHILIPS_4IN1, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT | TDA9887_INTERCARRIER, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x03ff, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x03fe, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x03fe, +		}}, +	}, +	[CX88_BOARD_WINFAST2000XP_EXPERT] = { +		.name           = "Leadtek Winfast 2000XP Expert", +		.tuner_type     = TUNER_PHILIPS_4IN1, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0	= 0x00F5e700, +			.gpio1  = 0x00003004, +			.gpio2  = 0x00F5e700, +			.gpio3  = 0x02000000, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0	= 0x00F5c700, +			.gpio1  = 0x00003004, +			.gpio2  = 0x00F5c700, +			.gpio3  = 0x02000000, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0	= 0x00F5c700, +			.gpio1  = 0x00003004, +			.gpio2  = 0x00F5c700, +			.gpio3  = 0x02000000, +		}}, +		.radio = { +			.type   = CX88_RADIO, +			.gpio0	= 0x00F5d700, +			.gpio1  = 0x00003004, +			.gpio2  = 0x00F5d700, +			.gpio3  = 0x02000000, +		}, +	}, +	[CX88_BOARD_AVERTV_STUDIO_303] = { +		.name           = "AverTV Studio 303 (M126)", +		.tuner_type     = TUNER_PHILIPS_FM1216ME_MK3, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio1  = 0xe09f, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio1  = 0xe05f, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio1  = 0xe05f, +		}}, +		.radio = { +			.gpio1  = 0xe0df, +			.type   = CX88_RADIO, +		}, +	}, +	[CX88_BOARD_MSI_TVANYWHERE_MASTER] = { +		// added gpio values thanks to Michal +		// values for PAL from DScaler +		.name           = "MSI TV-@nywhere Master", +		.tuner_type     = TUNER_MT2032, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf	= TDA9887_PRESENT | TDA9887_INTERCARRIER_NTSC, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x000040bf, +			.gpio1  = 0x000080c0, +			.gpio2  = 0x0000ff40, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x000040bf, +			.gpio1  = 0x000080c0, +			.gpio2  = 0x0000ff40, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x000040bf, +			.gpio1  = 0x000080c0, +			.gpio2  = 0x0000ff40, +		}}, +		.radio = { +			 .type   = CX88_RADIO, +			 .vmux   = 3, +			 .gpio0  = 0x000040bf, +			 .gpio1  = 0x000080c0, +			 .gpio2  = 0x0000ff20, +		}, +	}, +	[CX88_BOARD_WINFAST_DV2000] = { +		.name           = "Leadtek Winfast DV2000", +		.tuner_type     = TUNER_PHILIPS_FM1216ME_MK3, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x0035e700, +			.gpio1  = 0x00003004, +			.gpio2  = 0x0035e700, +			.gpio3  = 0x02000000, +		},{ + +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x0035c700, +			.gpio1  = 0x00003004, +			.gpio2  = 0x0035c700, +			.gpio3  = 0x02000000, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x0035c700, +			.gpio1  = 0x0035c700, +			.gpio2  = 0x02000000, +			.gpio3  = 0x02000000, +		}}, +		.radio = { +			.type   = CX88_RADIO, +			.gpio0  = 0x0035d700, +			.gpio1  = 0x00007004, +			.gpio2  = 0x0035d700, +			.gpio3  = 0x02000000, +		}, +	}, +	[CX88_BOARD_LEADTEK_PVR2000] = { +		// gpio values for PAL version from regspy by DScaler +		.name           = "Leadtek PVR 2000", +		.tuner_type     = TUNER_PHILIPS_FM1216ME_MK3, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x0000bde2, +			.audioroute = 1, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x0000bde6, +			.audioroute = 1, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x0000bde6, +			.audioroute = 1, +		}}, +		.radio = { +			.type   = CX88_RADIO, +			.gpio0  = 0x0000bd62, +			.audioroute = 1, +		}, +		.mpeg           = CX88_MPEG_BLACKBIRD, +	}, +	[CX88_BOARD_IODATA_GVVCP3PCI] = { +		.name		= "IODATA GV-VCP3/PCI", +		.tuner_type     = 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     = CX88_AUDIO_TVAUDIO, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0xbf61,  /* internal decoder */ +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0	= 0xbf63, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0	= 0xbf63, +		}}, +		.radio = { +			 .type  = CX88_RADIO, +			 .gpio0 = 0xbf60, +		 }, +	}, +	[CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T] = { +		.name           = "DViCO FusionHDTV 3 Gold-T", +		.tuner_type     = TUNER_THOMSON_DTT761X, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x97ed, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x97e9, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x97e9, +		}}, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_ADSTECH_DVB_T_PCI] = { +		.name           = "ADS Tech Instant TV DVB-T PCI", +		.tuner_type     = 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	= CX88_AUDIO_WM8775, +		.i2sinputcntl   = 2, +		.input		= {{ +			.type	= CX88_VMUX_DVB, +			.vmux	= 0, +			/* 2: Line-In */ +			.audioroute = 2, +		},{ +			.type	= CX88_VMUX_COMPOSITE1, +			.vmux	= 1, +			/* 2: Line-In */ +			.audioroute = 2, +		},{ +			.type	= CX88_VMUX_SVIDEO, +			.vmux	= 2, +			/* 2: Line-In */ +			.audioroute = 2, +		}}, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_HAUPPAUGE_NOVASE2_S1] = { +		.name		= "Hauppauge Nova-SE2 DVB-S", +		.tuner_type	= 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 = CX88_AUDIO_WM8775, +		.input		= {{ +			.type	= CX88_VMUX_DVB, +			.vmux	= 0, +			/* 2: Line-In */ +			.audioroute = 2, +		},{ +			.type	= CX88_VMUX_COMPOSITE1, +			.vmux	= 1, +			/* 2: Line-In */ +			.audioroute = 2, +		},{ +			.type	= CX88_VMUX_SVIDEO, +			.vmux	= 2, +			/* 2: Line-In */ +			.audioroute = 2, +		}}, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_HAUPPAUGE_HVR1100] = { +		.name		= "Hauppauge WinTV-HVR1100 DVB-T/Hybrid", +		.tuner_type     = TUNER_PHILIPS_FMD1216ME_MK3, +		.radio_type	= UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input		= {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +		},{ +			.type	= CX88_VMUX_COMPOSITE1, +			.vmux	= 1, +		},{ +			.type	= CX88_VMUX_SVIDEO, +			.vmux	= 2, +		}}, +		/* fixme: Add radio support */ +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_HAUPPAUGE_HVR1100LP] = { +		.name		= "Hauppauge WinTV-HVR1100 DVB-T/Hybrid (Low Profile)", +		.tuner_type     = TUNER_PHILIPS_FMD1216ME_MK3, +		.radio_type	= UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.input		= {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +		},{ +			.type	= CX88_VMUX_COMPOSITE1, +			.vmux	= 1, +		}}, +		/* fixme: Add radio support */ +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_DNTV_LIVE_DVB_T_PRO] = { +		.name           = "digitalnow DNTV Live! DVB-T Pro", +		.tuner_type     = TUNER_PHILIPS_FMD1216ME_MK3, +		.radio_type     = UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE | +				  TDA9887_PORT2_ACTIVE, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0xf80808, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0	= 0xf80808, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0	= 0xf80808, +		}}, +		.radio = { +			 .type  = CX88_RADIO, +			 .gpio0 = 0xf80808, +		}, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_KWORLD_DVB_T_CX22702] = { +		/* Kworld V-stream Xpert DVB-T with Thomson tuner */ +		/* DTT 7579 Conexant CX22702-19 Conexant CX2388x  */ +		/* Manenti Marco <marco_manenti@colman.it> */ +		.name           = "KWorld/VStream XPert DVB-T with cx22702", +		.tuner_type     = 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     = CX88_AUDIO_WM8775, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x84bf, +			/* 1: TV Audio / FM Mono */ +			.audioroute = 1, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x84bf, +			/* 2: Line-In */ +			.audioroute = 2, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x84bf, +			/* 2: Line-In */ +			.audioroute = 2, +		}}, +		.radio = { +			.type   = CX88_RADIO, +			.gpio0	= 0x84bf, +			/* 4: FM Stereo (untested) */ +			.audioroute = 8, +		}, +		.mpeg           = CX88_MPEG_DVB, +		.num_frontends	= 2, +	}, +	[CX88_BOARD_NORWOOD_MICRO] = { +		.name           = "Norwood Micro TV Tuner", +		.tuner_type     = TUNER_TNF_5335MF, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x0709, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x070b, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x070b, +		}}, +	}, +	[CX88_BOARD_TE_DTV_250_OEM_SWANN] = { +		.name           = "Shenzhen Tungsten Ages Tech TE-DTV-250 / Swann OEM", +		.tuner_type     = TUNER_LG_PAL_NEW_TAPC, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0x003fffff, +			.gpio1  = 0x00e00000, +			.gpio2  = 0x003fffff, +			.gpio3  = 0x02000000, +		},{ +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0x003fffff, +			.gpio1  = 0x00e00000, +			.gpio2  = 0x003fffff, +			.gpio3  = 0x02000000, +		},{ +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0x003fffff, +			.gpio1  = 0x00e00000, +			.gpio2  = 0x003fffff, +			.gpio3  = 0x02000000, +		}}, +	}, +	[CX88_BOARD_HAUPPAUGE_HVR1300] = { +		.name		= "Hauppauge WinTV-HVR1300 DVB-T/Hybrid MPEG Encoder", +		.tuner_type     = TUNER_PHILIPS_FMD1216ME_MK3, +		.radio_type	= UNSET, +		.tuner_addr	= ADDR_UNSET, +		.radio_addr	= ADDR_UNSET, +		.tda9887_conf   = TDA9887_PRESENT, +		.audio_chip     = CX88_AUDIO_WM8775, +		/* +		 * gpio0 as reported by Mike Crash <mike AT mikecrash.com> +		 */ +		.input		= {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0	= 0xef88, +			/* 1: TV Audio / FM Mono */ +			.audioroute = 1, +		},{ +			.type	= CX88_VMUX_COMPOSITE1, +			.vmux	= 1, +			.gpio0	= 0xef88, +			/* 2: Line-In */ +			.audioroute = 2, +		},{ +			.type	= CX88_VMUX_SVIDEO, +			.vmux	= 2, +			.gpio0	= 0xef88, +			/* 2: Line-In */ +			.audioroute = 2, +		}}, +		.mpeg           = CX88_MPEG_DVB | CX88_MPEG_BLACKBIRD, +		.radio = { +			.type   = CX88_RADIO, +			.gpio0	= 0xef88, +			/* 4: FM Stereo (untested) */ +			.audioroute = 8, +		}, +	}, +	[CX88_BOARD_SAMSUNG_SMT_7020] = { +		.name		= "Samsung SMT 7020 DVB-S", +		.tuner_type	= 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     = CX88_AUDIO_WM8775, +		/* +		 * GPIO0 (WINTV2000) +		 * +		 * Analogue     SAT     DVB-T +		 * Antenna      0xc4bf  0xc4bb +		 * Composite    0xc4bf  0xc4bb +		 * S-Video      0xc4bf  0xc4bb +		 * Composite1   0xc4ff  0xc4fb +		 * S-Video1     0xc4ff  0xc4fb +		 * +		 * BIT  VALUE   FUNCTION GP{x}_IO +		 * 0    1       I:? +		 * 1    1       I:? +		 * 2    1       O:MPEG PORT 0=DVB-T 1=DVB-S +		 * 3    1       I:? +		 * 4    1       I:? +		 * 5    1       I:? +		 * 6    0       O:INPUT SELECTOR 0=INTERNAL 1=EXPANSION +		 * 7    1       O:DVB-T DEMOD RESET LOW +		 * +		 * BIT  VALUE   FUNCTION GP{x}_OE +		 * 8    0       I +		 * 9    0       I +		 * a    1       O +		 * b    0       I +		 * c    0       I +		 * d    0       I +		 * e    1       O +		 * f    1       O +		 * +		 * WM8775 ADC +		 * +		 * 1: TV Audio / FM Mono +		 * 2: Line-In +		 * 3: Line-In Expansion +		 * 4: FM Stereo +		 */ +		.input          = {{ +			.type   = CX88_VMUX_TELEVISION, +			.vmux   = 0, +			.gpio0  = 0xc4bf, +			/* 1: TV Audio / FM Mono */ +			.audioroute = 1, +		}, { +			.type   = CX88_VMUX_COMPOSITE1, +			.vmux   = 1, +			.gpio0  = 0xc4bf, +			/* 2: Line-In */ +			.audioroute = 2, +		}, { +			.type   = CX88_VMUX_SVIDEO, +			.vmux   = 2, +			.gpio0  = 0xc4bf, +			/* 2: Line-In */ +			.audioroute = 2, +		} }, +		.radio = { +			.type   = CX88_RADIO, +			.gpio0	= 0xc4bf, +			/* 4: FM Stereo */ +			.audioroute = 8, +		}, +		.mpeg           = CX88_MPEG_DVB, +		.num_frontends	= 2, +	}, +	[CX88_BOARD_HAUPPAUGE_HVR4000LITE] = { +		.name           = "Hauppauge WinTV-HVR4000(Lite) DVB-S/S2", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_DVB, +			.vmux   = 0, +		} }, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_TEVII_S420] = { +		.name           = "TeVii S420 DVB-S", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_DVB, +			.vmux   = 0, +		} }, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_TEVII_S460] = { +		.name           = "TeVii S460 DVB-S/S2", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_DVB, +			.vmux   = 0, +		} }, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_TEVII_S464] = { +		.name           = "TeVii S464 DVB-S/S2", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_DVB, +			.vmux   = 0, +		} }, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_OMICOM_SS4_PCI] = { +		.name           = "Omicom SS4 DVB-S/S2 PCI", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_DVB, +			.vmux   = 0, +		} }, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_TBS_8910] = { +		.name           = "TBS 8910 DVB-S", +		.tuner_type     = UNSET, +		.radio_type     = UNSET, +		.tuner_addr     = ADDR_UNSET, +		.radio_addr     = ADDR_UNSET, +		.input          = {{ +			.type   = CX88_VMUX_DVB, +			.vmux   = 0, +		} }, +		.mpeg           = CX88_MPEG_DVB, +	}, +	[CX88_BOARD_TBS_8920] = { +		.name           = "TBS 8920 DVB-S/S2", +		.tuner_type     = 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; +	core->model = tv.model; + +	/* Make sure we support the board model */ +	switch (tv.model) +	{ +	case 14009: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in) */ +	case 14019: /* WinTV-HVR3000 (Retail, IR Blaster, b/panel video, 3.5mm audio in) */ +	case 14029: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge) */ +	case 14109: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - low profile) */ +	case 14129: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge - LP) */ +	case 14559: /* WinTV-HVR3000 (OEM, no IR, b/panel video, 3.5mm audio in) */ +	case 14569: /* WinTV-HVR3000 (OEM, no IR, no back panel video) */ +	case 14659: /* WinTV-HVR3000 (OEM, no IR, b/panel video, RCA audio in - Low profile) */ +	case 14669: /* WinTV-HVR3000 (OEM, no IR, no b/panel video - Low profile) */ +	case 28552: /* WinTV-PVR 'Roslyn' (No IR) */ +	case 34519: /* WinTV-PCI-FM */ +	case 69009: +		/* WinTV-HVR4000 (DVBS/S2/T, Video and IR, back panel inputs) */ +	case 69100: /* WinTV-HVR4000LITE (DVBS/S2, IR) */ +	case 69500: /* WinTV-HVR4000LITE (DVBS/S2, No IR) */ +	case 69559: +		/* WinTV-HVR4000 (DVBS/S2/T, Video no IR, back panel inputs) */ +	case 69569: /* WinTV-HVR4000 (DVBS/S2/T, Video no IR) */ +	case 90002: /* Nova-T-PCI (9002) */ +	case 92001: /* Nova-S-Plus (Video and IR) */ +	case 92002: /* Nova-S-Plus (Video and IR) */ +	case 90003: /* Nova-T-PCI (9002 No RF out) */ +	case 90500: /* Nova-T-PCI (oem) */ +	case 90501: /* Nova-T-PCI (oem/IR) */ +	case 92000: /* Nova-SE2 (OEM, No Video or IR) */ +	case 92900: /* WinTV-IROnly (No analog or digital Video inputs) */ +	case 94009: /* WinTV-HVR1100 (Video and IR Retail) */ +	case 94501: /* WinTV-HVR1100 (Video and IR OEM) */ +	case 96009: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX) */ +	case 96019: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX/TX) */ +	case 96559: /* WinTV-HVR1300 (PAL Video, MPEG Video no IR) */ +	case 96569: /* WinTV-HVR1300 () */ +	case 96659: /* WinTV-HVR1300 () */ +	case 98559: /* WinTV-HVR1100LP (Video no IR, Retail - Low Profile) */ +		/* known */ +		break; +	case CX88_BOARD_SAMSUNG_SMT_7020: +		cx_set(MO_GP0_IO, 0x008989FF); +		break; +	default: +		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: +			dprintk(1, "setting GPIO to radio!\n"); +			cx_write(MO_GP0_IO, 0x4ff); +			mdelay(250); +			cx_write(MO_GP2_IO, 0xff); +			mdelay(250); +			break; +		case CX88_VMUX_DVB:	/* Digital TV*/ +		default:		/* Analog TV */ +			dprintk(1, "setting GPIO to TV!\n"); +			break; +		} +		cx_write(MO_GP1_IO, 0x101010); +		mdelay(250); +		cx_write(MO_GP1_IO, 0x101000); +		mdelay(250); +		cx_write(MO_GP1_IO, 0x101010); +		mdelay(250); +		return 0; +	} +	return -EINVAL; +} + +static int cx88_xc4000_tuner_callback(struct cx88_core *core, +				      int command, int arg) +{ +	/* Board-specific callbacks */ +	switch (core->boardnr) { +	case CX88_BOARD_WINFAST_DTV1800H_XC4000: +	case CX88_BOARD_WINFAST_DTV2000H_PLUS: +	case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36: +	case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43: +		return cx88_xc4000_winfast2000h_plus_callback(core, +							      command, arg); +	} +	return -EINVAL; +} + +/* ----------------------------------------------------------------------- */ +/* Tuner callback function. Currently only needed for the Pinnacle 	   * + * PCTV HD 800i with an xc5000 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 { +			dprintk(1, "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 { +			dprintk(1, "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: +			dprintk(1, "Calling XC2028/3028 callback\n"); +			return cx88_xc2028_tuner_callback(core, command, arg); +		case TUNER_XC4000: +			dprintk(1, "Calling XC4000 callback\n"); +			return cx88_xc4000_tuner_callback(core, command, arg); +		case TUNER_XC5000: +			dprintk(1, "Calling XC5000 callback\n"); +			return cx88_xc5000_tuner_callback(core, command, arg); +	} +	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; +		dprintk(1, "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); +	} + +	core->board = cx88_boards[core->boardnr]; + +	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]; + +	dprintk(1, "TV tuner type %d, Radio tuner type %d\n", +		core->board.tuner_type, core->board.radio_type); + +	/* init hardware */ +	cx88_reset(core); +	cx88_card_setup_pre_i2c(core); +	cx88_i2c_init(core, pci); + +	/* load tuner module, if needed */ +	if (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..e061c88b697 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-core.c @@ -0,0 +1,1140 @@ +/* + * + * 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"); + +/* ------------------------------------------------------------------ */ + +unsigned int cx88_core_debug; +module_param_named(core_debug, cx88_core_debug, int, 0644); +MODULE_PARM_DESC(core_debug, "enable debug messages [core]"); + +static unsigned int nicam; +module_param(nicam,int,0644); +MODULE_PARM_DESC(nicam,"tv audio is nicam"); + +static unsigned int nocomb; +module_param(nocomb,int,0644); +MODULE_PARM_DESC(nocomb,"disable comb filter"); + +#define dprintk(level,fmt, arg...)	do {				\ +	if (cx88_core_debug >= level)					\ +		printk(KERN_DEBUG "%s: " fmt, core->name , ## arg);	\ +	} while(0) + +static unsigned int cx88_devcount; +static LIST_HEAD(cx88_devlist); +static DEFINE_MUTEX(devlist); + +#define NO_SYNC_LINE (-1U) + +/* @lpi: lines per IRQ, or 0 to not generate irqs. Note: IRQ to be +	 generated _after_ lpi lines are transferred. */ +static __le32* cx88_risc_field(__le32 *rp, struct scatterlist *sglist, +			    unsigned int offset, u32 sync_line, +			    unsigned int bpl, unsigned int padding, +			    unsigned int lines, unsigned int lpi) +{ +	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; +		v4l2_get_timestamp(&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 inline unsigned int norm_swidth(v4l2_std_id norm) +{ +	return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 754 : 922; +} + +static inline unsigned int norm_hdelay(v4l2_std_id norm) +{ +	return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 135 : 186; +} + +static inline unsigned int norm_vdelay(v4l2_std_id norm) +{ +	return (norm & V4L2_STD_625_50) ? 0x24 : 0x18; +} + +static inline unsigned int norm_fsc8(v4l2_std_id norm) +{ +	if (norm & V4L2_STD_PAL_M) +		return 28604892;      // 3.575611 MHz + +	if (norm & (V4L2_STD_PAL_Nc)) +		return 28656448;      // 3.582056 MHz + +	if (norm & V4L2_STD_NTSC) // All NTSC/M and variants +		return 28636360;      // 3.57954545 MHz +/- 10 Hz + +	/* SECAM have also different sub carrier for chroma, +	   but step_db and step_dr, at cx88_set_tvnorm already handles that. + +	   The same FSC applies to PAL/BGDKIH, PAL/60, NTSC/4.43 and PAL/N +	 */ + +	return 35468950;      // 4.43361875 MHz +/- 5 Hz +} + +static inline unsigned int norm_htotal(v4l2_std_id norm) +{ + +	unsigned int fsc4=norm_fsc8(norm)/2; + +	/* returns 4*FSC / vtotal / frames per seconds */ +	return (norm & V4L2_STD_625_50) ? +				((fsc4+312)/625+12)/25 : +				((fsc4+262)/525*1001+15000)/30000; +} + +static inline unsigned int norm_vbipack(v4l2_std_id norm) +{ +	return (norm & V4L2_STD_625_50) ? 511 : 400; +} + +int cx88_set_scale(struct cx88_core *core, unsigned int width, unsigned int height, +		   enum v4l2_field field) +{ +	unsigned int swidth  = norm_swidth(core->tvnorm); +	unsigned int sheight = norm_maxh(core->tvnorm); +	u32 value; + +	dprintk(1,"set_scale: %dx%d [%s%s,%s]\n", width, height, +		V4L2_FIELD_HAS_TOP(field)    ? "T" : "", +		V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "", +		v4l2_norm_to_name(core->tvnorm)); +	if (!V4L2_FIELD_HAS_BOTH(field)) +		height *= 2; + +	// recalc H delay and scale registers +	value = (width * norm_hdelay(core->tvnorm)) / swidth; +	value &= 0x3fe; +	cx_write(MO_HDELAY_EVEN,  value); +	cx_write(MO_HDELAY_ODD,   value); +	dprintk(1,"set_scale: hdelay  0x%04x (width %d)\n", value,swidth); + +	value = (swidth * 4096 / width) - 4096; +	cx_write(MO_HSCALE_EVEN,  value); +	cx_write(MO_HSCALE_ODD,   value); +	dprintk(1,"set_scale: hscale  0x%04x\n", value); + +	cx_write(MO_HACTIVE_EVEN, width); +	cx_write(MO_HACTIVE_ODD,  width); +	dprintk(1,"set_scale: hactive 0x%04x\n", width); + +	// recalc V scale Register (delay is constant) +	cx_write(MO_VDELAY_EVEN, norm_vdelay(core->tvnorm)); +	cx_write(MO_VDELAY_ODD,  norm_vdelay(core->tvnorm)); +	dprintk(1,"set_scale: vdelay  0x%04x\n", norm_vdelay(core->tvnorm)); + +	value = (0x10000 - (sheight * 512 / height - 512)) & 0x1fff; +	cx_write(MO_VSCALE_EVEN,  value); +	cx_write(MO_VSCALE_ODD,   value); +	dprintk(1,"set_scale: vscale  0x%04x\n", value); + +	cx_write(MO_VACTIVE_EVEN, sheight); +	cx_write(MO_VACTIVE_ODD,  sheight); +	dprintk(1,"set_scale: vactive 0x%04x\n", sheight); + +	// setup filters +	value = 0; +	value |= (1 << 19);        // CFILT (default) +	if (core->tvnorm & V4L2_STD_SECAM) { +		value |= (1 << 15); +		value |= (1 << 16); +	} +	if (INPUT(core->input).type == CX88_VMUX_SVIDEO) +		value |= (1 << 13) | (1 << 5); +	if (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, video, s_std, norm); + +	/* The chroma_agc control should be inaccessible if the video format is SECAM */ +	v4l2_ctrl_grab(core->chroma_agc, cxiformat == VideoFormatSECAM); + +	// done +	return 0; +} + +/* ------------------------------------------------------------------ */ + +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_; +	/* +	 * The dev pointer of v4l2_device is NULL, instead we set the +	 * video_device dev_parent pointer to the correct PCI bus device. +	 * This driver is a rare example where there is one v4l2_device, +	 * but the video nodes have different parent (PCI) devices. +	 */ +	vfd->v4l2_dev = &core->v4l2_dev; +	vfd->dev_parent = &pci->dev; +	vfd->release = video_device_release; +	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..053ed1ba1d8 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-dvb.c @@ -0,0 +1,1795 @@ +/* + * + * 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 "ts2020.h" +#include "ds3000.h" + +MODULE_DESCRIPTION("driver for cx2388x based DVB cards"); +MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages [dvb]"); + +static unsigned int dvb_buf_tscnt = 32; +module_param(dvb_buf_tscnt, int, 0644); +MODULE_PARM_DESC(dvb_buf_tscnt, "DVB Buffer TS count [dvb]"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define dprintk(level,fmt, arg...)	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 IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) +static int dntv_live_dvbt_pro_demod_init(struct dvb_frontend* fe) +{ +	static const u8 clock_config []  = { 0x89, 0x38, 0x38 }; +	static const u8 reset []         = { 0x50, 0x80 }; +	static const u8 adc_ctl_1_cfg [] = { 0x8E, 0x40 }; +	static const u8 agc_cfg []       = { 0x67, 0x10, 0x20, 0x00, 0xFF, 0xFF, +				       0x00, 0xFF, 0x00, 0x40, 0x40 }; +	static const u8 dntv_extra[]     = { 0xB5, 0x7A }; +	static const u8 capt_range_cfg[] = { 0x75, 0x32 }; + +	mt352_write(fe, clock_config,   sizeof(clock_config)); +	udelay(2000); +	mt352_write(fe, reset,          sizeof(reset)); +	mt352_write(fe, adc_ctl_1_cfg,  sizeof(adc_ctl_1_cfg)); + +	mt352_write(fe, agc_cfg,        sizeof(agc_cfg)); +	udelay(2000); +	mt352_write(fe, dntv_extra,     sizeof(dntv_extra)); +	mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + +	return 0; +} + +static const struct mt352_config dntv_live_dvbt_pro_config = { +	.demod_address = 0x0f, +	.no_tuner      = 1, +	.demod_init    = dntv_live_dvbt_pro_demod_init, +}; +#endif + +static const struct zl10353_config dvico_fusionhdtv_hybrid = { +	.demod_address = 0x0f, +	.no_tuner      = 1, +}; + +static const struct zl10353_config dvico_fusionhdtv_xc3028 = { +	.demod_address = 0x0f, +	.if2           = 45600, +	.no_tuner      = 1, +}; + +static const struct mt352_config dvico_fusionhdtv_mt352_xc3028 = { +	.demod_address = 0x0f, +	.if2 = 4560, +	.no_tuner = 1, +	.demod_init = dvico_fusionhdtv_demod_init, +}; + +static const struct zl10353_config dvico_fusionhdtv_plus_v1_1 = { +	.demod_address = 0x0f, +}; + +static const struct cx22702_config connexant_refboard_config = { +	.demod_address = 0x43, +	.output_mode   = CX22702_SERIAL_OUTPUT, +}; + +static const struct cx22702_config hauppauge_hvr_config = { +	.demod_address = 0x63, +	.output_mode   = CX22702_SERIAL_OUTPUT, +}; + +static int or51132_set_ts_param(struct dvb_frontend* fe, int is_punctured) +{ +	struct cx8802_dev *dev= fe->dvb->priv; +	dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00; +	return 0; +} + +static const struct or51132_config pchdtv_hd3000 = { +	.demod_address = 0x15, +	.set_ts_params = or51132_set_ts_param, +}; + +static int lgdt330x_pll_rf_set(struct dvb_frontend* fe, int index) +{ +	struct cx8802_dev *dev= fe->dvb->priv; +	struct cx88_core *core = dev->core; + +	dprintk(1, "%s: index = %d\n", __func__, index); +	if (index == 0) +		cx_clear(MO_GP0_IO, 8); +	else +		cx_set(MO_GP0_IO, 8); +	return 0; +} + +static int lgdt330x_set_ts_param(struct dvb_frontend* fe, int is_punctured) +{ +	struct cx8802_dev *dev= fe->dvb->priv; +	if (is_punctured) +		dev->ts_gen_cntrl |= 0x04; +	else +		dev->ts_gen_cntrl &= ~0x04; +	return 0; +} + +static struct lgdt330x_config fusionhdtv_3_gold = { +	.demod_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 struct ts2020_config tevii_ts2020_config  = { +	.tuner_address = 0x60, +	.clk_out_div = 1, +}; + +static const struct stv0900_config prof_7301_stv0900_config = { +	.demod_address = 0x6a, +/*	demod_mode = 0,*/ +	.xtal = 27000000, +	.clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */ +	.diseqc_mode = 2,/* 2/3 PWM */ +	.tun1_maddress = 0,/* 0x60 */ +	.tun1_adc = 0,/* 2 Vpp */ +	.path1_mode = 3, +	.set_ts_params = stv0900_set_ts_param, +}; + +static const struct stb6100_config prof_7301_stb6100_config = { +	.tuner_address = 0x60, +	.refclock = 27000000, +}; + +static const struct stv0299_config tevii_tuner_sharp_config = { +	.demod_address = 0x68, +	.inittab = sharp_z0194a_inittab, +	.mclk = 88000000UL, +	.invert = 1, +	.skip_reinit = 0, +	.lock_output = 1, +	.volt13_op0_op1 = STV0299_VOLT13_OP1, +	.min_delay_ms = 100, +	.set_symbol_rate = sharp_z0194a_set_symbol_rate, +	.set_ts_params = cx24116_set_ts_param, +}; + +static const struct stv0288_config tevii_tuner_earda_config = { +	.demod_address = 0x68, +	.min_delay_ms = 100, +	.set_ts_params = cx24116_set_ts_param, +}; + +static int cx8802_alloc_frontends(struct cx8802_dev *dev) +{ +	struct cx88_core *core = dev->core; +	struct 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, false)) +				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 IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) +		/* MT352 is on a secondary I2C bus made from some GPIO lines */ +		fe0->dvb.frontend = dvb_attach(mt352_attach, &dntv_live_dvbt_pro_config, +					       &dev->vp3054->adap); +		if (fe0->dvb.frontend != 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) { +			bool override_tone; + +			if (core->model == 92001) +				override_tone = true; +			else +				override_tone = false; + +			if (!dvb_attach(isl6421_attach, fe0->dvb.frontend, +					&core->i2c_adap, 0x08, ISL6421_DCL, 0x00, +					override_tone)) +				goto frontend_detach; +		} +		break; +	case CX88_BOARD_KWORLD_DVBS_100: +		fe0->dvb.frontend = dvb_attach(cx24123_attach, +					       &kworld_dvbs_100_config, +					       &core->i2c_adap); +		if (fe0->dvb.frontend) { +			core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; +			fe0->dvb.frontend->ops.set_voltage = kworld_dvbs_100_set_voltage; +		} +		break; +	case CX88_BOARD_GENIATECH_DVBS: +		fe0->dvb.frontend = dvb_attach(cx24123_attach, +					       &geniatech_dvbs_config, +					       &core->i2c_adap); +		if (fe0->dvb.frontend) { +			core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; +			fe0->dvb.frontend->ops.set_voltage = geniatech_dvbs_set_voltage; +		} +		break; +	case CX88_BOARD_PINNACLE_PCTV_HD_800i: +		fe0->dvb.frontend = dvb_attach(s5h1409_attach, +					       &pinnacle_pctv_hd_800i_config, +					       &core->i2c_adap); +		if (fe0->dvb.frontend != 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, false)) +				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, false)) +				goto frontend_detach; +		} +		break; +	case CX88_BOARD_PROF_6200: +	case CX88_BOARD_TBS_8910: +	case CX88_BOARD_TEVII_S420: +		fe0->dvb.frontend = dvb_attach(stv0299_attach, +						&tevii_tuner_sharp_config, +						&core->i2c_adap); +		if (fe0->dvb.frontend != 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) { +			dvb_attach(ts2020_attach, fe0->dvb.frontend, +				&tevii_ts2020_config, &core->i2c_adap); +			fe0->dvb.frontend->ops.set_voltage = +							tevii_dvbs_set_voltage; +		} +		break; +	case CX88_BOARD_OMICOM_SS4_PCI: +	case CX88_BOARD_TBS_8920: +	case CX88_BOARD_PROF_7300: +	case CX88_BOARD_SATTRADE_ST4200: +		fe0->dvb.frontend = dvb_attach(cx24116_attach, +					       &hauppauge_hvr4000_config, +					       &core->i2c_adap); +		if (fe0->dvb.frontend != 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..cf2d6961583 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-i2c.c @@ -0,0 +1,183 @@ + +/* + +    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; + +	core->i2c_algo = cx8800_i2c_algo_template; + + +	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..f991696a6c5 --- /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_BIT_OTHER; +	int err = -ENOMEM; +	u32 hardware_mask = 0;	/* For devices with a hardware mask, when +				 * used with a full-code IR table +				 */ + +	ir = kzalloc(sizeof(*ir), GFP_KERNEL); +	dev = rc_allocate_device(); +	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_BIT_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; +		rc_set_allowed_protocols(dev, 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_BIT_UNKNOWN; +		core->init_data.get_key = get_key_pvr2000; +		core->init_data.ir_codes = RC_MAP_EMPTY; +		break; +	} + +	/* +	 * We can't call i2c_new_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_BIT_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..74b7b8614c2 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-mpeg.c @@ -0,0 +1,920 @@ +/* + * + *  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...) do {				       \ +	if (debug + 1 > level)						       \ +		printk(KERN_DEBUG "%s/2-mpeg: " fmt, dev->core->name, ## arg); \ +} while(0) + +#define mpeg_dbg(level, fmt, arg...) do {				  \ +	if (debug + 1 > level)						  \ +		printk(KERN_DEBUG "%s/2-mpeg: " fmt, core->name, ## arg); \ +} while(0) + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ +	struct cx8802_dev *dev=container_of(work, struct cx8802_dev, request_module_wk); + +	if (dev->core->board.mpeg & CX88_MPEG_DVB) +		request_module("cx88-dvb"); +	if (dev->core->board.mpeg & CX88_MPEG_BLACKBIRD) +		request_module("cx88-blackbird"); +} + +static void request_modules(struct cx8802_dev *dev) +{ +	INIT_WORK(&dev->request_module_wk, request_module_async); +	schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct cx8802_dev *dev) +{ +	flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + + +static LIST_HEAD(cx8802_devlist); +static DEFINE_MUTEX(cx8802_mutex); +/* ------------------------------------------------------------------ */ + +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_move_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_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(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, 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); + +	/* 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; +	unsigned long flags; + +	/* stop mpeg dma */ +	spin_lock_irqsave(&dev->slock, flags); +	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_irqrestore(&dev->slock, flags); + +	/* 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; +	unsigned long flags; +	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_irqsave(&dev->slock, flags); +	if (!list_empty(&dev->mpegq.active)) { +		printk("%s: resume mpeg\n", core->name); +		cx8802_restart_queue(dev,&dev->mpegq); +	} +	spin_unlock_irqrestore(&dev->slock, flags); + +	return 0; +} + +struct cx8802_driver * cx8802_get_driver(struct cx8802_dev *dev, enum cx88_board_type btype) +{ +	struct cx8802_driver *d; + +	list_for_each_entry(d, &dev->drvlist, drvlist) +		if (d->type_id == btype) +			return d; + +	return NULL; +} + +/* 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 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 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   = cx8802_remove, +}; + +module_pci_driver(cx8802_pci_driver); + +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..ed8cb9037b6 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-video.c @@ -0,0 +1,2041 @@ +/* + * + * 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->sd_wm8775) { +			call_all(core, audio, s_routing, +				 INPUT(input).audioroute, 0, 0); +		} +		/* cx2388's C-ADC is connected to the tuner only. +		   When used with S-Video, that ADC is busy dealing with +		   chroma, so an external must be used for baseband audio */ +		if (INPUT(input).type != CX88_VMUX_TELEVISION && +		    INPUT(input).type != CX88_VMUX_CABLE) { +			/* "I2S ADC mode" */ +			core->tvaudio = WW_I2SADC; +			cx88_set_tvaudio(core); +		} else { +			/* Normal mode */ +			cx_write(AUD_I2SCNTL, 0x0); +			cx_clear(AUD_CTL, EN_I2SIN_ENABLE); +		} +	} + +	return 0; +} +EXPORT_SYMBOL(cx88_video_mux); + +/* ------------------------------------------------------------------ */ + +static int start_video_dma(struct cx8800_dev    *dev, +			   struct cx88_dmaqueue *q, +			   struct cx88_buffer   *buf) +{ +	struct cx88_core *core = dev->core; + +	/* setup fifo + format */ +	cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21], +				buf->bpl, buf->risc.dma); +	cx88_set_scale(core, 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->sd_wm8775) { +				call_all(core, audio, s_routing, +					core->board.radio.audioroute, 0, 0); +			} +			/* "I2S ADC mode" */ +			core->tvaudio = WW_I2SADC; +			cx88_set_tvaudio(core); +		} else { +			/* FM Mode */ +			core->tvaudio = WW_FM; +			cx88_set_tvaudio(core); +			cx88_set_stereo(core,V4L2_TUNER_MODE_STEREO,1); +		} +		call_all(core, tuner, s_radio); +	} + +	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->sd_wm8775) { +		switch (ctrl->id) { +		case V4L2_CID_AUDIO_MUTE: +			wm8775_s_ctrl(core, ctrl->id, ctrl->val); +			break; +		case V4L2_CID_AUDIO_VOLUME: +			wm8775_s_ctrl(core, ctrl->id, (ctrl->val) ? +						(0x90 + ctrl->val) << 8 : 0); +			break; +		case V4L2_CID_AUDIO_BALANCE: +			wm8775_s_ctrl(core, ctrl->id, ctrl->val << 9); +			break; +		default: +			break; +		} +	} + +	mask = cc->mask; +	switch (ctrl->id) { +	case V4L2_CID_AUDIO_BALANCE: +		value = (ctrl->val < 0x40) ? (0x7f - ctrl->val) : (ctrl->val - 0x40); +		break; +	case V4L2_CID_AUDIO_VOLUME: +		value = 0x3f - (ctrl->val & 0x3f); +		break; +	default: +		value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; +		break; +	} +	dprintk(1,"set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n", +				ctrl->id, ctrl->name, ctrl->val, cc->reg, value, +				mask, cc->sreg ? " [shadowed]" : ""); +	if (cc->sreg) +		cx_sandor(cc->sreg, cc->reg, mask, value); +	else +		cx_andor(cc->reg, mask, value); +	return 0; +} + +/* ------------------------------------------------------------------ */ +/* VIDEO IOCTLS                                                       */ + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, +					struct v4l2_format *f) +{ +	struct cx8800_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, +				const 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, +				const struct v4l2_frequency *f) +{ +	struct v4l2_frequency new_freq = *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, &new_freq); +	core->freq = new_freq.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, +				const struct v4l2_frequency *f) +{ +	struct cx8800_fh  *fh   = priv; +	struct cx88_core  *core = fh->dev->core; + +	return cx88_set_freq(core, f); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register (struct file *file, void *fh, +				struct v4l2_dbg_register *reg) +{ +	struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core; + +	/* cx2388x has a 24-bit register space */ +	reg->val = cx_read(reg->reg & 0xfffffc); +	reg->size = 4; +	return 0; +} + +static int vidioc_s_register (struct file *file, void *fh, +				const struct v4l2_dbg_register *reg) +{ +	struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core; + +	cx_write(reg->reg & 0xfffffc, reg->val); +	return 0; +} +#endif + +/* ----------------------------------------------------------- */ +/* RADIO ESPECIFIC IOCTLS                                      */ +/* ----------------------------------------------------------- */ + +static int radio_g_tuner (struct file *file, void *priv, +				struct v4l2_tuner *t) +{ +	struct 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; +} + +static int radio_s_tuner (struct file *file, void *priv, +				const struct v4l2_tuner *t) +{ +	struct cx88_core  *core = ((struct cx8800_fh *)priv)->dev->core; + +	if (0 != t->index) +		return -EINVAL; + +	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, +#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, +#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, +#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 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, 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 == CX88_AUDIO_WM8775) { +		struct i2c_board_info wm8775_info = { +			.type = "wm8775", +			.addr = 0x36 >> 1, +			.platform_data = &core->wm8775_data, +		}; +		struct v4l2_subdev *sd; + +		if (core->boardnr == CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1) +			core->wm8775_data.is_nova_s = true; +		else +			core->wm8775_data.is_nova_s = false; + +		sd = v4l2_i2c_new_subdev_board(&core->v4l2_dev, &core->i2c_adap, +				&wm8775_info, NULL); +		if (sd != NULL) { +			core->sd_wm8775 = sd; +			sd->grp_id = WM8775_GID; +		} +	} + +	if (core->board.audio_chip == CX88_AUDIO_TVAUDIO) { +		/* This probes for a tda9874 as is used on some +		   Pixelview Ultra boards. */ +		v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap, +				"tvaudio", 0, I2C_ADDRS(0xb0 >> 1)); +	} + +	switch (core->boardnr) { +	case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: +	case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: { +		static const struct i2c_board_info rtc_info = { +			I2C_BOARD_INFO("isl1208", 0x6f) +		}; + +		request_module("rtc-isl1208"); +		core->i2c_rtc = i2c_new_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 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); + +	/* 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; +	unsigned long flags; + +	/* stop video+vbi capture */ +	spin_lock_irqsave(&dev->slock, flags); +	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_irqrestore(&dev->slock, flags); + +	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; +	unsigned long flags; +	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_irqsave(&dev->slock, flags); +	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_irqrestore(&dev->slock, flags); + +	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   = cx8800_finidev, +#ifdef CONFIG_PM +	.suspend  = cx8800_suspend, +	.resume   = cx8800_resume, +#endif +}; + +module_pci_driver(cx8800_pci_driver); diff --git a/drivers/media/pci/cx88/cx88-vp3054-i2c.c b/drivers/media/pci/cx88/cx88-vp3054-i2c.c new file mode 100644 index 00000000000..deede6e25d9 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-vp3054-i2c.c @@ -0,0 +1,158 @@ +/* + +    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; + +	vp3054_i2c->algo = vp3054_i2c_algo_template; + +	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..95d0c60a35e --- /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 IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) +int  vp3054_i2c_probe(struct cx8802_dev *dev); +void vp3054_i2c_remove(struct cx8802_dev *dev); +#else +static inline int  vp3054_i2c_probe(struct cx8802_dev *dev) +{ return 0; } +static inline void vp3054_i2c_remove(struct cx8802_dev *dev) +{ } +#endif diff --git a/drivers/media/pci/cx88/cx88.h b/drivers/media/pci/cx88/cx88.h new file mode 100644 index 00000000000..28893a6b249 --- /dev/null +++ b/drivers/media/pci/cx88/cx88.h @@ -0,0 +1,753 @@ +/* + * + * 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/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 inline unsigned int norm_maxw(v4l2_std_id norm) +{ +	return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 720 : 768; +} + + +static inline unsigned int 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; +}; + +enum cx88_audio_chip { +	CX88_AUDIO_WM8775 = 1, +	CX88_AUDIO_TVAUDIO, +}; + +struct cx88_board { +	const char              *name; +	unsigned int            tuner_type; +	unsigned int		radio_type; +	unsigned char		tuner_addr; +	unsigned char		radio_addr; +	int                     tda9887_conf; +	struct cx88_input       input[MAX_CX88_INPUT]; +	struct cx88_input       radio; +	enum cx88_board_type    mpeg; +	enum cx88_audio_chip	audio_chip; +	int			num_frontends; + +	/* Used for I2S devices */ +	int			i2sinputcntl; +}; + +struct cx88_subid { +	u16     subvendor; +	u16     subdevice; +	u32     card; +}; + +enum cx88_tvaudio { +	WW_NONE = 1, +	WW_BTSC, +	WW_BG, +	WW_DK, +	WW_I, +	WW_L, +	WW_EIAJ, +	WW_I2SPT, +	WW_FM, +	WW_I2SADC, +	WW_M +}; + +#define INPUT(nr) (core->board.input[nr]) + +/* ----------------------------------------------------------- */ +/* device / file handle status                                 */ + +#define RESOURCE_OVERLAY       1 +#define RESOURCE_VIDEO         2 +#define RESOURCE_VBI           4 + +#define BUFFER_TIMEOUT     msecs_to_jiffies(2000) + +/* 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]; +	u32			   model; + +	/* pci stuff */ +	int                        pci_bus; +	int                        pci_slot; +	u32                        __iomem *lmmio; +	u8                         __iomem *bmmio; +	u32                        shadow[SHADOW_MAX]; +	int                        pci_irqmask; + +	/* i2c i/o */ +	struct i2c_adapter         i2c_adap; +	struct i2c_algo_bit_data   i2c_algo; +	struct i2c_client          i2c_client; +	u32                        i2c_state, i2c_rc; + +	/* config info -- analog */ +	struct v4l2_device 	   v4l2_dev; +	struct v4l2_ctrl_handler   video_hdl; +	struct v4l2_ctrl	   *chroma_agc; +	struct v4l2_ctrl_handler   audio_hdl; +	struct v4l2_subdev	   *sd_wm8775; +	struct i2c_client 	   *i2c_rtc; +	unsigned int               boardnr; +	struct cx88_board	   board; + +	/* Supported V4L _STD_ tuner formats */ +	unsigned int               tuner_formats; + +	/* config info -- dvb */ +#if IS_ENABLED(CONFIG_VIDEO_CX88_DVB) +	int 			   (*prev_set_voltage)(struct dvb_frontend *fe, 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 IS_ENABLED(CONFIG_VIDEO_CX88_BLACKBIRD) +	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 IS_ENABLED(CONFIG_VIDEO_CX88_DVB) +	/* for dvb only */ +	struct videobuf_dvb_frontends frontends; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) +	/* For VP3045 secondary I2C bus support */ +	struct vp3054_i2c_state	   *vp3054; +#endif +	/* for switching modulation types */ +	unsigned char              ts_gen_cntrl; + +	/* List of attached drivers; must hold core->lock to access */ +	struct list_head	   drvlist; + +	struct work_struct	   request_module_wk; +}; + +/* ----------------------------------------------------------- */ + +#define cx_read(reg)             readl(core->lmmio + ((reg)>>2)) +#define cx_write(reg,value)      writel((value), core->lmmio + ((reg)>>2)) +#define cx_writeb(reg,value)     writeb((value), core->bmmio + (reg)) + +#define cx_andor(reg,mask,value) \ +  writel((readl(core->lmmio+((reg)>>2)) & ~(mask)) |\ +  ((value) & (mask)), core->lmmio+((reg)>>2)) +#define cx_set(reg,bit)          cx_andor((reg),(bit),(bit)) +#define cx_clear(reg,bit)        cx_andor((reg),(bit),0) + +#define cx_wait(d) { if (need_resched()) schedule(); else udelay(d); } + +/* shadow registers */ +#define cx_sread(sreg)		    (core->shadow[sreg]) +#define cx_swrite(sreg,reg,value) \ +  (core->shadow[sreg] = value, \ +   writel(core->shadow[sreg], core->lmmio + ((reg)>>2))) +#define cx_sandor(sreg,reg,mask,value) \ +  (core->shadow[sreg] = (core->shadow[sreg] & ~(mask)) | ((value) & (mask)), \ +   writel(core->shadow[sreg], core->lmmio + ((reg)>>2))) + +/* ----------------------------------------------------------- */ +/* cx88-core.c                                                 */ + +extern unsigned int cx88_core_debug; + +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, const 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);  | 
