diff options
Diffstat (limited to 'drivers/media/usb/tm6000')
| -rw-r--r-- | drivers/media/usb/tm6000/Kconfig | 33 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/Makefile | 15 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-alsa.c | 529 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-cards.c | 1413 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-core.c | 938 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-dvb.c | 467 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-i2c.c | 334 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-input.c | 501 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-regs.h | 600 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-stds.c | 638 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-usb-isoc.h | 50 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000-video.c | 1760 | ||||
| -rw-r--r-- | drivers/media/usb/tm6000/tm6000.h | 409 | 
13 files changed, 7687 insertions, 0 deletions
diff --git a/drivers/media/usb/tm6000/Kconfig b/drivers/media/usb/tm6000/Kconfig new file mode 100644 index 00000000000..a43b77abd93 --- /dev/null +++ b/drivers/media/usb/tm6000/Kconfig @@ -0,0 +1,33 @@ +config VIDEO_TM6000 +	tristate "TV Master TM5600/6000/6010 driver" +	depends on VIDEO_DEV && I2C && INPUT && RC_CORE && USB +	select VIDEO_TUNER +	select MEDIA_TUNER_XC2028 +	select MEDIA_TUNER_XC5000 +	select VIDEOBUF_VMALLOC +	help +	  Support for TM5600/TM6000/TM6010 USB Device + +	  Since these cards have no MPEG decoder onboard, they transmit +	  only compressed MPEG data over the usb bus, so you need +	  an external software decoder to watch TV on your computer. + +	  Say Y if you own such a device and want to use it. + +config VIDEO_TM6000_ALSA +	tristate "TV Master TM5600/6000/6010 audio support" +	depends on VIDEO_TM6000 && SND +	select SND_PCM +	---help--- +	  This is a video4linux driver for direct (DMA) audio for +	  TM5600/TM6000/TM6010 USB Devices. + +	  To compile this driver as a module, choose M here: the +	  module will be called tm6000-alsa. + +config VIDEO_TM6000_DVB +	tristate "DVB Support for tm6000 based TV cards" +	depends on VIDEO_TM6000 && DVB_CORE && USB +	select DVB_ZL10353 +	---help--- +	  This adds support for DVB cards based on the tm5600/tm6000 chip. diff --git a/drivers/media/usb/tm6000/Makefile b/drivers/media/usb/tm6000/Makefile new file mode 100644 index 00000000000..f2644933b8d --- /dev/null +++ b/drivers/media/usb/tm6000/Makefile @@ -0,0 +1,15 @@ +tm6000-y := tm6000-cards.o \ +		   tm6000-core.o  \ +		   tm6000-i2c.o   \ +		   tm6000-video.o \ +		   tm6000-stds.o \ +		   tm6000-input.o + +obj-$(CONFIG_VIDEO_TM6000) += tm6000.o +obj-$(CONFIG_VIDEO_TM6000_ALSA) += tm6000-alsa.o +obj-$(CONFIG_VIDEO_TM6000_DVB) += tm6000-dvb.o + +ccflags-y += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/tuners +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/dvb-frontends diff --git a/drivers/media/usb/tm6000/tm6000-alsa.c b/drivers/media/usb/tm6000/tm6000-alsa.c new file mode 100644 index 00000000000..74e5697d867 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-alsa.c @@ -0,0 +1,529 @@ +/* + * + *  Support for audio capture for tm5600/6000/6010 + *    (c) 2007-2008 Mauro Carvalho Chehab + * + *  Based on cx88-alsa.c + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License version 2 as + *  published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/control.h> +#include <sound/initval.h> + + +#include "tm6000.h" +#include "tm6000-regs.h" + +#undef dprintk + +#define dprintk(level, fmt, arg...) do {				   \ +	if (debug >= level)						   \ +		printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg); \ +	} while (0) + +/**************************************************************************** +			Module global static vars + ****************************************************************************/ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */ + +static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable tm6000x soundcard. default enabled."); + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for tm6000x capture interface(s)."); + + +/**************************************************************************** +				Module macros + ****************************************************************************/ + +MODULE_DESCRIPTION("ALSA driver module for tm5600/tm6000/tm6010 based TV cards"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Trident,tm5600}," +			"{{Trident,tm6000}," +			"{{Trident,tm6010}"); +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 _tm6000_start_audio_dma(struct snd_tm6000_card *chip) +{ +	struct tm6000_core *core = chip->core; + +	dprintk(1, "Starting audio DMA\n"); + +	/* Enables audio */ +	tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x40, 0x40); + +	tm6000_set_audio_bitrate(core, 48000); + +	return 0; +} + +/* + * BOARD Specific: Resets audio DMA + */ +static int _tm6000_stop_audio_dma(struct snd_tm6000_card *chip) +{ +	struct tm6000_core *core = chip->core; + +	dprintk(1, "Stopping audio DMA\n"); + +	/* Disables audio */ +	tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x00, 0x40); + +	return 0; +} + +static void dsp_buffer_free(struct snd_pcm_substream *substream) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + +	dprintk(2, "Freeing buffer\n"); + +	vfree(substream->runtime->dma_area); +	substream->runtime->dma_area = NULL; +	substream->runtime->dma_bytes = 0; +} + +static int dsp_buffer_alloc(struct snd_pcm_substream *substream, int size) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + +	dprintk(2, "Allocating buffer\n"); + +	if (substream->runtime->dma_area) { +		if (substream->runtime->dma_bytes > size) +			return 0; + +		dsp_buffer_free(substream); +	} + +	substream->runtime->dma_area = vmalloc(size); +	if (!substream->runtime->dma_area) +		return -ENOMEM; + +	substream->runtime->dma_bytes = size; + +	return 0; +} + + +/**************************************************************************** +				ALSA PCM Interface + ****************************************************************************/ + +/* + * Digital hardware definition + */ +#define DEFAULT_FIFO_SIZE	4096 + +static struct snd_pcm_hardware snd_tm6000_digital_hw = { +	.info = SNDRV_PCM_INFO_BATCH | +		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_CONTINUOUS | SNDRV_PCM_RATE_KNOT, +	.rate_min = 48000, +	.rate_max = 48000, +	.channels_min = 2, +	.channels_max = 2, +	.period_bytes_min = 64, +	.period_bytes_max = 12544, +	.periods_min = 2, +	.periods_max = 98, +	.buffer_bytes_max = 62720 * 8, +}; + +/* + * audio pcm capture open callback + */ +static int snd_tm6000_pcm_open(struct snd_pcm_substream *substream) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_runtime *runtime = substream->runtime; +	int err; + +	err = snd_pcm_hw_constraint_pow2(runtime, 0, +					 SNDRV_PCM_HW_PARAM_PERIODS); +	if (err < 0) +		goto _error; + +	chip->substream = substream; + +	runtime->hw = snd_tm6000_digital_hw; +	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + +	return 0; +_error: +	dprintk(1, "Error opening PCM!\n"); +	return err; +} + +/* + * audio close callback + */ +static int snd_tm6000_close(struct snd_pcm_substream *substream) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); +	struct tm6000_core *core = chip->core; + +	if (atomic_read(&core->stream_started) > 0) { +		atomic_set(&core->stream_started, 0); +		schedule_work(&core->wq_trigger); +	} + +	return 0; +} + +static int tm6000_fillbuf(struct tm6000_core *core, char *buf, int size) +{ +	struct snd_tm6000_card *chip = core->adev; +	struct snd_pcm_substream *substream = chip->substream; +	struct snd_pcm_runtime *runtime; +	int period_elapsed = 0; +	unsigned int stride, buf_pos; +	int length; + +	if (atomic_read(&core->stream_started) == 0) +		return 0; + +	if (!size || !substream) { +		dprintk(1, "substream was NULL\n"); +		return -EINVAL; +	} + +	runtime = substream->runtime; +	if (!runtime || !runtime->dma_area) { +		dprintk(1, "runtime was NULL\n"); +		return -EINVAL; +	} + +	buf_pos = chip->buf_pos; +	stride = runtime->frame_bits >> 3; + +	if (stride == 0) { +		dprintk(1, "stride is zero\n"); +		return -EINVAL; +	} + +	length = size / stride; +	if (length == 0) { +		dprintk(1, "%s: length was zero\n", __func__); +		return -EINVAL; +	} + +	dprintk(1, "Copying %d bytes at %p[%d] - buf size=%d x %d\n", size, +		runtime->dma_area, buf_pos, +		(unsigned int)runtime->buffer_size, stride); + +	if (buf_pos + length >= runtime->buffer_size) { +		unsigned int cnt = runtime->buffer_size - buf_pos; +		memcpy(runtime->dma_area + buf_pos * stride, buf, cnt * stride); +		memcpy(runtime->dma_area, buf + cnt * stride, +			length * stride - cnt * stride); +	} else +		memcpy(runtime->dma_area + buf_pos * stride, buf, +			length * stride); + +	snd_pcm_stream_lock(substream); + +	chip->buf_pos += length; +	if (chip->buf_pos >= runtime->buffer_size) +		chip->buf_pos -= runtime->buffer_size; + +	chip->period_pos += length; +	if (chip->period_pos >= runtime->period_size) { +		chip->period_pos -= runtime->period_size; +		period_elapsed = 1; +	} + +	snd_pcm_stream_unlock(substream); + +	if (period_elapsed) +		snd_pcm_period_elapsed(substream); + +	return 0; +} + +/* + * hw_params callback + */ +static int snd_tm6000_hw_params(struct snd_pcm_substream *substream, +			      struct snd_pcm_hw_params *hw_params) +{ +	int size, rc; + +	size = params_period_bytes(hw_params) * params_periods(hw_params); + +	rc = dsp_buffer_alloc(substream, size); +	if (rc < 0) +		return rc; + +	return 0; +} + +/* + * hw free callback + */ +static int snd_tm6000_hw_free(struct snd_pcm_substream *substream) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); +	struct tm6000_core *core = chip->core; + +	if (atomic_read(&core->stream_started) > 0) { +		atomic_set(&core->stream_started, 0); +		schedule_work(&core->wq_trigger); +	} + +	dsp_buffer_free(substream); +	return 0; +} + +/* + * prepare callback + */ +static int snd_tm6000_prepare(struct snd_pcm_substream *substream) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + +	chip->buf_pos = 0; +	chip->period_pos = 0; + +	return 0; +} + + +/* + * trigger callback + */ +static void audio_trigger(struct work_struct *work) +{ +	struct tm6000_core *core = container_of(work, struct tm6000_core, +						wq_trigger); +	struct snd_tm6000_card *chip = core->adev; + +	if (atomic_read(&core->stream_started)) { +		dprintk(1, "starting capture"); +		_tm6000_start_audio_dma(chip); +	} else { +		dprintk(1, "stopping capture"); +		_tm6000_stop_audio_dma(chip); +	} +} + +static int snd_tm6000_card_trigger(struct snd_pcm_substream *substream, int cmd) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); +	struct tm6000_core *core = chip->core; +	int err = 0; + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */ +	case SNDRV_PCM_TRIGGER_RESUME: /* fall through */ +	case SNDRV_PCM_TRIGGER_START: +		atomic_set(&core->stream_started, 1); +		break; +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */ +	case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */ +	case SNDRV_PCM_TRIGGER_STOP: +		atomic_set(&core->stream_started, 0); +		break; +	default: +		err = -EINVAL; +		break; +	} +	schedule_work(&core->wq_trigger); + +	return err; +} +/* + * pointer callback + */ +static snd_pcm_uframes_t snd_tm6000_pointer(struct snd_pcm_substream *substream) +{ +	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + +	return chip->buf_pos; +} + +static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, +					     unsigned long offset) +{ +	void *pageptr = subs->runtime->dma_area + offset; + +	return vmalloc_to_page(pageptr); +} + +/* + * operators + */ +static struct snd_pcm_ops snd_tm6000_pcm_ops = { +	.open = snd_tm6000_pcm_open, +	.close = snd_tm6000_close, +	.ioctl = snd_pcm_lib_ioctl, +	.hw_params = snd_tm6000_hw_params, +	.hw_free = snd_tm6000_hw_free, +	.prepare = snd_tm6000_prepare, +	.trigger = snd_tm6000_card_trigger, +	.pointer = snd_tm6000_pointer, +	.page = snd_pcm_get_vmalloc_page, +}; + +/* + * create a PCM device + */ + +/* FIXME: Control interface - How to control volume/mute? */ + +/**************************************************************************** +			Basic Flow for Sound Devices + ****************************************************************************/ + +/* + * Alsa Constructor - Component probe + */ +static int tm6000_audio_init(struct tm6000_core *dev) +{ +	struct snd_card		*card; +	struct snd_tm6000_card	*chip; +	int			rc; +	static int		devnr; +	char			component[14]; +	struct snd_pcm		*pcm; + +	if (!dev) +		return 0; + +	if (devnr >= SNDRV_CARDS) +		return -ENODEV; + +	if (!enable[devnr]) +		return -ENOENT; + +	rc = snd_card_new(&dev->udev->dev, index[devnr], "tm6000", +			  THIS_MODULE, 0, &card); +	if (rc < 0) { +		snd_printk(KERN_ERR "cannot create card instance %d\n", devnr); +		return rc; +	} +	strcpy(card->driver, "tm6000-alsa"); +	strcpy(card->shortname, "TM5600/60x0"); +	sprintf(card->longname, "TM5600/60x0 Audio at bus %d device %d", +		dev->udev->bus->busnum, dev->udev->devnum); + +	sprintf(component, "USB%04x:%04x", +		le16_to_cpu(dev->udev->descriptor.idVendor), +		le16_to_cpu(dev->udev->descriptor.idProduct)); +	snd_component_add(card, component); + +	chip = kzalloc(sizeof(struct snd_tm6000_card), GFP_KERNEL); +	if (!chip) { +		rc = -ENOMEM; +		goto error; +	} + +	chip->core = dev; +	chip->card = card; +	dev->adev = chip; +	spin_lock_init(&chip->reg_lock); + +	rc = snd_pcm_new(card, "TM6000 Audio", 0, 0, 1, &pcm); +	if (rc < 0) +		goto error_chip; + +	pcm->info_flags = 0; +	pcm->private_data = chip; +	strcpy(pcm->name, "Trident TM5600/60x0"); + +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_tm6000_pcm_ops); + +	INIT_WORK(&dev->wq_trigger, audio_trigger); +	rc = snd_card_register(card); +	if (rc < 0) +		goto error_chip; + +	dprintk(1, "Registered audio driver for %s\n", card->longname); + +	return 0; + +error_chip: +	kfree(chip); +	dev->adev = NULL; +error: +	snd_card_free(card); +	return rc; +} + +static int tm6000_audio_fini(struct tm6000_core *dev) +{ +	struct snd_tm6000_card *chip; + +	if (!dev) +		return 0; +	chip = dev->adev; + +	if (!chip) +		return 0; + +	if (!chip->card) +		return 0; + +	snd_card_free(chip->card); +	chip->card = NULL; +	kfree(chip); +	dev->adev = NULL; + +	return 0; +} + +static struct tm6000_ops audio_ops = { +	.type	= TM6000_AUDIO, +	.name	= "TM6000 Audio Extension", +	.init	= tm6000_audio_init, +	.fini	= tm6000_audio_fini, +	.fillbuf = tm6000_fillbuf, +}; + +static int __init tm6000_alsa_register(void) +{ +	return tm6000_register_extension(&audio_ops); +} + +static void __exit tm6000_alsa_unregister(void) +{ +	tm6000_unregister_extension(&audio_ops); +} + +module_init(tm6000_alsa_register); +module_exit(tm6000_alsa_unregister); diff --git a/drivers/media/usb/tm6000/tm6000-cards.c b/drivers/media/usb/tm6000/tm6000-cards.c new file mode 100644 index 00000000000..2e8c3afe4ec --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-cards.c @@ -0,0 +1,1413 @@ +/* + *  tm6000-cards.c - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.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 version 2 + * + *  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/i2c.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <media/v4l2-common.h> +#include <media/tuner.h> +#include <media/tvaudio.h> +#include <media/i2c-addr.h> +#include <media/rc-map.h> + +#include "tm6000.h" +#include "tm6000-regs.h" +#include "tuner-xc2028.h" +#include "xc5000.h" + +#define TM6000_BOARD_UNKNOWN			0 +#define TM5600_BOARD_GENERIC			1 +#define TM6000_BOARD_GENERIC			2 +#define TM6010_BOARD_GENERIC			3 +#define TM5600_BOARD_10MOONS_UT821		4 +#define TM5600_BOARD_10MOONS_UT330		5 +#define TM6000_BOARD_ADSTECH_DUAL_TV		6 +#define TM6000_BOARD_FREECOM_AND_SIMILAR	7 +#define TM6000_BOARD_ADSTECH_MINI_DUAL_TV	8 +#define TM6010_BOARD_HAUPPAUGE_900H		9 +#define TM6010_BOARD_BEHOLD_WANDER		10 +#define TM6010_BOARD_BEHOLD_VOYAGER		11 +#define TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE	12 +#define TM6010_BOARD_TWINHAN_TU501		13 +#define TM6010_BOARD_BEHOLD_WANDER_LITE		14 +#define TM6010_BOARD_BEHOLD_VOYAGER_LITE	15 +#define TM5600_BOARD_TERRATEC_GRABSTER		16 + +#define is_generic(model) ((model == TM6000_BOARD_UNKNOWN) || \ +			   (model == TM5600_BOARD_GENERIC) || \ +			   (model == TM6000_BOARD_GENERIC) || \ +			   (model == TM6010_BOARD_GENERIC)) + +#define TM6000_MAXBOARDS        16 +static unsigned int card[]     = {[0 ... (TM6000_MAXBOARDS - 1)] = UNSET }; + +module_param_array(card,  int, NULL, 0444); + +static unsigned long tm6000_devused; + + +struct tm6000_board { +	char            *name; +	char		eename[16];		/* EEPROM name */ +	unsigned	eename_size;		/* size of EEPROM name */ +	unsigned	eename_pos;		/* Position where it appears at ROM */ + +	struct tm6000_capabilities caps; + +	enum		tm6000_devtype type;	/* variant of the chipset */ +	int             tuner_type;     /* type of the tuner */ +	int             tuner_addr;     /* tuner address */ +	int             demod_addr;     /* demodulator address */ + +	struct tm6000_gpio gpio; + +	struct tm6000_input	vinput[3]; +	struct tm6000_input	rinput; + +	char		*ir_codes; +}; + +static struct tm6000_board tm6000_boards[] = { +	[TM6000_BOARD_UNKNOWN] = { +		.name         = "Unknown tm6000 video grabber", +		.caps = { +			.has_tuner	= 1, +			.has_eeprom	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6000_GPIO_1, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM5600_BOARD_GENERIC] = { +		.name         = "Generic tm5600 board", +		.type         = TM5600, +		.tuner_type   = TUNER_XC2028, +		.tuner_addr   = 0xc2 >> 1, +		.caps = { +			.has_tuner	= 1, +			.has_eeprom	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6000_GPIO_1, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6000_BOARD_GENERIC] = { +		.name         = "Generic tm6000 board", +		.tuner_type   = TUNER_XC2028, +		.tuner_addr   = 0xc2 >> 1, +		.caps = { +			.has_tuner	= 1, +			.has_eeprom	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6000_GPIO_1, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6010_BOARD_GENERIC] = { +		.name         = "Generic tm6010 board", +		.type         = TM6010, +		.tuner_type   = TUNER_XC2028, +		.tuner_addr   = 0xc2 >> 1, +		.demod_addr   = 0x1e >> 1, +		.caps = { +			.has_tuner	= 1, +			.has_dvb	= 1, +			.has_zl10353	= 1, +			.has_eeprom	= 1, +			.has_remote	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_2, +			.tuner_on	= TM6010_GPIO_3, +			.demod_reset	= TM6010_GPIO_1, +			.demod_on	= TM6010_GPIO_4, +			.power_led	= TM6010_GPIO_7, +			.dvb_led	= TM6010_GPIO_5, +			.ir		= TM6010_GPIO_0, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM5600_BOARD_10MOONS_UT821] = { +		.name         = "10Moons UT 821", +		.tuner_type   = TUNER_XC2028, +		.eename       = { '1', '0', 'M', 'O', 'O', 'N', 'S', '5', '6', '0', '0', 0xff, 0x45, 0x5b}, +		.eename_size  = 14, +		.eename_pos   = 0x14, +		.type         = TM5600, +		.tuner_addr   = 0xc2 >> 1, +		.caps = { +			.has_tuner    = 1, +			.has_eeprom   = 1, +		}, +		.gpio = { +			.tuner_reset	= TM6000_GPIO_1, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM5600_BOARD_10MOONS_UT330] = { +		.name         = "10Moons UT 330", +		.tuner_type   = TUNER_PHILIPS_FQ1216AME_MK4, +		.tuner_addr   = 0xc8 >> 1, +		.caps = { +			.has_tuner    = 1, +			.has_dvb      = 0, +			.has_zl10353  = 0, +			.has_eeprom   = 1, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6000_BOARD_ADSTECH_DUAL_TV] = { +		.name         = "ADSTECH Dual TV USB", +		.tuner_type   = TUNER_XC2028, +		.tuner_addr   = 0xc8 >> 1, +		.caps = { +			.has_tuner    = 1, +			.has_tda9874  = 1, +			.has_dvb      = 1, +			.has_zl10353  = 1, +			.has_eeprom   = 1, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6000_BOARD_FREECOM_AND_SIMILAR] = { +		.name         = "Freecom Hybrid Stick / Moka DVB-T Receiver Dual", +		.tuner_type   = TUNER_XC2028, /* has a XC3028 */ +		.tuner_addr   = 0xc2 >> 1, +		.demod_addr   = 0x1e >> 1, +		.caps = { +			.has_tuner    = 1, +			.has_dvb      = 1, +			.has_zl10353  = 1, +			.has_eeprom   = 0, +			.has_remote   = 1, +		}, +		.gpio = { +			.tuner_reset	= TM6000_GPIO_4, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6000_BOARD_ADSTECH_MINI_DUAL_TV] = { +		.name         = "ADSTECH Mini Dual TV USB", +		.tuner_type   = TUNER_XC2028, /* has a XC3028 */ +		.tuner_addr   = 0xc8 >> 1, +		.demod_addr   = 0x1e >> 1, +		.caps = { +			.has_tuner    = 1, +			.has_dvb      = 1, +			.has_zl10353  = 1, +			.has_eeprom   = 0, +		}, +		.gpio = { +			.tuner_reset	= TM6000_GPIO_4, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6010_BOARD_HAUPPAUGE_900H] = { +		.name         = "Hauppauge WinTV HVR-900H / WinTV USB2-Stick", +		.eename       = { 'H', 0, 'V', 0, 'R', 0, '9', 0, '0', 0, '0', 0, 'H', 0 }, +		.eename_size  = 14, +		.eename_pos   = 0x42, +		.tuner_type   = TUNER_XC2028, /* has a XC3028 */ +		.tuner_addr   = 0xc2 >> 1, +		.demod_addr   = 0x1e >> 1, +		.type         = TM6010, +		.ir_codes = RC_MAP_HAUPPAUGE, +		.caps = { +			.has_tuner    = 1, +			.has_dvb      = 1, +			.has_zl10353  = 1, +			.has_eeprom   = 1, +			.has_remote   = 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_2, +			.tuner_on	= TM6010_GPIO_3, +			.demod_reset	= TM6010_GPIO_1, +			.demod_on	= TM6010_GPIO_4, +			.power_led	= TM6010_GPIO_7, +			.dvb_led	= TM6010_GPIO_5, +			.ir		= TM6010_GPIO_0, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6010_BOARD_BEHOLD_WANDER] = { +		.name         = "Beholder Wander DVB-T/TV/FM USB2.0", +		.tuner_type   = TUNER_XC5000, +		.tuner_addr   = 0xc2 >> 1, +		.demod_addr   = 0x1e >> 1, +		.type         = TM6010, +		.caps = { +			.has_tuner      = 1, +			.has_dvb        = 1, +			.has_zl10353    = 1, +			.has_eeprom     = 1, +			.has_remote     = 1, +			.has_radio	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_0, +			.demod_reset	= TM6010_GPIO_1, +			.power_led	= TM6010_GPIO_6, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +		.rinput = { +			.type	= TM6000_INPUT_RADIO, +			.amux	= TM6000_AMUX_ADC1, +		}, +	}, +	[TM6010_BOARD_BEHOLD_VOYAGER] = { +		.name         = "Beholder Voyager TV/FM USB2.0", +		.tuner_type   = TUNER_XC5000, +		.tuner_addr   = 0xc2 >> 1, +		.type         = TM6010, +		.caps = { +			.has_tuner      = 1, +			.has_dvb        = 0, +			.has_zl10353    = 0, +			.has_eeprom     = 1, +			.has_remote     = 1, +			.has_radio	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_0, +			.power_led	= TM6010_GPIO_6, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +		.rinput = { +			.type	= TM6000_INPUT_RADIO, +			.amux	= TM6000_AMUX_ADC1, +		}, +	}, +	[TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE] = { +		.name         = "Terratec Cinergy Hybrid XE / Cinergy Hybrid-Stick", +		.tuner_type   = TUNER_XC2028, /* has a XC3028 */ +		.tuner_addr   = 0xc2 >> 1, +		.demod_addr   = 0x1e >> 1, +		.type         = TM6010, +		.caps = { +			.has_tuner    = 1, +			.has_dvb      = 1, +			.has_zl10353  = 1, +			.has_eeprom   = 1, +			.has_remote   = 1, +			.has_radio    = 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_2, +			.tuner_on	= TM6010_GPIO_3, +			.demod_reset	= TM6010_GPIO_1, +			.demod_on	= TM6010_GPIO_4, +			.power_led	= TM6010_GPIO_7, +			.dvb_led	= TM6010_GPIO_5, +			.ir		= TM6010_GPIO_0, +		}, +		.ir_codes = RC_MAP_NEC_TERRATEC_CINERGY_XS, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +		.rinput = { +			.type = TM6000_INPUT_RADIO, +			.amux = TM6000_AMUX_SIF1, +		}, +	}, +	[TM5600_BOARD_TERRATEC_GRABSTER] = { +		.name         = "Terratec Grabster AV 150/250 MX", +		.type         = TM5600, +		.tuner_type   = TUNER_ABSENT, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_ADC1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6010_BOARD_TWINHAN_TU501] = { +		.name         = "Twinhan TU501(704D1)", +		.tuner_type   = TUNER_XC2028, /* has a XC3028 */ +		.tuner_addr   = 0xc2 >> 1, +		.demod_addr   = 0x1e >> 1, +		.type         = TM6010, +		.caps = { +			.has_tuner    = 1, +			.has_dvb      = 1, +			.has_zl10353  = 1, +			.has_eeprom   = 1, +			.has_remote   = 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_2, +			.tuner_on	= TM6010_GPIO_3, +			.demod_reset	= TM6010_GPIO_1, +			.demod_on	= TM6010_GPIO_4, +			.power_led	= TM6010_GPIO_7, +			.dvb_led	= TM6010_GPIO_5, +			.ir		= TM6010_GPIO_0, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, { +			.type	= TM6000_INPUT_COMPOSITE1, +			.vmux	= TM6000_VMUX_VIDEO_A, +			.amux	= TM6000_AMUX_ADC2, +			}, { +			.type	= TM6000_INPUT_SVIDEO, +			.vmux	= TM6000_VMUX_VIDEO_AB, +			.amux	= TM6000_AMUX_ADC2, +			}, +		}, +	}, +	[TM6010_BOARD_BEHOLD_WANDER_LITE] = { +		.name         = "Beholder Wander Lite DVB-T/TV/FM USB2.0", +		.tuner_type   = TUNER_XC5000, +		.tuner_addr   = 0xc2 >> 1, +		.demod_addr   = 0x1e >> 1, +		.type         = TM6010, +		.caps = { +			.has_tuner      = 1, +			.has_dvb        = 1, +			.has_zl10353    = 1, +			.has_eeprom     = 1, +			.has_remote     = 0, +			.has_radio	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_0, +			.demod_reset	= TM6010_GPIO_1, +			.power_led	= TM6010_GPIO_6, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, +		}, +		.rinput = { +			.type	= TM6000_INPUT_RADIO, +			.amux	= TM6000_AMUX_ADC1, +		}, +	}, +	[TM6010_BOARD_BEHOLD_VOYAGER_LITE] = { +		.name         = "Beholder Voyager Lite TV/FM USB2.0", +		.tuner_type   = TUNER_XC5000, +		.tuner_addr   = 0xc2 >> 1, +		.type         = TM6010, +		.caps = { +			.has_tuner      = 1, +			.has_dvb        = 0, +			.has_zl10353    = 0, +			.has_eeprom     = 1, +			.has_remote     = 0, +			.has_radio	= 1, +		}, +		.gpio = { +			.tuner_reset	= TM6010_GPIO_0, +			.power_led	= TM6010_GPIO_6, +		}, +		.vinput = { { +			.type	= TM6000_INPUT_TV, +			.vmux	= TM6000_VMUX_VIDEO_B, +			.amux	= TM6000_AMUX_SIF1, +			}, +		}, +		.rinput = { +			.type	= TM6000_INPUT_RADIO, +			.amux	= TM6000_AMUX_ADC1, +		}, +	}, +}; + +/* table of devices that work with this driver */ +static struct usb_device_id tm6000_id_table[] = { +	{ USB_DEVICE(0x6000, 0x0001), .driver_info = TM5600_BOARD_GENERIC }, +	{ USB_DEVICE(0x6000, 0x0002), .driver_info = TM6010_BOARD_GENERIC }, +	{ USB_DEVICE(0x06e1, 0xf332), .driver_info = TM6000_BOARD_ADSTECH_DUAL_TV }, +	{ USB_DEVICE(0x14aa, 0x0620), .driver_info = TM6000_BOARD_FREECOM_AND_SIMILAR }, +	{ USB_DEVICE(0x06e1, 0xb339), .driver_info = TM6000_BOARD_ADSTECH_MINI_DUAL_TV }, +	{ USB_DEVICE(0x2040, 0x6600), .driver_info = TM6010_BOARD_HAUPPAUGE_900H }, +	{ USB_DEVICE(0x2040, 0x6601), .driver_info = TM6010_BOARD_HAUPPAUGE_900H }, +	{ USB_DEVICE(0x2040, 0x6610), .driver_info = TM6010_BOARD_HAUPPAUGE_900H }, +	{ USB_DEVICE(0x2040, 0x6611), .driver_info = TM6010_BOARD_HAUPPAUGE_900H }, +	{ USB_DEVICE(0x6000, 0xdec0), .driver_info = TM6010_BOARD_BEHOLD_WANDER }, +	{ USB_DEVICE(0x6000, 0xdec1), .driver_info = TM6010_BOARD_BEHOLD_VOYAGER }, +	{ USB_DEVICE(0x0ccd, 0x0086), .driver_info = TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE }, +	{ USB_DEVICE(0x0ccd, 0x00A5), .driver_info = TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE }, +	{ USB_DEVICE(0x0ccd, 0x0079), .driver_info = TM5600_BOARD_TERRATEC_GRABSTER }, +	{ USB_DEVICE(0x13d3, 0x3240), .driver_info = TM6010_BOARD_TWINHAN_TU501 }, +	{ USB_DEVICE(0x13d3, 0x3241), .driver_info = TM6010_BOARD_TWINHAN_TU501 }, +	{ USB_DEVICE(0x13d3, 0x3243), .driver_info = TM6010_BOARD_TWINHAN_TU501 }, +	{ USB_DEVICE(0x13d3, 0x3264), .driver_info = TM6010_BOARD_TWINHAN_TU501 }, +	{ USB_DEVICE(0x6000, 0xdec2), .driver_info = TM6010_BOARD_BEHOLD_WANDER_LITE }, +	{ USB_DEVICE(0x6000, 0xdec3), .driver_info = TM6010_BOARD_BEHOLD_VOYAGER_LITE }, +	{ } +}; +MODULE_DEVICE_TABLE(usb, tm6000_id_table); + +/* Control power led for show some activity */ +void tm6000_flash_led(struct tm6000_core *dev, u8 state) +{ +	/* Power LED unconfigured */ +	if (!dev->gpio.power_led) +		return; + +	/* ON Power LED */ +	if (state) { +		switch (dev->model) { +		case TM6010_BOARD_HAUPPAUGE_900H: +		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: +		case TM6010_BOARD_TWINHAN_TU501: +			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +				dev->gpio.power_led, 0x00); +			break; +		case TM6010_BOARD_BEHOLD_WANDER: +		case TM6010_BOARD_BEHOLD_VOYAGER: +		case TM6010_BOARD_BEHOLD_WANDER_LITE: +		case TM6010_BOARD_BEHOLD_VOYAGER_LITE: +			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +				dev->gpio.power_led, 0x01); +			break; +		} +	} +	/* OFF Power LED */ +	else { +		switch (dev->model) { +		case TM6010_BOARD_HAUPPAUGE_900H: +		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: +		case TM6010_BOARD_TWINHAN_TU501: +			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +				dev->gpio.power_led, 0x01); +			break; +		case TM6010_BOARD_BEHOLD_WANDER: +		case TM6010_BOARD_BEHOLD_VOYAGER: +		case TM6010_BOARD_BEHOLD_WANDER_LITE: +		case TM6010_BOARD_BEHOLD_VOYAGER_LITE: +			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +				dev->gpio.power_led, 0x00); +			break; +		} +	} +} + +/* Tuner callback to provide the proper gpio changes needed for xc5000 */ +int tm6000_xc5000_callback(void *ptr, int component, int command, int arg) +{ +	int rc = 0; +	struct tm6000_core *dev = ptr; + +	if (dev->tuner_type != TUNER_XC5000) +		return 0; + +	switch (command) { +	case XC5000_TUNER_RESET: +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +			       dev->gpio.tuner_reset, 0x01); +		msleep(15); +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +			       dev->gpio.tuner_reset, 0x00); +		msleep(15); +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +			       dev->gpio.tuner_reset, 0x01); +		break; +	} +	return rc; +} +EXPORT_SYMBOL_GPL(tm6000_xc5000_callback); + +/* Tuner callback to provide the proper gpio changes needed for xc2028 */ + +int tm6000_tuner_callback(void *ptr, int component, int command, int arg) +{ +	int rc = 0; +	struct tm6000_core *dev = ptr; + +	if (dev->tuner_type != TUNER_XC2028) +		return 0; + +	switch (command) { +	case XC2028_RESET_CLK: +		tm6000_ir_wait(dev, 0); + +		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, +					0x02, arg); +		msleep(10); +		rc = tm6000_i2c_reset(dev, 10); +		break; +	case XC2028_TUNER_RESET: +		/* Reset codes during load firmware */ +		switch (arg) { +		case 0: +			/* newer tuner can faster reset */ +			switch (dev->model) { +			case TM5600_BOARD_10MOONS_UT821: +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x01); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       0x300, 0x01); +				msleep(10); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x00); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       0x300, 0x00); +				msleep(10); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x01); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       0x300, 0x01); +				break; +			case TM6010_BOARD_HAUPPAUGE_900H: +			case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: +			case TM6010_BOARD_TWINHAN_TU501: +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x01); +				msleep(60); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x00); +				msleep(75); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x01); +				msleep(60); +				break; +			default: +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x00); +				msleep(130); +				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +					       dev->gpio.tuner_reset, 0x01); +				msleep(130); +				break; +			} + +			tm6000_ir_wait(dev, 1); +			break; +		case 1: +			tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, +						0x02, 0x01); +			msleep(10); +			break; +		case 2: +			rc = tm6000_i2c_reset(dev, 100); +			break; +		} +		break; +	case XC2028_I2C_FLUSH: +		tm6000_set_reg(dev, REQ_50_SET_START, 0, 0); +		tm6000_set_reg(dev, REQ_51_SET_STOP, 0, 0); +		break; +	} +	return rc; +} +EXPORT_SYMBOL_GPL(tm6000_tuner_callback); + +int tm6000_cards_setup(struct tm6000_core *dev) +{ +	/* +	 * Board-specific initialization sequence. Handles all GPIO +	 * initialization sequences that are board-specific. +	 * Up to now, all found devices use GPIO1 and GPIO4 at the same way. +	 * Probably, they're all based on some reference device. Due to that, +	 * there's a common routine at the end to handle those GPIO's. Devices +	 * that use different pinups or init sequences can just return at +	 * the board-specific session. +	 */ +	switch (dev->model) { +	case TM6010_BOARD_HAUPPAUGE_900H: +	case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: +	case TM6010_BOARD_TWINHAN_TU501: +	case TM6010_BOARD_GENERIC: +		/* Turn xceive 3028 on */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.tuner_on, 0x01); +		msleep(15); +		/* Turn zarlink zl10353 on */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x00); +		msleep(15); +		/* Reset zarlink zl10353 */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x00); +		msleep(50); +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x01); +		msleep(15); +		/* Turn zarlink zl10353 off */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x01); +		msleep(15); +		/* ir ? */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.ir, 0x01); +		msleep(15); +		/* Power led on (blue) */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x00); +		msleep(15); +		/* DVB led off (orange) */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.dvb_led, 0x01); +		msleep(15); +		/* Turn zarlink zl10353 on */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x00); +		msleep(15); +		break; +	case TM6010_BOARD_BEHOLD_WANDER: +	case TM6010_BOARD_BEHOLD_WANDER_LITE: +		/* Power led on (blue) */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x01); +		msleep(15); +		/* Reset zarlink zl10353 */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x00); +		msleep(50); +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x01); +		msleep(15); +		break; +	case TM6010_BOARD_BEHOLD_VOYAGER: +	case TM6010_BOARD_BEHOLD_VOYAGER_LITE: +		/* Power led on (blue) */ +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x01); +		msleep(15); +		break; +	default: +		break; +	} + +	/* +	 * Default initialization. Most of the devices seem to use GPIO1 +	 * and GPIO4.on the same way, so, this handles the common sequence +	 * used by most devices. +	 * If a device uses a different sequence or different GPIO pins for +	 * reset, just add the code at the board-specific part +	 */ + +	if (dev->gpio.tuner_reset) { +		int rc; +		int i; + +		for (i = 0; i < 2; i++) { +			rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +						dev->gpio.tuner_reset, 0x00); +			if (rc < 0) { +				printk(KERN_ERR "Error %i doing tuner reset\n", rc); +				return rc; +			} + +			msleep(10); /* Just to be conservative */ +			rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +						dev->gpio.tuner_reset, 0x01); +			if (rc < 0) { +				printk(KERN_ERR "Error %i doing tuner reset\n", rc); +				return rc; +			} +		} +	} else { +		printk(KERN_ERR "Tuner reset is not configured\n"); +		return -1; +	} + +	msleep(50); + +	return 0; +}; + +static void tm6000_config_tuner(struct tm6000_core *dev) +{ +	struct tuner_setup tun_setup; + +	/* Load tuner module */ +	v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap, +		"tuner", dev->tuner_addr, NULL); + +	memset(&tun_setup, 0, sizeof(tun_setup)); +	tun_setup.type = dev->tuner_type; +	tun_setup.addr = dev->tuner_addr; + +	tun_setup.mode_mask = 0; +	if (dev->caps.has_tuner) +		tun_setup.mode_mask |= (T_ANALOG_TV | T_RADIO); + +	switch (dev->tuner_type) { +	case TUNER_XC2028: +		tun_setup.tuner_callback = tm6000_tuner_callback; +		break; +	case TUNER_XC5000: +		tun_setup.tuner_callback = tm6000_xc5000_callback; +		break; +	} + +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup); + +	switch (dev->tuner_type) { +	case TUNER_XC2028: { +		struct v4l2_priv_tun_config xc2028_cfg; +		struct xc2028_ctrl ctl; + +		memset(&xc2028_cfg, 0, sizeof(xc2028_cfg)); +		memset(&ctl, 0, sizeof(ctl)); + +		ctl.demod = XC3028_FE_ZARLINK456; + +		xc2028_cfg.tuner = TUNER_XC2028; +		xc2028_cfg.priv  = &ctl; + +		switch (dev->model) { +		case TM6010_BOARD_HAUPPAUGE_900H: +		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: +		case TM6010_BOARD_TWINHAN_TU501: +			ctl.max_len = 80; +			ctl.fname = "xc3028L-v36.fw"; +			break; +		default: +			if (dev->dev_type == TM6010) +				ctl.fname = "xc3028-v27.fw"; +			else +				ctl.fname = "xc3028-v24.fw"; +		} + +		printk(KERN_INFO "Setting firmware parameters for xc2028\n"); +		v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, +				     &xc2028_cfg); + +		} +		break; +	case TUNER_XC5000: +		{ +		struct v4l2_priv_tun_config  xc5000_cfg; +		struct xc5000_config ctl = { +			.i2c_address = dev->tuner_addr, +			.if_khz      = 4570, +			.radio_input = XC5000_RADIO_FM1_MONO, +			}; + +		xc5000_cfg.tuner = TUNER_XC5000; +		xc5000_cfg.priv  = &ctl; + +		v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, +				     &xc5000_cfg); +		} +		break; +	default: +		printk(KERN_INFO "Unknown tuner type. Tuner is not configured.\n"); +		break; +	} +} + +static int fill_board_specific_data(struct tm6000_core *dev) +{ +	int rc; + +	dev->dev_type   = tm6000_boards[dev->model].type; +	dev->tuner_type = tm6000_boards[dev->model].tuner_type; +	dev->tuner_addr = tm6000_boards[dev->model].tuner_addr; + +	dev->gpio = tm6000_boards[dev->model].gpio; + +	dev->ir_codes = tm6000_boards[dev->model].ir_codes; + +	dev->demod_addr = tm6000_boards[dev->model].demod_addr; + +	dev->caps = tm6000_boards[dev->model].caps; + +	dev->vinput[0] = tm6000_boards[dev->model].vinput[0]; +	dev->vinput[1] = tm6000_boards[dev->model].vinput[1]; +	dev->vinput[2] = tm6000_boards[dev->model].vinput[2]; +	dev->rinput = tm6000_boards[dev->model].rinput; + +	/* setup per-model quirks */ +	switch (dev->model) { +	case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: +	case TM6010_BOARD_HAUPPAUGE_900H: +		dev->quirks |= TM6000_QUIRK_NO_USB_DELAY; +		break; + +	default: +		break; +	} + +	/* initialize hardware */ +	rc = tm6000_init(dev); +	if (rc < 0) +		return rc; + +	return v4l2_device_register(&dev->udev->dev, &dev->v4l2_dev); +} + + +static void use_alternative_detection_method(struct tm6000_core *dev) +{ +	int i, model = -1; + +	if (!dev->eedata_size) +		return; + +	for (i = 0; i < ARRAY_SIZE(tm6000_boards); i++) { +		if (!tm6000_boards[i].eename_size) +			continue; +		if (dev->eedata_size < tm6000_boards[i].eename_pos + +				       tm6000_boards[i].eename_size) +			continue; + +		if (!memcmp(&dev->eedata[tm6000_boards[i].eename_pos], +			    tm6000_boards[i].eename, +			    tm6000_boards[i].eename_size)) { +			model = i; +			break; +		} +	} +	if (model < 0) { +		printk(KERN_INFO "Device has eeprom but is currently unknown\n"); +		return; +	} + +	dev->model = model; + +	printk(KERN_INFO "Device identified via eeprom as %s (type = %d)\n", +	       tm6000_boards[model].name, model); +} + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ +	struct tm6000_core *dev = container_of(work, struct tm6000_core, +					       request_module_wk); + +	request_module("tm6000-alsa"); + +	if (dev->caps.has_dvb) +		request_module("tm6000-dvb"); +} + +static void request_modules(struct tm6000_core *dev) +{ +	INIT_WORK(&dev->request_module_wk, request_module_async); +	schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct tm6000_core *dev) +{ +	flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + +static int tm6000_init_dev(struct tm6000_core *dev) +{ +	struct v4l2_frequency f; +	int rc = 0; + +	mutex_init(&dev->lock); +	mutex_lock(&dev->lock); + +	if (!is_generic(dev->model)) { +		rc = fill_board_specific_data(dev); +		if (rc < 0) +			goto err; + +		/* register i2c bus */ +		rc = tm6000_i2c_register(dev); +		if (rc < 0) +			goto err; +	} else { +		/* register i2c bus */ +		rc = tm6000_i2c_register(dev); +		if (rc < 0) +			goto err; + +		use_alternative_detection_method(dev); + +		rc = fill_board_specific_data(dev); +		if (rc < 0) +			goto err; +	} + +	/* Default values for STD and resolutions */ +	dev->width = 720; +	dev->height = 480; +	dev->norm = V4L2_STD_NTSC_M; + +	/* Configure tuner */ +	tm6000_config_tuner(dev); + +	/* Set video standard */ +	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, dev->norm); + +	/* Set tuner frequency - also loads firmware on xc2028/xc3028 */ +	f.tuner = 0; +	f.type = V4L2_TUNER_ANALOG_TV; +	f.frequency = 3092;	/* 193.25 MHz */ +	dev->freq = f.frequency; +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f); + +	if (dev->caps.has_tda9874) +		v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap, +			"tvaudio", I2C_ADDR_TDA9874, NULL); + +	/* register and initialize V4L2 */ +	rc = tm6000_v4l2_register(dev); +	if (rc < 0) +		goto err; + +	tm6000_add_into_devlist(dev); +	tm6000_init_extension(dev); + +	tm6000_ir_init(dev); + +	request_modules(dev); + +	mutex_unlock(&dev->lock); +	return 0; + +err: +	mutex_unlock(&dev->lock); +	return rc; +} + +/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) + +static void get_max_endpoint(struct usb_device *udev, +			     struct usb_host_interface *alt, +			     char *msgtype, +			     struct usb_host_endpoint *curr_e, +			     struct tm6000_endpoint *tm_ep) +{ +	u16 tmp = le16_to_cpu(curr_e->desc.wMaxPacketSize); +	unsigned int size = tmp & 0x7ff; + +	if (udev->speed == USB_SPEED_HIGH) +		size = size * hb_mult(tmp); + +	if (size > tm_ep->maxsize) { +		tm_ep->endp = curr_e; +		tm_ep->maxsize = size; +		tm_ep->bInterfaceNumber = alt->desc.bInterfaceNumber; +		tm_ep->bAlternateSetting = alt->desc.bAlternateSetting; + +		printk(KERN_INFO "tm6000: %s endpoint: 0x%02x (max size=%u bytes)\n", +					msgtype, curr_e->desc.bEndpointAddress, +					size); +	} +} + +/* + * tm6000_usb_probe() + * checks for supported devices + */ +static int tm6000_usb_probe(struct usb_interface *interface, +			    const struct usb_device_id *id) +{ +	struct usb_device *usbdev; +	struct tm6000_core *dev = NULL; +	int i, rc = 0; +	int nr = 0; +	char *speed; + +	usbdev = usb_get_dev(interface_to_usbdev(interface)); + +	/* Selects the proper interface */ +	rc = usb_set_interface(usbdev, 0, 1); +	if (rc < 0) +		goto err; + +	/* Check to see next free device and mark as used */ +	nr = find_first_zero_bit(&tm6000_devused, TM6000_MAXBOARDS); +	if (nr >= TM6000_MAXBOARDS) { +		printk(KERN_ERR "tm6000: Supports only %i tm60xx boards.\n", TM6000_MAXBOARDS); +		usb_put_dev(usbdev); +		return -ENOMEM; +	} + +	/* Create and initialize dev struct */ +	dev = kzalloc(sizeof(*dev), GFP_KERNEL); +	if (dev == NULL) { +		printk(KERN_ERR "tm6000" ": out of memory!\n"); +		usb_put_dev(usbdev); +		return -ENOMEM; +	} +	spin_lock_init(&dev->slock); +	mutex_init(&dev->usb_lock); + +	/* Increment usage count */ +	set_bit(nr, &tm6000_devused); +	snprintf(dev->name, 29, "tm6000 #%d", nr); + +	dev->model = id->driver_info; +	if (card[nr] < ARRAY_SIZE(tm6000_boards)) +		dev->model = card[nr]; + +	dev->udev = usbdev; +	dev->devno = nr; + +	switch (usbdev->speed) { +	case USB_SPEED_LOW: +		speed = "1.5"; +		break; +	case USB_SPEED_UNKNOWN: +	case USB_SPEED_FULL: +		speed = "12"; +		break; +	case USB_SPEED_HIGH: +		speed = "480"; +		break; +	default: +		speed = "unknown"; +	} + +	/* Get endpoints */ +	for (i = 0; i < interface->num_altsetting; i++) { +		int ep; + +		for (ep = 0; ep < interface->altsetting[i].desc.bNumEndpoints; ep++) { +			struct usb_host_endpoint	*e; +			int dir_out; + +			e = &interface->altsetting[i].endpoint[ep]; + +			dir_out = ((e->desc.bEndpointAddress & +					USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); + +			printk(KERN_INFO "tm6000: alt %d, interface %i, class %i\n", +			       i, +			       interface->altsetting[i].desc.bInterfaceNumber, +			       interface->altsetting[i].desc.bInterfaceClass); + +			switch (e->desc.bmAttributes) { +			case USB_ENDPOINT_XFER_BULK: +				if (!dir_out) { +					get_max_endpoint(usbdev, +							 &interface->altsetting[i], +							 "Bulk IN", e, +							 &dev->bulk_in); +				} else { +					get_max_endpoint(usbdev, +							 &interface->altsetting[i], +							 "Bulk OUT", e, +							 &dev->bulk_out); +				} +				break; +			case USB_ENDPOINT_XFER_ISOC: +				if (!dir_out) { +					get_max_endpoint(usbdev, +							 &interface->altsetting[i], +							 "ISOC IN", e, +							 &dev->isoc_in); +				} else { +					get_max_endpoint(usbdev, +							 &interface->altsetting[i], +							 "ISOC OUT", e, +							 &dev->isoc_out); +				} +				break; +			case USB_ENDPOINT_XFER_INT: +				if (!dir_out) { +					get_max_endpoint(usbdev, +							&interface->altsetting[i], +							"INT IN", e, +							&dev->int_in); +				} else { +					get_max_endpoint(usbdev, +							&interface->altsetting[i], +							"INT OUT", e, +							&dev->int_out); +				} +				break; +			} +		} +	} + + +	printk(KERN_INFO "tm6000: New video device @ %s Mbps (%04x:%04x, ifnum %d)\n", +		speed, +		le16_to_cpu(dev->udev->descriptor.idVendor), +		le16_to_cpu(dev->udev->descriptor.idProduct), +		interface->altsetting->desc.bInterfaceNumber); + +/* check if the the device has the iso in endpoint at the correct place */ +	if (!dev->isoc_in.endp) { +		printk(KERN_ERR "tm6000: probing error: no IN ISOC endpoint!\n"); +		rc = -ENODEV; + +		goto err; +	} + +	/* save our data pointer in this interface device */ +	usb_set_intfdata(interface, dev); + +	printk(KERN_INFO "tm6000: Found %s\n", tm6000_boards[dev->model].name); + +	rc = tm6000_init_dev(dev); +	if (rc < 0) +		goto err; + +	return 0; + +err: +	printk(KERN_ERR "tm6000: Error %d while registering\n", rc); + +	clear_bit(nr, &tm6000_devused); +	usb_put_dev(usbdev); + +	kfree(dev); +	return rc; +} + +/* + * tm6000_usb_disconnect() + * called when the device gets diconencted + * video device will be unregistered on v4l2_close in case it is still open + */ +static void tm6000_usb_disconnect(struct usb_interface *interface) +{ +	struct tm6000_core *dev = usb_get_intfdata(interface); +	usb_set_intfdata(interface, NULL); + +	if (!dev) +		return; + +	printk(KERN_INFO "tm6000: disconnecting %s\n", dev->name); + +	flush_request_modules(dev); + +	tm6000_ir_fini(dev); + +	if (dev->gpio.power_led) { +		switch (dev->model) { +		case TM6010_BOARD_HAUPPAUGE_900H: +		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: +		case TM6010_BOARD_TWINHAN_TU501: +			/* Power led off */ +			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +				dev->gpio.power_led, 0x01); +			msleep(15); +			break; +		case TM6010_BOARD_BEHOLD_WANDER: +		case TM6010_BOARD_BEHOLD_VOYAGER: +		case TM6010_BOARD_BEHOLD_WANDER_LITE: +		case TM6010_BOARD_BEHOLD_VOYAGER_LITE: +			/* Power led off */ +			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +				dev->gpio.power_led, 0x00); +			msleep(15); +			break; +		} +	} +	tm6000_v4l2_unregister(dev); + +	tm6000_i2c_unregister(dev); + +	v4l2_device_unregister(&dev->v4l2_dev); + +	dev->state |= DEV_DISCONNECTED; + +	usb_put_dev(dev->udev); + +	tm6000_close_extension(dev); +	tm6000_remove_from_devlist(dev); + +	clear_bit(dev->devno, &tm6000_devused); +	kfree(dev); +} + +static struct usb_driver tm6000_usb_driver = { +		.name = "tm6000", +		.probe = tm6000_usb_probe, +		.disconnect = tm6000_usb_disconnect, +		.id_table = tm6000_id_table, +}; + +module_usb_driver(tm6000_usb_driver); + +MODULE_DESCRIPTION("Trident TVMaster TM5600/TM6000/TM6010 USB2 adapter"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/tm6000/tm6000-core.c b/drivers/media/usb/tm6000/tm6000-core.c new file mode 100644 index 00000000000..7c32353c59d --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-core.c @@ -0,0 +1,938 @@ +/* + *  tm6000-core.c - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + * + *  Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + *      - DVB-T support + * + *  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 version 2 + * + *  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/kernel.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include "tm6000.h" +#include "tm6000-regs.h" +#include <media/v4l2-common.h> +#include <media/tuner.h> + +#define USB_TIMEOUT	(5 * HZ) /* ms */ + +int tm6000_read_write_usb(struct tm6000_core *dev, u8 req_type, u8 req, +			  u16 value, u16 index, u8 *buf, u16 len) +{ +	int          ret, i; +	unsigned int pipe; +	u8	     *data = NULL; +	int delay = 5000; + +	if (len) { +		data = kzalloc(len, GFP_KERNEL); +		if (!data) +			return -ENOMEM; +	} + +	mutex_lock(&dev->usb_lock); + +	if (req_type & USB_DIR_IN) +		pipe = usb_rcvctrlpipe(dev->udev, 0); +	else { +		pipe = usb_sndctrlpipe(dev->udev, 0); +		memcpy(data, buf, len); +	} + +	if (tm6000_debug & V4L2_DEBUG_I2C) { +		printk(KERN_DEBUG "(dev %p, pipe %08x): ", dev->udev, pipe); + +		printk(KERN_CONT "%s: %02x %02x %02x %02x %02x %02x %02x %02x ", +			(req_type & USB_DIR_IN) ? " IN" : "OUT", +			req_type, req, value&0xff, value>>8, index&0xff, +			index>>8, len&0xff, len>>8); + +		if (!(req_type & USB_DIR_IN)) { +			printk(KERN_CONT ">>> "); +			for (i = 0; i < len; i++) +				printk(KERN_CONT " %02x", buf[i]); +			printk(KERN_CONT "\n"); +		} +	} + +	ret = usb_control_msg(dev->udev, pipe, req, req_type, value, index, +			      data, len, USB_TIMEOUT); + +	if (req_type &  USB_DIR_IN) +		memcpy(buf, data, len); + +	if (tm6000_debug & V4L2_DEBUG_I2C) { +		if (ret < 0) { +			if (req_type &  USB_DIR_IN) +				printk(KERN_DEBUG "<<< (len=%d)\n", len); + +			printk(KERN_CONT "%s: Error #%d\n", __func__, ret); +		} else if (req_type &  USB_DIR_IN) { +			printk(KERN_CONT "<<< "); +			for (i = 0; i < len; i++) +				printk(KERN_CONT " %02x", buf[i]); +			printk(KERN_CONT "\n"); +		} +	} + +	kfree(data); + +	if (dev->quirks & TM6000_QUIRK_NO_USB_DELAY) +		delay = 0; + +	if (req == REQ_16_SET_GET_I2C_WR1_RDN && !(req_type & USB_DIR_IN)) { +		unsigned int tsleep; +		/* Calculate delay time, 14000us for 64 bytes */ +		tsleep = (len * 200) + 200; +		if (tsleep < delay) +			tsleep = delay; +		usleep_range(tsleep, tsleep + 1000); +	} +	else if (delay) +		usleep_range(delay, delay + 1000); + +	mutex_unlock(&dev->usb_lock); +	return ret; +} + +int tm6000_set_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index) +{ +	return +		tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR, +				      req, value, index, NULL, 0); +} +EXPORT_SYMBOL_GPL(tm6000_set_reg); + +int tm6000_get_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index) +{ +	int rc; +	u8 buf[1]; + +	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req, +					value, index, buf, 1); + +	if (rc < 0) +		return rc; + +	return *buf; +} +EXPORT_SYMBOL_GPL(tm6000_get_reg); + +int tm6000_set_reg_mask(struct tm6000_core *dev, u8 req, u16 value, +						u16 index, u16 mask) +{ +	int rc; +	u8 buf[1]; +	u8 new_index; + +	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req, +					value, 0, buf, 1); + +	if (rc < 0) +		return rc; + +	new_index = (buf[0] & ~mask) | (index & mask); + +	if (new_index == buf[0]) +		return 0; + +	return tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR, +				      req, value, new_index, NULL, 0); +} +EXPORT_SYMBOL_GPL(tm6000_set_reg_mask); + +int tm6000_get_reg16(struct tm6000_core *dev, u8 req, u16 value, u16 index) +{ +	int rc; +	u8 buf[2]; + +	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req, +					value, index, buf, 2); + +	if (rc < 0) +		return rc; + +	return buf[1]|buf[0]<<8; +} + +int tm6000_get_reg32(struct tm6000_core *dev, u8 req, u16 value, u16 index) +{ +	int rc; +	u8 buf[4]; + +	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req, +					value, index, buf, 4); + +	if (rc < 0) +		return rc; + +	return buf[3] | buf[2] << 8 | buf[1] << 16 | buf[0] << 24; +} + +int tm6000_i2c_reset(struct tm6000_core *dev, u16 tsleep) +{ +	int rc; + +	rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_CLK, 0); +	if (rc < 0) +		return rc; + +	msleep(tsleep); + +	rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_CLK, 1); +	msleep(tsleep); + +	return rc; +} + +void tm6000_set_fourcc_format(struct tm6000_core *dev) +{ +	if (dev->dev_type == TM6010) { +		int val; + +		val = tm6000_get_reg(dev, TM6010_REQ07_RCC_ACTIVE_IF, 0) & 0xfc; +		if (dev->fourcc == V4L2_PIX_FMT_UYVY) +			tm6000_set_reg(dev, TM6010_REQ07_RCC_ACTIVE_IF, val); +		else +			tm6000_set_reg(dev, TM6010_REQ07_RCC_ACTIVE_IF, val | 1); +	} else { +		if (dev->fourcc == V4L2_PIX_FMT_UYVY) +			tm6000_set_reg(dev, TM6010_REQ07_RC1_TRESHOLD, 0xd0); +		else +			tm6000_set_reg(dev, TM6010_REQ07_RC1_TRESHOLD, 0x90); +	} +} + +static void tm6000_set_vbi(struct tm6000_core *dev) +{ +	/* +	 * FIXME: +	 * VBI lines and start/end are different between 60Hz and 50Hz +	 * So, it is very likely that we need to change the config to +	 * something that takes it into account, doing something different +	 * if (dev->norm & V4L2_STD_525_60) +	 */ + +	if (dev->dev_type == TM6010) { +		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x01); +		tm6000_set_reg(dev, TM6010_REQ07_R41_TELETEXT_VBI_CODE1, 0x27); +		tm6000_set_reg(dev, TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL, 0x55); +		tm6000_set_reg(dev, TM6010_REQ07_R43_VBI_DATA_TYPE_LINE7, 0x66); +		tm6000_set_reg(dev, TM6010_REQ07_R44_VBI_DATA_TYPE_LINE8, 0x66); +		tm6000_set_reg(dev, TM6010_REQ07_R45_VBI_DATA_TYPE_LINE9, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R46_VBI_DATA_TYPE_LINE10, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R47_VBI_DATA_TYPE_LINE11, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R48_VBI_DATA_TYPE_LINE12, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R49_VBI_DATA_TYPE_LINE13, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R4A_VBI_DATA_TYPE_LINE14, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R4B_VBI_DATA_TYPE_LINE15, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R4C_VBI_DATA_TYPE_LINE16, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R4D_VBI_DATA_TYPE_LINE17, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R4E_VBI_DATA_TYPE_LINE18, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R4F_VBI_DATA_TYPE_LINE19, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R50_VBI_DATA_TYPE_LINE20, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R52_VBI_DATA_TYPE_LINE22, 0x66); +		tm6000_set_reg(dev, +			TM6010_REQ07_R53_VBI_DATA_TYPE_LINE23, 0x00); +		tm6000_set_reg(dev, +			TM6010_REQ07_R54_VBI_DATA_TYPE_RLINES, 0x00); +		tm6000_set_reg(dev, +			TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN, 0x01); +		tm6000_set_reg(dev, +			TM6010_REQ07_R56_VBI_LOOP_FILTER_I_GAIN, 0x00); +		tm6000_set_reg(dev, +			TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN, 0x02); +		tm6000_set_reg(dev, TM6010_REQ07_R58_VBI_CAPTION_DTO1, 0x35); +		tm6000_set_reg(dev, TM6010_REQ07_R59_VBI_CAPTION_DTO0, 0xa0); +		tm6000_set_reg(dev, TM6010_REQ07_R5A_VBI_TELETEXT_DTO1, 0x11); +		tm6000_set_reg(dev, TM6010_REQ07_R5B_VBI_TELETEXT_DTO0, 0x4c); +		tm6000_set_reg(dev, TM6010_REQ07_R40_TELETEXT_VBI_CODE0, 0x01); +		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x00); +	} +} + +int tm6000_init_analog_mode(struct tm6000_core *dev) +{ +	struct v4l2_frequency f; + +	if (dev->dev_type == TM6010) { +		u8 active = TM6010_REQ07_RCC_ACTIVE_IF_AUDIO_ENABLE; + +		if (!dev->radio) +			active |= TM6010_REQ07_RCC_ACTIVE_IF_VIDEO_ENABLE; + +		/* Enable video and audio */ +		tm6000_set_reg_mask(dev, TM6010_REQ07_RCC_ACTIVE_IF, +							active, 0x60); +		/* Disable TS input */ +		tm6000_set_reg_mask(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, +							0x00, 0x40); +	} else { +		/* Enables soft reset */ +		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x01); + +		if (dev->scaler) +			/* Disable Hfilter and Enable TS Drop err */ +			tm6000_set_reg(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x20); +		else	/* Enable Hfilter and disable TS Drop err */ +			tm6000_set_reg(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x80); + +		tm6000_set_reg(dev, TM6010_REQ07_RC3_HSTART1, 0x88); +		tm6000_set_reg(dev, TM6000_REQ07_RDA_CLK_SEL, 0x23); +		tm6000_set_reg(dev, TM6010_REQ07_RD1_ADDR_FOR_REQ1, 0xc0); +		tm6000_set_reg(dev, TM6010_REQ07_RD2_ADDR_FOR_REQ2, 0xd8); +		tm6000_set_reg(dev, TM6010_REQ07_RD6_ENDP_REQ1_REQ2, 0x06); +		tm6000_set_reg(dev, TM6000_REQ07_RDF_PWDOWN_ACLK, 0x1f); + +		/* AP Software reset */ +		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x08); +		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x00); + +		tm6000_set_fourcc_format(dev); + +		/* Disables soft reset */ +		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x00); +	} +	msleep(20); + +	/* Tuner firmware can now be loaded */ + +	/* +	 * FIXME: This is a hack! xc3028 "sleeps" when no channel is detected +	 * for more than a few seconds. Not sure why, as this behavior does +	 * not happen on other devices with xc3028. So, I suspect that it +	 * is yet another bug at tm6000. After start sleeping, decoding +	 * doesn't start automatically. Instead, it requires some +	 * I2C commands to wake it up. As we want to have image at the +	 * beginning, we needed to add this hack. The better would be to +	 * discover some way to make tm6000 to wake up without this hack. +	 */ +	f.frequency = dev->freq; +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f); + +	msleep(100); +	tm6000_set_standard(dev); +	tm6000_set_vbi(dev); +	tm6000_set_audio_bitrate(dev, 48000); + +	/* switch dvb led off */ +	if (dev->gpio.dvb_led) { +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +			dev->gpio.dvb_led, 0x01); +	} + +	return 0; +} + +int tm6000_init_digital_mode(struct tm6000_core *dev) +{ +	if (dev->dev_type == TM6010) { +		/* Disable video and audio */ +		tm6000_set_reg_mask(dev, TM6010_REQ07_RCC_ACTIVE_IF, +				0x00, 0x60); +		/* Enable TS input */ +		tm6000_set_reg_mask(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, +				0x40, 0x40); +		/* all power down, but not the digital data port */ +		tm6000_set_reg(dev, TM6010_REQ07_RFE_POWER_DOWN, 0x28); +		tm6000_set_reg(dev, TM6010_REQ08_RE2_POWER_DOWN_CTRL1, 0xfc); +		tm6000_set_reg(dev, TM6010_REQ08_RE6_POWER_DOWN_CTRL2, 0xff); +	} else  { +		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x08); +		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x00); +		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x01); +		tm6000_set_reg(dev, TM6000_REQ07_RDF_PWDOWN_ACLK, 0x08); +		tm6000_set_reg(dev, TM6000_REQ07_RE2_VADC_STATUS_CTL, 0x0c); +		tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0xff); +		tm6000_set_reg(dev, TM6000_REQ07_REB_VADC_AADC_MODE, 0xd8); +		tm6000_set_reg(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x40); +		tm6000_set_reg(dev, TM6010_REQ07_RC1_TRESHOLD, 0xd0); +		tm6000_set_reg(dev, TM6010_REQ07_RC3_HSTART1, 0x09); +		tm6000_set_reg(dev, TM6000_REQ07_RDA_CLK_SEL, 0x37); +		tm6000_set_reg(dev, TM6010_REQ07_RD1_ADDR_FOR_REQ1, 0xd8); +		tm6000_set_reg(dev, TM6010_REQ07_RD2_ADDR_FOR_REQ2, 0xc0); +		tm6000_set_reg(dev, TM6010_REQ07_RD6_ENDP_REQ1_REQ2, 0x60); + +		tm6000_set_reg(dev, TM6000_REQ07_RE2_VADC_STATUS_CTL, 0x0c); +		tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0xff); +		tm6000_set_reg(dev, TM6000_REQ07_REB_VADC_AADC_MODE, 0x08); +		msleep(50); + +		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x00); +		msleep(50); +		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x01); +		msleep(50); +		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x00); +		msleep(100); +	} + +	/* switch dvb led on */ +	if (dev->gpio.dvb_led) { +		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, +			dev->gpio.dvb_led, 0x00); +	} + +	return 0; +} +EXPORT_SYMBOL(tm6000_init_digital_mode); + +struct reg_init { +	u8 req; +	u8 reg; +	u8 val; +}; + +/* The meaning of those initializations are unknown */ +static struct reg_init tm6000_init_tab[] = { +	/* REG  VALUE */ +	{ TM6000_REQ07_RDF_PWDOWN_ACLK, 0x1f }, +	{ TM6010_REQ07_RFF_SOFT_RESET, 0x08 }, +	{ TM6010_REQ07_RFF_SOFT_RESET, 0x00 }, +	{ TM6010_REQ07_RD5_POWERSAVE, 0x4f }, +	{ TM6000_REQ07_RDA_CLK_SEL, 0x23 }, +	{ TM6000_REQ07_RDB_OUT_SEL, 0x08 }, +	{ TM6000_REQ07_RE2_VADC_STATUS_CTL, 0x00 }, +	{ TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x10 }, +	{ TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x00 }, +	{ TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x00 }, +	{ TM6000_REQ07_REB_VADC_AADC_MODE, 0x64 },	/* 48000 bits/sample, external input */ +	{ TM6000_REQ07_REE_VADC_CTRL_SEL_CONTROL, 0xc2 }, + +	{ TM6010_REQ07_R3F_RESET, 0x01 },		/* Start of soft reset */ +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x00 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x07 }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 }, +	{ TM6010_REQ07_R05_NOISE_THRESHOLD, 0x64 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x01 }, +	{ TM6010_REQ07_R08_LUMA_CONTRAST_ADJ, 0x82 }, +	{ TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ, 0x36 }, +	{ TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ, 0x50 }, +	{ TM6010_REQ07_R0C_CHROMA_AGC_CONTROL, 0x6a }, +	{ TM6010_REQ07_R11_AGC_PEAK_CONTROL, 0xc9 }, +	{ TM6010_REQ07_R12_AGC_GATE_STARTH, 0x07 }, +	{ TM6010_REQ07_R13_AGC_GATE_STARTL, 0x3b }, +	{ TM6010_REQ07_R14_AGC_GATE_WIDTH, 0x47 }, +	{ TM6010_REQ07_R15_AGC_BP_DELAY, 0x6f }, +	{ TM6010_REQ07_R17_HLOOP_MAXSTATE, 0xcd }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R20_HSYNC_RISING_EDGE_TIME, 0x3c }, +	{ TM6010_REQ07_R21_HSYNC_PHASE_OFFSET, 0x3c }, +	{ TM6010_REQ07_R2D_CHROMA_BURST_END, 0x48 }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 }, +	{ TM6010_REQ07_R32_VSYNC_HLOCK_MIN, 0x74 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c }, +	{ TM6010_REQ07_R34_VSYNC_AGC_MIN, 0x74 }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R36_VSYNC_VBI_MIN, 0x7a }, +	{ TM6010_REQ07_R37_VSYNC_VBI_MAX, 0x26 }, +	{ TM6010_REQ07_R38_VSYNC_THRESHOLD, 0x40 }, +	{ TM6010_REQ07_R39_VSYNC_TIME_CONSTANT, 0x0a }, +	{ TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL, 0x55 }, +	{ TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21, 0x11 }, +	{ TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN, 0x01 }, +	{ TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN, 0x02 }, +	{ TM6010_REQ07_R58_VBI_CAPTION_DTO1, 0x35 }, +	{ TM6010_REQ07_R59_VBI_CAPTION_DTO0, 0xa0 }, +	{ TM6010_REQ07_R80_COMB_FILTER_TRESHOLD, 0x15 }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 }, +	{ TM6010_REQ07_RC1_TRESHOLD, 0xd0 }, +	{ TM6010_REQ07_RC3_HSTART1, 0x88 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 },		/* End of the soft reset */ +	{ TM6010_REQ05_R18_IMASK7, 0x00 }, +}; + +static struct reg_init tm6010_init_tab[] = { +	{ TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x00 }, +	{ TM6010_REQ07_RC4_HSTART0, 0xa0 }, +	{ TM6010_REQ07_RC6_HEND0, 0x40 }, +	{ TM6010_REQ07_RCA_VEND0, 0x31 }, +	{ TM6010_REQ07_RCC_ACTIVE_IF, 0xe1 }, +	{ TM6010_REQ07_RE0_DVIDEO_SOURCE, 0x03 }, +	{ TM6010_REQ07_RFE_POWER_DOWN, 0x7f }, + +	{ TM6010_REQ08_RE2_POWER_DOWN_CTRL1, 0xf0 }, +	{ TM6010_REQ08_RE3_ADC_IN1_SEL, 0xf4 }, +	{ TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf8 }, +	{ TM6010_REQ08_RE6_POWER_DOWN_CTRL2, 0x00 }, +	{ TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf2 }, +	{ TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xf0 }, +	{ TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2 }, +	{ TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, 0x60 }, +	{ TM6010_REQ08_RF1_AADC_POWER_DOWN, 0xfc }, + +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x00 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x07 }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 }, +	{ TM6010_REQ07_R05_NOISE_THRESHOLD, 0x64 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x01 }, +	{ TM6010_REQ07_R08_LUMA_CONTRAST_ADJ, 0x82 }, +	{ TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ, 0x36 }, +	{ TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ, 0x50 }, +	{ TM6010_REQ07_R0C_CHROMA_AGC_CONTROL, 0x6a }, +	{ TM6010_REQ07_R11_AGC_PEAK_CONTROL, 0xc9 }, +	{ TM6010_REQ07_R12_AGC_GATE_STARTH, 0x07 }, +	{ TM6010_REQ07_R13_AGC_GATE_STARTL, 0x3b }, +	{ TM6010_REQ07_R14_AGC_GATE_WIDTH, 0x47 }, +	{ TM6010_REQ07_R15_AGC_BP_DELAY, 0x6f }, +	{ TM6010_REQ07_R17_HLOOP_MAXSTATE, 0xcd }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R20_HSYNC_RISING_EDGE_TIME, 0x3c }, +	{ TM6010_REQ07_R21_HSYNC_PHASE_OFFSET, 0x3c }, +	{ TM6010_REQ07_R2D_CHROMA_BURST_END, 0x48 }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 }, +	{ TM6010_REQ07_R32_VSYNC_HLOCK_MIN, 0x74 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c }, +	{ TM6010_REQ07_R34_VSYNC_AGC_MIN, 0x74 }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R36_VSYNC_VBI_MIN, 0x7a }, +	{ TM6010_REQ07_R37_VSYNC_VBI_MAX, 0x26 }, +	{ TM6010_REQ07_R38_VSYNC_THRESHOLD, 0x40 }, +	{ TM6010_REQ07_R39_VSYNC_TIME_CONSTANT, 0x0a }, +	{ TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL, 0x55 }, +	{ TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21, 0x11 }, +	{ TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN, 0x01 }, +	{ TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN, 0x02 }, +	{ TM6010_REQ07_R58_VBI_CAPTION_DTO1, 0x35 }, +	{ TM6010_REQ07_R59_VBI_CAPTION_DTO0, 0xa0 }, +	{ TM6010_REQ07_R80_COMB_FILTER_TRESHOLD, 0x15 }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 }, +	{ TM6010_REQ07_RC1_TRESHOLD, 0xd0 }, +	{ TM6010_REQ07_RC3_HSTART1, 0x88 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, + +	{ TM6010_REQ05_R18_IMASK7, 0x00 }, + +	{ TM6010_REQ07_RDC_IR_LEADER1, 0xaa }, +	{ TM6010_REQ07_RDD_IR_LEADER0, 0x30 }, +	{ TM6010_REQ07_RDE_IR_PULSE_CNT1, 0x20 }, +	{ TM6010_REQ07_RDF_IR_PULSE_CNT0, 0xd0 }, +	{ REQ_04_EN_DISABLE_MCU_INT, 0x02, 0x00 }, +	{ TM6010_REQ07_RD8_IR, 0x0f }, + +	/* set remote wakeup key:any key wakeup */ +	{ TM6010_REQ07_RE5_REMOTE_WAKEUP,  0xfe }, +	{ TM6010_REQ07_RDA_IR_WAKEUP_SEL,  0xff }, +}; + +int tm6000_init(struct tm6000_core *dev) +{ +	int board, rc = 0, i, size; +	struct reg_init *tab; + +	/* Check board revision */ +	board = tm6000_get_reg32(dev, REQ_40_GET_VERSION, 0, 0); +	if (board >= 0) { +		switch (board & 0xff) { +		case 0xf3: +			printk(KERN_INFO "Found tm6000\n"); +			if (dev->dev_type != TM6000) +				dev->dev_type = TM6000; +			break; +		case 0xf4: +			printk(KERN_INFO "Found tm6010\n"); +			if (dev->dev_type != TM6010) +				dev->dev_type = TM6010; +			break; +		default: +			printk(KERN_INFO "Unknown board version = 0x%08x\n", board); +		} +	} else +		printk(KERN_ERR "Error %i while retrieving board version\n", board); + +	if (dev->dev_type == TM6010) { +		tab = tm6010_init_tab; +		size = ARRAY_SIZE(tm6010_init_tab); +	} else { +		tab = tm6000_init_tab; +		size = ARRAY_SIZE(tm6000_init_tab); +	} + +	/* Load board's initialization table */ +	for (i = 0; i < size; i++) { +		rc = tm6000_set_reg(dev, tab[i].req, tab[i].reg, tab[i].val); +		if (rc < 0) { +			printk(KERN_ERR "Error %i while setting req %d, " +					"reg %d to value %d\n", rc, +					tab[i].req, tab[i].reg, tab[i].val); +			return rc; +		} +	} + +	msleep(5); /* Just to be conservative */ + +	rc = tm6000_cards_setup(dev); + +	return rc; +} + + +int tm6000_set_audio_bitrate(struct tm6000_core *dev, int bitrate) +{ +	int val = 0; +	u8 areg_f0 = 0x60; /* ADC MCLK = 250 Fs */ +	u8 areg_0a = 0x91; /* SIF 48KHz */ + +	switch (bitrate) { +	case 48000: +		areg_f0 = 0x60; /* ADC MCLK = 250 Fs */ +		areg_0a = 0x91; /* SIF 48KHz */ +		dev->audio_bitrate = bitrate; +		break; +	case 32000: +		areg_f0 = 0x00; /* ADC MCLK = 375 Fs */ +		areg_0a = 0x90; /* SIF 32KHz */ +		dev->audio_bitrate = bitrate; +		break; +	default: +		return -EINVAL; +	} + + +	/* enable I2S, if we use sif or external I2S device */ +	if (dev->dev_type == TM6010) { +		val = tm6000_set_reg(dev, TM6010_REQ08_R0A_A_I2S_MOD, areg_0a); +		if (val < 0) +			return val; + +		val = tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, +							areg_f0, 0xf0); +		if (val < 0) +			return val; +	} else { +		val = tm6000_set_reg_mask(dev, TM6000_REQ07_REB_VADC_AADC_MODE, +							areg_f0, 0xf0); +		if (val < 0) +			return val; +	} +	return 0; +} +EXPORT_SYMBOL_GPL(tm6000_set_audio_bitrate); + +int tm6000_set_audio_rinput(struct tm6000_core *dev) +{ +	if (dev->dev_type == TM6010) { +		/* Audio crossbar setting, default SIF1 */ +		u8 areg_f0; +		u8 areg_07 = 0x10; + +		switch (dev->rinput.amux) { +		case TM6000_AMUX_SIF1: +		case TM6000_AMUX_SIF2: +			areg_f0 = 0x03; +			areg_07 = 0x30; +			break; +		case TM6000_AMUX_ADC1: +			areg_f0 = 0x00; +			break; +		case TM6000_AMUX_ADC2: +			areg_f0 = 0x08; +			break; +		case TM6000_AMUX_I2S: +			areg_f0 = 0x04; +			break; +		default: +			printk(KERN_INFO "%s: audio input dosn't support\n", +				dev->name); +			return 0; +			break; +		} +		/* Set audio input crossbar */ +		tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, +							areg_f0, 0x0f); +		/* Mux overflow workaround */ +		tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL, +			areg_07, 0xf0); +	} else { +		u8 areg_eb; +		/* Audio setting, default LINE1 */ +		switch (dev->rinput.amux) { +		case TM6000_AMUX_ADC1: +			areg_eb = 0x00; +			break; +		case TM6000_AMUX_ADC2: +			areg_eb = 0x04; +			break; +		default: +			printk(KERN_INFO "%s: audio input dosn't support\n", +				dev->name); +			return 0; +			break; +		} +		/* Set audio input */ +		tm6000_set_reg_mask(dev, TM6000_REQ07_REB_VADC_AADC_MODE, +							areg_eb, 0x0f); +	} +	return 0; +} + +static void tm6010_set_mute_sif(struct tm6000_core *dev, u8 mute) +{ +	u8 mute_reg = 0; + +	if (mute) +		mute_reg = 0x08; + +	tm6000_set_reg_mask(dev, TM6010_REQ08_R0A_A_I2S_MOD, mute_reg, 0x08); +} + +static void tm6010_set_mute_adc(struct tm6000_core *dev, u8 mute) +{ +	u8 mute_reg = 0; + +	if (mute) +		mute_reg = 0x20; + +	if (dev->dev_type == TM6010) { +		tm6000_set_reg_mask(dev, TM6010_REQ08_RF2_LEFT_CHANNEL_VOL, +							mute_reg, 0x20); +		tm6000_set_reg_mask(dev, TM6010_REQ08_RF3_RIGHT_CHANNEL_VOL, +							mute_reg, 0x20); +	} else { +		tm6000_set_reg_mask(dev, TM6000_REQ07_REC_VADC_AADC_LVOL, +							mute_reg, 0x20); +		tm6000_set_reg_mask(dev, TM6000_REQ07_RED_VADC_AADC_RVOL, +							mute_reg, 0x20); +	} +} + +int tm6000_tvaudio_set_mute(struct tm6000_core *dev, u8 mute) +{ +	enum tm6000_mux mux; + +	if (dev->radio) +		mux = dev->rinput.amux; +	else +		mux = dev->vinput[dev->input].amux; + +	switch (mux) { +	case TM6000_AMUX_SIF1: +	case TM6000_AMUX_SIF2: +		if (dev->dev_type == TM6010) +			tm6010_set_mute_sif(dev, mute); +		else { +			printk(KERN_INFO "ERROR: TM5600 and TM6000 don't has" +					" SIF audio inputs. Please check the %s" +					" configuration.\n", dev->name); +			return -EINVAL; +		} +		break; +	case TM6000_AMUX_ADC1: +	case TM6000_AMUX_ADC2: +		tm6010_set_mute_adc(dev, mute); +		break; +	default: +		return -EINVAL; +		break; +	} +	return 0; +} + +static void tm6010_set_volume_sif(struct tm6000_core *dev, int vol) +{ +	u8 vol_reg; + +	vol_reg = vol & 0x0F; + +	if (vol < 0) +		vol_reg |= 0x40; + +	tm6000_set_reg(dev, TM6010_REQ08_R07_A_LEFT_VOL, vol_reg); +	tm6000_set_reg(dev, TM6010_REQ08_R08_A_RIGHT_VOL, vol_reg); +} + +static void tm6010_set_volume_adc(struct tm6000_core *dev, int vol) +{ +	u8 vol_reg; + +	vol_reg = (vol + 0x10) & 0x1f; + +	if (dev->dev_type == TM6010) { +		tm6000_set_reg(dev, TM6010_REQ08_RF2_LEFT_CHANNEL_VOL, vol_reg); +		tm6000_set_reg(dev, TM6010_REQ08_RF3_RIGHT_CHANNEL_VOL, vol_reg); +	} else { +		tm6000_set_reg(dev, TM6000_REQ07_REC_VADC_AADC_LVOL, vol_reg); +		tm6000_set_reg(dev, TM6000_REQ07_RED_VADC_AADC_RVOL, vol_reg); +	} +} + +void tm6000_set_volume(struct tm6000_core *dev, int vol) +{ +	enum tm6000_mux mux; + +	if (dev->radio) { +		mux = dev->rinput.amux; +		vol += 8; /* Offset to 0 dB */ +	} else +		mux = dev->vinput[dev->input].amux; + +	switch (mux) { +	case TM6000_AMUX_SIF1: +	case TM6000_AMUX_SIF2: +		if (dev->dev_type == TM6010) +			tm6010_set_volume_sif(dev, vol); +		else +			printk(KERN_INFO "ERROR: TM5600 and TM6000 don't has" +					" SIF audio inputs. Please check the %s" +					" configuration.\n", dev->name); +		break; +	case TM6000_AMUX_ADC1: +	case TM6000_AMUX_ADC2: +		tm6010_set_volume_adc(dev, vol); +		break; +	default: +		break; +	} +} + +static LIST_HEAD(tm6000_devlist); +static DEFINE_MUTEX(tm6000_devlist_mutex); + +/* + * tm6000_realease_resource() + */ + +void tm6000_remove_from_devlist(struct tm6000_core *dev) +{ +	mutex_lock(&tm6000_devlist_mutex); +	list_del(&dev->devlist); +	mutex_unlock(&tm6000_devlist_mutex); +}; + +void tm6000_add_into_devlist(struct tm6000_core *dev) +{ +	mutex_lock(&tm6000_devlist_mutex); +	list_add_tail(&dev->devlist, &tm6000_devlist); +	mutex_unlock(&tm6000_devlist_mutex); +}; + +/* + * Extension interface + */ + +static LIST_HEAD(tm6000_extension_devlist); + +int tm6000_call_fillbuf(struct tm6000_core *dev, enum tm6000_ops_type type, +			char *buf, int size) +{ +	struct tm6000_ops *ops = NULL; + +	/* FIXME: tm6000_extension_devlist_lock should be a spinlock */ + +	if (!list_empty(&tm6000_extension_devlist)) { +		list_for_each_entry(ops, &tm6000_extension_devlist, next) { +			if (ops->fillbuf && ops->type == type) +				ops->fillbuf(dev, buf, size); +		} +	} + +	return 0; +} + +int tm6000_register_extension(struct tm6000_ops *ops) +{ +	struct tm6000_core *dev = NULL; + +	mutex_lock(&tm6000_devlist_mutex); +	list_add_tail(&ops->next, &tm6000_extension_devlist); +	list_for_each_entry(dev, &tm6000_devlist, devlist) { +		ops->init(dev); +		printk(KERN_INFO "%s: Initialized (%s) extension\n", +		       dev->name, ops->name); +	} +	mutex_unlock(&tm6000_devlist_mutex); +	return 0; +} +EXPORT_SYMBOL(tm6000_register_extension); + +void tm6000_unregister_extension(struct tm6000_ops *ops) +{ +	struct tm6000_core *dev = NULL; + +	mutex_lock(&tm6000_devlist_mutex); +	list_for_each_entry(dev, &tm6000_devlist, devlist) +		ops->fini(dev); + +	printk(KERN_INFO "tm6000: Remove (%s) extension\n", ops->name); +	list_del(&ops->next); +	mutex_unlock(&tm6000_devlist_mutex); +} +EXPORT_SYMBOL(tm6000_unregister_extension); + +void tm6000_init_extension(struct tm6000_core *dev) +{ +	struct tm6000_ops *ops = NULL; + +	mutex_lock(&tm6000_devlist_mutex); +	if (!list_empty(&tm6000_extension_devlist)) { +		list_for_each_entry(ops, &tm6000_extension_devlist, next) { +			if (ops->init) +				ops->init(dev); +		} +	} +	mutex_unlock(&tm6000_devlist_mutex); +} + +void tm6000_close_extension(struct tm6000_core *dev) +{ +	struct tm6000_ops *ops = NULL; + +	mutex_lock(&tm6000_devlist_mutex); +	if (!list_empty(&tm6000_extension_devlist)) { +		list_for_each_entry(ops, &tm6000_extension_devlist, next) { +			if (ops->fini) +				ops->fini(dev); +		} +	} +	mutex_unlock(&tm6000_devlist_mutex); +} diff --git a/drivers/media/usb/tm6000/tm6000-dvb.c b/drivers/media/usb/tm6000/tm6000-dvb.c new file mode 100644 index 00000000000..095f5db1a79 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-dvb.c @@ -0,0 +1,467 @@ +/* + *  tm6000-dvb.c - dvb-t support for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + * + *  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 version 2 + * + *  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/kernel.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include "tm6000.h" +#include "tm6000-regs.h" + +#include "zl10353.h" + +#include <media/tuner.h> + +#include "tuner-xc2028.h" +#include "xc5000.h" + +MODULE_DESCRIPTION("DVB driver extension module for tm5600/6000/6010 based TV cards"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL"); + +MODULE_SUPPORTED_DEVICE("{{Trident, tm5600}," +			"{{Trident, tm6000}," +			"{{Trident, tm6010}"); + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug message"); + +static inline void print_err_status(struct tm6000_core *dev, +				    int packet, int status) +{ +	char *errmsg = "Unknown"; + +	switch (status) { +	case -ENOENT: +		errmsg = "unlinked synchronuously"; +		break; +	case -ECONNRESET: +		errmsg = "unlinked asynchronuously"; +		break; +	case -ENOSR: +		errmsg = "Buffer error (overrun)"; +		break; +	case -EPIPE: +		errmsg = "Stalled (device not responding)"; +		break; +	case -EOVERFLOW: +		errmsg = "Babble (bad cable?)"; +		break; +	case -EPROTO: +		errmsg = "Bit-stuff error (bad cable?)"; +		break; +	case -EILSEQ: +		errmsg = "CRC/Timeout (could be anything)"; +		break; +	case -ETIME: +		errmsg = "Device does not respond"; +		break; +	} +	if (packet < 0) { +		dprintk(dev, 1, "URB status %d [%s].\n", +			status, errmsg); +	} else { +		dprintk(dev, 1, "URB packet %d, status %d [%s].\n", +			packet, status, errmsg); +	} +} + +static void tm6000_urb_received(struct urb *urb) +{ +	int ret; +	struct tm6000_core *dev = urb->context; + +	switch (urb->status) { +	case 0: +	case -ETIMEDOUT: +		break; +	case -ENOENT: +	case -ECONNRESET: +	case -ESHUTDOWN: +		return; +	default: +		print_err_status(dev, 0, urb->status); +	} + +	if (urb->actual_length > 0) +		dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer, +						   urb->actual_length); + +	if (dev->dvb->streams > 0) { +		ret = usb_submit_urb(urb, GFP_ATOMIC); +		if (ret < 0) { +			printk(KERN_ERR "tm6000:  error %s\n", __func__); +			kfree(urb->transfer_buffer); +			usb_free_urb(urb); +		} +	} +} + +static int tm6000_start_stream(struct tm6000_core *dev) +{ +	int ret; +	unsigned int pipe, size; +	struct tm6000_dvb *dvb = dev->dvb; + +	printk(KERN_INFO "tm6000: got start stream request %s\n", __func__); + +	if (dev->mode != TM6000_MODE_DIGITAL) { +		tm6000_init_digital_mode(dev); +		dev->mode = TM6000_MODE_DIGITAL; +	} + +	dvb->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); +	if (dvb->bulk_urb == NULL) { +		printk(KERN_ERR "tm6000: couldn't allocate urb\n"); +		return -ENOMEM; +	} + +	pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in.endp->desc.bEndpointAddress +							  & USB_ENDPOINT_NUMBER_MASK); + +	size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); +	size = size * 15; /* 512 x 8 or 12 or 15 */ + +	dvb->bulk_urb->transfer_buffer = kzalloc(size, GFP_KERNEL); +	if (dvb->bulk_urb->transfer_buffer == NULL) { +		usb_free_urb(dvb->bulk_urb); +		printk(KERN_ERR "tm6000: couldn't allocate transfer buffer!\n"); +		return -ENOMEM; +	} + +	usb_fill_bulk_urb(dvb->bulk_urb, dev->udev, pipe, +						 dvb->bulk_urb->transfer_buffer, +						 size, +						 tm6000_urb_received, dev); + +	ret = usb_clear_halt(dev->udev, pipe); +	if (ret < 0) { +		printk(KERN_ERR "tm6000: error %i in %s during pipe reset\n", +							ret, __func__); +		return ret; +	} else +		printk(KERN_ERR "tm6000: pipe resetted\n"); + +/*	mutex_lock(&tm6000_driver.open_close_mutex); */ +	ret = usb_submit_urb(dvb->bulk_urb, GFP_ATOMIC); + +/*	mutex_unlock(&tm6000_driver.open_close_mutex); */ +	if (ret) { +		printk(KERN_ERR "tm6000: submit of urb failed (error=%i)\n", +									ret); + +		kfree(dvb->bulk_urb->transfer_buffer); +		usb_free_urb(dvb->bulk_urb); +		return ret; +	} + +	return 0; +} + +static void tm6000_stop_stream(struct tm6000_core *dev) +{ +	struct tm6000_dvb *dvb = dev->dvb; + +	if (dvb->bulk_urb) { +		printk(KERN_INFO "urb killing\n"); +		usb_kill_urb(dvb->bulk_urb); +		printk(KERN_INFO "urb buffer free\n"); +		kfree(dvb->bulk_urb->transfer_buffer); +		usb_free_urb(dvb->bulk_urb); +		dvb->bulk_urb = NULL; +	} +} + +static int tm6000_start_feed(struct dvb_demux_feed *feed) +{ +	struct dvb_demux *demux = feed->demux; +	struct tm6000_core *dev = demux->priv; +	struct tm6000_dvb *dvb = dev->dvb; +	printk(KERN_INFO "tm6000: got start feed request %s\n", __func__); + +	mutex_lock(&dvb->mutex); +	if (dvb->streams == 0) { +		dvb->streams = 1; +/*		mutex_init(&tm6000_dev->streming_mutex); */ +		tm6000_start_stream(dev); +	} else +		++(dvb->streams); +	mutex_unlock(&dvb->mutex); + +	return 0; +} + +static int tm6000_stop_feed(struct dvb_demux_feed *feed) +{ +	struct dvb_demux *demux = feed->demux; +	struct tm6000_core *dev = demux->priv; +	struct tm6000_dvb *dvb = dev->dvb; + +	printk(KERN_INFO "tm6000: got stop feed request %s\n", __func__); + +	mutex_lock(&dvb->mutex); + +	printk(KERN_INFO "stream %#x\n", dvb->streams); +	--(dvb->streams); +	if (dvb->streams == 0) { +		printk(KERN_INFO "stop stream\n"); +		tm6000_stop_stream(dev); +/*		mutex_destroy(&tm6000_dev->streaming_mutex); */ +	} +	mutex_unlock(&dvb->mutex); +/*	mutex_destroy(&tm6000_dev->streaming_mutex); */ + +	return 0; +} + +static int tm6000_dvb_attach_frontend(struct tm6000_core *dev) +{ +	struct tm6000_dvb *dvb = dev->dvb; + +	if (dev->caps.has_zl10353) { +		struct zl10353_config config = { +				     .demod_address = dev->demod_addr, +				     .no_tuner = 1, +				     .parallel_ts = 1, +				     .if2 = 45700, +				     .disable_i2c_gate_ctrl = 1, +				    }; + +		dvb->frontend = dvb_attach(zl10353_attach, &config, +							   &dev->i2c_adap); +	} else { +		printk(KERN_ERR "tm6000: no frontend defined for the device!\n"); +		return -1; +	} + +	return (!dvb->frontend) ? -1 : 0; +} + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int register_dvb(struct tm6000_core *dev) +{ +	int ret = -1; +	struct tm6000_dvb *dvb = dev->dvb; + +	mutex_init(&dvb->mutex); + +	dvb->streams = 0; + +	/* attach the frontend */ +	ret = tm6000_dvb_attach_frontend(dev); +	if (ret < 0) { +		printk(KERN_ERR "tm6000: couldn't attach the frontend!\n"); +		goto err; +	} + +	ret = dvb_register_adapter(&dvb->adapter, "Trident TVMaster 6000 DVB-T", +					THIS_MODULE, &dev->udev->dev, adapter_nr); +	dvb->adapter.priv = dev; + +	if (dvb->frontend) { +		switch (dev->tuner_type) { +		case TUNER_XC2028: { +			struct xc2028_config cfg = { +				.i2c_adap = &dev->i2c_adap, +				.i2c_addr = dev->tuner_addr, +			}; + +			dvb->frontend->callback = tm6000_tuner_callback; +			ret = dvb_register_frontend(&dvb->adapter, dvb->frontend); +			if (ret < 0) { +				printk(KERN_ERR +					"tm6000: couldn't register frontend\n"); +				goto adapter_err; +			} + +			if (!dvb_attach(xc2028_attach, dvb->frontend, &cfg)) { +				printk(KERN_ERR "tm6000: couldn't register " +						"frontend (xc3028)\n"); +				ret = -EINVAL; +				goto frontend_err; +			} +			printk(KERN_INFO "tm6000: XC2028/3028 asked to be " +					 "attached to frontend!\n"); +			break; +			} +		case TUNER_XC5000: { +			struct xc5000_config cfg = { +				.i2c_address = dev->tuner_addr, +			}; + +			dvb->frontend->callback = tm6000_xc5000_callback; +			ret = dvb_register_frontend(&dvb->adapter, dvb->frontend); +			if (ret < 0) { +				printk(KERN_ERR +					"tm6000: couldn't register frontend\n"); +				goto adapter_err; +			} + +			if (!dvb_attach(xc5000_attach, dvb->frontend, &dev->i2c_adap, &cfg)) { +				printk(KERN_ERR "tm6000: couldn't register " +						"frontend (xc5000)\n"); +				ret = -EINVAL; +				goto frontend_err; +			} +			printk(KERN_INFO "tm6000: XC5000 asked to be " +					 "attached to frontend!\n"); +			break; +			} +		} +	} else +		printk(KERN_ERR "tm6000: no frontend found\n"); + +	dvb->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING +							    | DMX_MEMORY_BASED_FILTERING; +	dvb->demux.priv = dev; +	dvb->demux.filternum = 8; +	dvb->demux.feednum = 8; +	dvb->demux.start_feed = tm6000_start_feed; +	dvb->demux.stop_feed = tm6000_stop_feed; +	dvb->demux.write_to_decoder = NULL; +	ret = dvb_dmx_init(&dvb->demux); +	if (ret < 0) { +		printk(KERN_ERR "tm6000: dvb_dmx_init failed (errno = %d)\n", ret); +		goto frontend_err; +	} + +	dvb->dmxdev.filternum = dev->dvb->demux.filternum; +	dvb->dmxdev.demux = &dev->dvb->demux.dmx; +	dvb->dmxdev.capabilities = 0; + +	ret =  dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter); +	if (ret < 0) { +		printk(KERN_ERR "tm6000: dvb_dmxdev_init failed (errno = %d)\n", ret); +		goto dvb_dmx_err; +	} + +	return 0; + +dvb_dmx_err: +	dvb_dmx_release(&dvb->demux); +frontend_err: +	if (dvb->frontend) { +		dvb_unregister_frontend(dvb->frontend); +		dvb_frontend_detach(dvb->frontend); +	} +adapter_err: +	dvb_unregister_adapter(&dvb->adapter); +err: +	return ret; +} + +static void unregister_dvb(struct tm6000_core *dev) +{ +	struct tm6000_dvb *dvb = dev->dvb; + +	if (dvb->bulk_urb != NULL) { +		struct urb *bulk_urb = dvb->bulk_urb; + +		kfree(bulk_urb->transfer_buffer); +		bulk_urb->transfer_buffer = NULL; +		usb_unlink_urb(bulk_urb); +		usb_free_urb(bulk_urb); +	} + +/*	mutex_lock(&tm6000_driver.open_close_mutex); */ +	if (dvb->frontend) { +		dvb_unregister_frontend(dvb->frontend); +		dvb_frontend_detach(dvb->frontend); +	} + +	dvb_dmxdev_release(&dvb->dmxdev); +	dvb_dmx_release(&dvb->demux); +	dvb_unregister_adapter(&dvb->adapter); +	mutex_destroy(&dvb->mutex); +/*	mutex_unlock(&tm6000_driver.open_close_mutex); */ +} + +static int dvb_init(struct tm6000_core *dev) +{ +	struct tm6000_dvb *dvb; +	int rc; + +	if (!dev) +		return 0; + +	if (!dev->caps.has_dvb) +		return 0; + +	if (dev->udev->speed == USB_SPEED_FULL) { +		printk(KERN_INFO "This USB2.0 device cannot be run on a USB1.1 port. (it lacks a hardware PID filter)\n"); +		return 0; +	} + +	dvb = kzalloc(sizeof(struct tm6000_dvb), GFP_KERNEL); +	if (!dvb) { +		printk(KERN_INFO "Cannot allocate memory\n"); +		return -ENOMEM; +	} + +	dev->dvb = dvb; + +	rc = register_dvb(dev); +	if (rc < 0) { +		kfree(dvb); +		dev->dvb = NULL; +		return 0; +	} + +	return 0; +} + +static int dvb_fini(struct tm6000_core *dev) +{ +	if (!dev) +		return 0; + +	if (!dev->caps.has_dvb) +		return 0; + +	if (dev->dvb) { +		unregister_dvb(dev); +		kfree(dev->dvb); +		dev->dvb = NULL; +	} + +	return 0; +} + +static struct tm6000_ops dvb_ops = { +	.type	= TM6000_DVB, +	.name	= "TM6000 dvb Extension", +	.init	= dvb_init, +	.fini	= dvb_fini, +}; + +static int __init tm6000_dvb_register(void) +{ +	return tm6000_register_extension(&dvb_ops); +} + +static void __exit tm6000_dvb_unregister(void) +{ +	tm6000_unregister_extension(&dvb_ops); +} + +module_init(tm6000_dvb_register); +module_exit(tm6000_dvb_unregister); diff --git a/drivers/media/usb/tm6000/tm6000-i2c.c b/drivers/media/usb/tm6000/tm6000-i2c.c new file mode 100644 index 00000000000..c7e23e3dd75 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-i2c.c @@ -0,0 +1,334 @@ +/* + *  tm6000-i2c.c - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + * + *  Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + *	- Fix SMBus Read Byte command + * + *  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 version 2 + * + *  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/kernel.h> +#include <linux/usb.h> +#include <linux/i2c.h> + +#include "tm6000.h" +#include "tm6000-regs.h" +#include <media/v4l2-common.h> +#include <media/tuner.h> +#include "tuner-xc2028.h" + + +/* ----------------------------------------------------------- */ + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +#define i2c_dprintk(lvl, fmt, args...) if (i2c_debug >= lvl) do { \ +			printk(KERN_DEBUG "%s at %s: " fmt, \ +			dev->name, __func__, ##args); } while (0) + +static int tm6000_i2c_send_regs(struct tm6000_core *dev, unsigned char addr, +				__u8 reg, char *buf, int len) +{ +	int rc; +	unsigned int i2c_packet_limit = 16; + +	if (dev->dev_type == TM6010) +		i2c_packet_limit = 80; + +	if (!buf) +		return -1; + +	if (len < 1 || len > i2c_packet_limit) { +		printk(KERN_ERR "Incorrect length of i2c packet = %d, limit set to %d\n", +			len, i2c_packet_limit); +		return -1; +	} + +	/* capture mutex */ +	rc = tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR | +		USB_RECIP_DEVICE, REQ_16_SET_GET_I2C_WR1_RDN, +		addr | reg << 8, 0, buf, len); + +	if (rc < 0) { +		/* release mutex */ +		return rc; +	} + +	/* release mutex */ +	return rc; +} + +/* Generic read - doesn't work fine with 16bit registers */ +static int tm6000_i2c_recv_regs(struct tm6000_core *dev, unsigned char addr, +				__u8 reg, char *buf, int len) +{ +	int rc; +	u8 b[2]; +	unsigned int i2c_packet_limit = 16; + +	if (dev->dev_type == TM6010) +		i2c_packet_limit = 64; + +	if (!buf) +		return -1; + +	if (len < 1 || len > i2c_packet_limit) { +		printk(KERN_ERR "Incorrect length of i2c packet = %d, limit set to %d\n", +			len, i2c_packet_limit); +		return -1; +	} + +	/* capture mutex */ +	if ((dev->caps.has_zl10353) && (dev->demod_addr << 1 == addr) && (reg % 2 == 0)) { +		/* +		 * Workaround an I2C bug when reading from zl10353 +		 */ +		reg -= 1; +		len += 1; + +		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +			REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, b, len); + +		*buf = b[1]; +	} else { +		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +			REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, buf, len); +	} + +	/* release mutex */ +	return rc; +} + +/* + * read from a 16bit register + * for example xc2028, xc3028 or xc3028L + */ +static int tm6000_i2c_recv_regs16(struct tm6000_core *dev, unsigned char addr, +				  __u16 reg, char *buf, int len) +{ +	int rc; +	unsigned char ureg; + +	if (!buf || len != 2) +		return -1; + +	/* capture mutex */ +	if (dev->dev_type == TM6010) { +		ureg = reg & 0xFF; +		rc = tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR | +			USB_RECIP_DEVICE, REQ_16_SET_GET_I2C_WR1_RDN, +			addr | (reg & 0xFF00), 0, &ureg, 1); + +		if (rc < 0) { +			/* release mutex */ +			return rc; +		} + +		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | +			USB_RECIP_DEVICE, REQ_35_AFTEK_TUNER_READ, +			reg, 0, buf, len); +	} else { +		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | +			USB_RECIP_DEVICE, REQ_14_SET_GET_I2C_WR2_RDN, +			addr, reg, buf, len); +	} + +	/* release mutex */ +	return rc; +} + +static int tm6000_i2c_xfer(struct i2c_adapter *i2c_adap, +			   struct i2c_msg msgs[], int num) +{ +	struct tm6000_core *dev = i2c_adap->algo_data; +	int addr, rc, i, byte; + +	if (num <= 0) +		return 0; +	for (i = 0; i < num; i++) { +		addr = (msgs[i].addr << 1) & 0xff; +		i2c_dprintk(2, "%s %s addr=0x%x len=%d:", +			 (msgs[i].flags & I2C_M_RD) ? "read" : "write", +			 i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len); +		if (msgs[i].flags & I2C_M_RD) { +			/* read request without preceding register selection */ +			/* +			 * The TM6000 only supports a read transaction +			 * immediately after a 1 or 2 byte write to select +			 * a register.  We cannot fulfil this request. +			 */ +			i2c_dprintk(2, " read without preceding write not" +				       " supported"); +			rc = -EOPNOTSUPP; +			goto err; +		} else if (i + 1 < num && msgs[i].len <= 2 && +			   (msgs[i + 1].flags & I2C_M_RD) && +			   msgs[i].addr == msgs[i + 1].addr) { +			/* 1 or 2 byte write followed by a read */ +			if (i2c_debug >= 2) +				for (byte = 0; byte < msgs[i].len; byte++) +					printk(KERN_CONT " %02x", msgs[i].buf[byte]); +			i2c_dprintk(2, "; joined to read %s len=%d:", +				    i == num - 2 ? "stop" : "nonstop", +				    msgs[i + 1].len); + +			if (msgs[i].len == 2) { +				rc = tm6000_i2c_recv_regs16(dev, addr, +					msgs[i].buf[0] << 8 | msgs[i].buf[1], +					msgs[i + 1].buf, msgs[i + 1].len); +			} else { +				rc = tm6000_i2c_recv_regs(dev, addr, msgs[i].buf[0], +					msgs[i + 1].buf, msgs[i + 1].len); +			} + +			i++; + +			if (addr == dev->tuner_addr << 1) { +				tm6000_set_reg(dev, REQ_50_SET_START, 0, 0); +				tm6000_set_reg(dev, REQ_51_SET_STOP, 0, 0); +			} +			if (i2c_debug >= 2) +				for (byte = 0; byte < msgs[i].len; byte++) +					printk(KERN_CONT " %02x", msgs[i].buf[byte]); +		} else { +			/* write bytes */ +			if (i2c_debug >= 2) +				for (byte = 0; byte < msgs[i].len; byte++) +					printk(KERN_CONT " %02x", msgs[i].buf[byte]); +			rc = tm6000_i2c_send_regs(dev, addr, msgs[i].buf[0], +				msgs[i].buf + 1, msgs[i].len - 1); +		} +		if (i2c_debug >= 2) +			printk(KERN_CONT "\n"); +		if (rc < 0) +			goto err; +	} + +	return num; +err: +	i2c_dprintk(2, " ERROR: %i\n", rc); +	return rc; +} + +static int tm6000_i2c_eeprom(struct tm6000_core *dev) +{ +	int i, rc; +	unsigned char *p = dev->eedata; +	unsigned char bytes[17]; + +	dev->i2c_client.addr = 0xa0 >> 1; +	dev->eedata_size = 0; + +	bytes[16] = '\0'; +	for (i = 0; i < sizeof(dev->eedata); ) { +		*p = i; +		rc = tm6000_i2c_recv_regs(dev, 0xa0, i, p, 1); +		if (rc < 1) { +			if (p == dev->eedata) +				goto noeeprom; +			else { +				printk(KERN_WARNING +				"%s: i2c eeprom read error (err=%d)\n", +				dev->name, rc); +			} +			return -EINVAL; +		} +		dev->eedata_size++; +		p++; +		if (0 == (i % 16)) +			printk(KERN_INFO "%s: i2c eeprom %02x:", dev->name, i); +		printk(KERN_CONT " %02x", dev->eedata[i]); +		if ((dev->eedata[i] >= ' ') && (dev->eedata[i] <= 'z')) +			bytes[i%16] = dev->eedata[i]; +		else +			bytes[i%16] = '.'; + +		i++; + +		if (0 == (i % 16)) { +			bytes[16] = '\0'; +			printk(KERN_CONT "  %s\n", bytes); +		} +	} +	if (0 != (i%16)) { +		bytes[i%16] = '\0'; +		for (i %= 16; i < 16; i++) +			printk(KERN_CONT "   "); +		printk(KERN_CONT "  %s\n", bytes); +	} + +	return 0; + +noeeprom: +	printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n", +	       dev->name, rc); +	return -EINVAL; +} + +/* ----------------------------------------------------------- */ + +/* + * functionality() + */ +static u32 functionality(struct i2c_adapter *adap) +{ +	return I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm tm6000_algo = { +	.master_xfer   = tm6000_i2c_xfer, +	.functionality = functionality, +}; + +/* ----------------------------------------------------------- */ + +/* + * tm6000_i2c_register() + * register i2c bus + */ +int tm6000_i2c_register(struct tm6000_core *dev) +{ +	int rc; + +	dev->i2c_adap.owner = THIS_MODULE; +	dev->i2c_adap.algo = &tm6000_algo; +	dev->i2c_adap.dev.parent = &dev->udev->dev; +	strlcpy(dev->i2c_adap.name, dev->name, sizeof(dev->i2c_adap.name)); +	dev->i2c_adap.algo_data = dev; +	i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev); +	rc = i2c_add_adapter(&dev->i2c_adap); +	if (rc) +		return rc; + +	dev->i2c_client.adapter = &dev->i2c_adap; +	strlcpy(dev->i2c_client.name, "tm6000 internal", I2C_NAME_SIZE); +	tm6000_i2c_eeprom(dev); + +	return 0; +} + +/* + * tm6000_i2c_unregister() + * unregister i2c_bus + */ +int tm6000_i2c_unregister(struct tm6000_core *dev) +{ +	i2c_del_adapter(&dev->i2c_adap); +	return 0; +} diff --git a/drivers/media/usb/tm6000/tm6000-input.c b/drivers/media/usb/tm6000/tm6000-input.c new file mode 100644 index 00000000000..d1af5438c16 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-input.c @@ -0,0 +1,501 @@ +/* + *  tm6000-input.c - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2010 Stefan Ringel <stefan.ringel@arcor.de> + * + *  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 version 2 + * + *  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/delay.h> + +#include <linux/input.h> +#include <linux/usb.h> + +#include <media/rc-core.h> + +#include "tm6000.h" +#include "tm6000-regs.h" + +static unsigned int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "debug message level"); + +static unsigned int enable_ir = 1; +module_param(enable_ir, int, 0644); +MODULE_PARM_DESC(enable_ir, "enable ir (default is enable)"); + +static unsigned int ir_clock_mhz = 12; +module_param(ir_clock_mhz, int, 0644); +MODULE_PARM_DESC(enable_ir, "ir clock, in MHz"); + +#define URB_SUBMIT_DELAY	100	/* ms - Delay to submit an URB request on retrial and init */ +#define URB_INT_LED_DELAY	100	/* ms - Delay to turn led on again on int mode */ + +#undef dprintk + +#define dprintk(level, fmt, arg...) do {\ +	if (ir_debug >= level) \ +		printk(KERN_DEBUG "%s/ir: " fmt, ir->name , ## arg); \ +	} while (0) + +struct tm6000_ir_poll_result { +	u16 rc_data; +}; + +struct tm6000_IR { +	struct tm6000_core	*dev; +	struct rc_dev		*rc; +	char			name[32]; +	char			phys[32]; + +	/* poll expernal decoder */ +	int			polling; +	struct delayed_work	work; +	u8			wait:1; +	u8			pwled:2; +	u8			submit_urb:1; +	u16			key_addr; +	struct urb		*int_urb; + +	/* IR device properties */ +	u64			rc_type; +}; + +void tm6000_ir_wait(struct tm6000_core *dev, u8 state) +{ +	struct tm6000_IR *ir = dev->ir; + +	if (!dev->ir) +		return; + +	dprintk(2, "%s: %i\n",__func__, ir->wait); + +	if (state) +		ir->wait = 1; +	else +		ir->wait = 0; +} + +static int tm6000_ir_config(struct tm6000_IR *ir) +{ +	struct tm6000_core *dev = ir->dev; +	u32 pulse = 0, leader = 0; + +	dprintk(2, "%s\n",__func__); + +	/* +	 * The IR decoder supports RC-5 or NEC, with a configurable timing. +	 * The timing configuration there is not that accurate, as it uses +	 * approximate values. The NEC spec mentions a 562.5 unit period, +	 * and RC-5 uses a 888.8 period. +	 * Currently, driver assumes a clock provided by a 12 MHz XTAL, but +	 * a modprobe parameter can adjust it. +	 * Adjustments are required for other timings. +	 * It seems that the 900ms timing for NEC is used to detect a RC-5 +	 * IR, in order to discard such decoding +	 */ + +	switch (ir->rc_type) { +	case RC_BIT_NEC: +		leader = 900;	/* ms */ +		pulse  = 700;	/* ms - the actual value would be 562 */ +		break; +	default: +	case RC_BIT_RC5: +		leader = 900;	/* ms - from the NEC decoding */ +		pulse  = 1780;	/* ms - The actual value would be 1776 */ +		break; +	} + +	pulse = ir_clock_mhz * pulse; +	leader = ir_clock_mhz * leader; +	if (ir->rc_type == RC_BIT_NEC) +		leader = leader | 0x8000; + +	dprintk(2, "%s: %s, %d MHz, leader = 0x%04x, pulse = 0x%06x \n", +		__func__, +		(ir->rc_type == RC_BIT_NEC) ? "NEC" : "RC-5", +		ir_clock_mhz, leader, pulse); + +	/* Remote WAKEUP = enable, normal mode, from IR decoder output */ +	tm6000_set_reg(dev, TM6010_REQ07_RE5_REMOTE_WAKEUP, 0xfe); + +	/* Enable IR reception on non-busrt mode */ +	tm6000_set_reg(dev, TM6010_REQ07_RD8_IR, 0x2f); + +	/* IR_WKUP_SEL = Low byte in decoded IR data */ +	tm6000_set_reg(dev, TM6010_REQ07_RDA_IR_WAKEUP_SEL, 0xff); +	/* IR_WKU_ADD code */ +	tm6000_set_reg(dev, TM6010_REQ07_RDB_IR_WAKEUP_ADD, 0xff); + +	tm6000_set_reg(dev, TM6010_REQ07_RDC_IR_LEADER1, leader >> 8); +	tm6000_set_reg(dev, TM6010_REQ07_RDD_IR_LEADER0, leader); + +	tm6000_set_reg(dev, TM6010_REQ07_RDE_IR_PULSE_CNT1, pulse >> 8); +	tm6000_set_reg(dev, TM6010_REQ07_RDF_IR_PULSE_CNT0, pulse); + +	if (!ir->polling) +		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0); +	else +		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 1); +	msleep(10); + +	/* Shows that IR is working via the LED */ +	tm6000_flash_led(dev, 0); +	msleep(100); +	tm6000_flash_led(dev, 1); +	ir->pwled = 1; + +	return 0; +} + +static void tm6000_ir_urb_received(struct urb *urb) +{ +	struct tm6000_core *dev = urb->context; +	struct tm6000_IR *ir = dev->ir; +	struct tm6000_ir_poll_result poll_result; +	char *buf; + +	dprintk(2, "%s\n",__func__); +	if (urb->status < 0 || urb->actual_length <= 0) { +		printk(KERN_INFO "tm6000: IR URB failure: status: %i, length %i\n", +		       urb->status, urb->actual_length); +		ir->submit_urb = 1; +		schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY)); +		return; +	} +	buf = urb->transfer_buffer; + +	if (ir_debug) +		print_hex_dump(KERN_DEBUG, "tm6000: IR data: ", +			       DUMP_PREFIX_OFFSET,16, 1, +			       buf, urb->actual_length, false); + +	poll_result.rc_data = buf[0]; +	if (urb->actual_length > 1) +		poll_result.rc_data |= buf[1] << 8; + +	dprintk(1, "%s, scancode: 0x%04x\n",__func__, poll_result.rc_data); +	rc_keydown(ir->rc, poll_result.rc_data, 0); + +	usb_submit_urb(urb, GFP_ATOMIC); +	/* +	 * Flash the led. We can't do it here, as it is running on IRQ context. +	 * So, use the scheduler to do it, in a few ms. +	 */ +	ir->pwled = 2; +	schedule_delayed_work(&ir->work, msecs_to_jiffies(10)); +} + +static void tm6000_ir_handle_key(struct work_struct *work) +{ +	struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work); +	struct tm6000_core *dev = ir->dev; +	struct tm6000_ir_poll_result poll_result; +	int rc; +	u8 buf[2]; + +	if (ir->wait) +		return; + +	dprintk(3, "%s\n",__func__); + +	rc = tm6000_read_write_usb(dev, USB_DIR_IN | +		USB_TYPE_VENDOR | USB_RECIP_DEVICE, +		REQ_02_GET_IR_CODE, 0, 0, buf, 2); +	if (rc < 0) +		return; + +	if (rc > 1) +		poll_result.rc_data = buf[0] | buf[1] << 8; +	else +		poll_result.rc_data = buf[0]; + +	/* Check if something was read */ +	if ((poll_result.rc_data & 0xff) == 0xff) { +		if (!ir->pwled) { +			tm6000_flash_led(dev, 1); +			ir->pwled = 1; +		} +		return; +	} + +	dprintk(1, "%s, scancode: 0x%04x\n",__func__, poll_result.rc_data); +	rc_keydown(ir->rc, poll_result.rc_data, 0); +	tm6000_flash_led(dev, 0); +	ir->pwled = 0; + +	/* Re-schedule polling */ +	schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling)); +} + +static void tm6000_ir_int_work(struct work_struct *work) +{ +	struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work); +	struct tm6000_core *dev = ir->dev; +	int rc; + +	dprintk(3, "%s, submit_urb = %d, pwled = %d\n",__func__, ir->submit_urb, +		ir->pwled); + +	if (ir->submit_urb) { +		dprintk(3, "Resubmit urb\n"); +		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0); + +		rc = usb_submit_urb(ir->int_urb, GFP_ATOMIC); +		if (rc < 0) { +			printk(KERN_ERR "tm6000: Can't submit an IR interrupt. Error %i\n", +			       rc); +			/* Retry in 100 ms */ +			schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY)); +			return; +		} +		ir->submit_urb = 0; +	} + +	/* Led is enabled only if USB submit doesn't fail */ +	if (ir->pwled == 2) { +		tm6000_flash_led(dev, 0); +		ir->pwled = 0; +		schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_INT_LED_DELAY)); +	} else if (!ir->pwled) { +		tm6000_flash_led(dev, 1); +		ir->pwled = 1; +	} +} + +static int tm6000_ir_start(struct rc_dev *rc) +{ +	struct tm6000_IR *ir = rc->priv; + +	dprintk(2, "%s\n",__func__); + +	schedule_delayed_work(&ir->work, 0); + +	return 0; +} + +static void tm6000_ir_stop(struct rc_dev *rc) +{ +	struct tm6000_IR *ir = rc->priv; + +	dprintk(2, "%s\n",__func__); + +	cancel_delayed_work_sync(&ir->work); +} + +static int tm6000_ir_change_protocol(struct rc_dev *rc, u64 *rc_type) +{ +	struct tm6000_IR *ir = rc->priv; + +	if (!ir) +		return 0; + +	dprintk(2, "%s\n",__func__); + +	if ((rc->rc_map.scan) && (*rc_type == RC_BIT_NEC)) +		ir->key_addr = ((rc->rc_map.scan[0].scancode >> 8) & 0xffff); + +	ir->rc_type = *rc_type; + +	tm6000_ir_config(ir); +	/* TODO */ +	return 0; +} + +static int __tm6000_ir_int_start(struct rc_dev *rc) +{ +	struct tm6000_IR *ir = rc->priv; +	struct tm6000_core *dev; +	int pipe, size; +	int err = -ENOMEM; + +	if (!ir) +		return -ENODEV; +	dev = ir->dev; + +	dprintk(2, "%s\n",__func__); + +	ir->int_urb = usb_alloc_urb(0, GFP_ATOMIC); +	if (!ir->int_urb) +		return -ENOMEM; + +	pipe = usb_rcvintpipe(dev->udev, +		dev->int_in.endp->desc.bEndpointAddress +		& USB_ENDPOINT_NUMBER_MASK); + +	size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); +	dprintk(1, "IR max size: %d\n", size); + +	ir->int_urb->transfer_buffer = kzalloc(size, GFP_ATOMIC); +	if (ir->int_urb->transfer_buffer == NULL) { +		usb_free_urb(ir->int_urb); +		return err; +	} +	dprintk(1, "int interval: %d\n", dev->int_in.endp->desc.bInterval); + +	usb_fill_int_urb(ir->int_urb, dev->udev, pipe, +		ir->int_urb->transfer_buffer, size, +		tm6000_ir_urb_received, dev, +		dev->int_in.endp->desc.bInterval); + +	ir->submit_urb = 1; +	schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY)); + +	return 0; +} + +static void __tm6000_ir_int_stop(struct rc_dev *rc) +{ +	struct tm6000_IR *ir = rc->priv; + +	if (!ir || !ir->int_urb) +		return; + +	dprintk(2, "%s\n",__func__); + +	usb_kill_urb(ir->int_urb); +	kfree(ir->int_urb->transfer_buffer); +	usb_free_urb(ir->int_urb); +	ir->int_urb = NULL; +} + +int tm6000_ir_int_start(struct tm6000_core *dev) +{ +	struct tm6000_IR *ir = dev->ir; + +	if (!ir) +		return 0; + +	return __tm6000_ir_int_start(ir->rc); +} + +void tm6000_ir_int_stop(struct tm6000_core *dev) +{ +	struct tm6000_IR *ir = dev->ir; + +	if (!ir || !ir->rc) +		return; + +	__tm6000_ir_int_stop(ir->rc); +} + +int tm6000_ir_init(struct tm6000_core *dev) +{ +	struct tm6000_IR *ir; +	struct rc_dev *rc; +	int err = -ENOMEM; +	u64 rc_type; + +	if (!enable_ir) +		return -ENODEV; + +	if (!dev->caps.has_remote) +		return 0; + +	if (!dev->ir_codes) +		return 0; + +	ir = kzalloc(sizeof(*ir), GFP_ATOMIC); +	rc = rc_allocate_device(); +	if (!ir || !rc) +		goto out; + +	dprintk(2, "%s\n", __func__); + +	/* record handles to ourself */ +	ir->dev = dev; +	dev->ir = ir; +	ir->rc = rc; + +	/* input setup */ +	rc_set_allowed_protocols(rc, RC_BIT_RC5 | RC_BIT_NEC); +	/* Neded, in order to support NEC remotes with 24 or 32 bits */ +	rc->scanmask = 0xffff; +	rc->priv = ir; +	rc->change_protocol = tm6000_ir_change_protocol; +	if (dev->int_in.endp) { +		rc->open    = __tm6000_ir_int_start; +		rc->close   = __tm6000_ir_int_stop; +		INIT_DELAYED_WORK(&ir->work, tm6000_ir_int_work); +	} else { +		rc->open  = tm6000_ir_start; +		rc->close = tm6000_ir_stop; +		ir->polling = 50; +		INIT_DELAYED_WORK(&ir->work, tm6000_ir_handle_key); +	} +	rc->driver_type = RC_DRIVER_SCANCODE; + +	snprintf(ir->name, sizeof(ir->name), "tm5600/60x0 IR (%s)", +						dev->name); + +	usb_make_path(dev->udev, ir->phys, sizeof(ir->phys)); +	strlcat(ir->phys, "/input0", sizeof(ir->phys)); + +	rc_type = RC_BIT_UNKNOWN; +	tm6000_ir_change_protocol(rc, &rc_type); + +	rc->input_name = ir->name; +	rc->input_phys = ir->phys; +	rc->input_id.bustype = BUS_USB; +	rc->input_id.version = 1; +	rc->input_id.vendor = le16_to_cpu(dev->udev->descriptor.idVendor); +	rc->input_id.product = le16_to_cpu(dev->udev->descriptor.idProduct); +	rc->map_name = dev->ir_codes; +	rc->driver_name = "tm6000"; +	rc->dev.parent = &dev->udev->dev; + +	/* ir register */ +	err = rc_register_device(rc); +	if (err) +		goto out; + +	return 0; + +out: +	dev->ir = NULL; +	rc_free_device(rc); +	kfree(ir); +	return err; +} + +int tm6000_ir_fini(struct tm6000_core *dev) +{ +	struct tm6000_IR *ir = dev->ir; + +	/* skip detach on non attached board */ + +	if (!ir) +		return 0; + +	dprintk(2, "%s\n",__func__); + +	if (!ir->polling) +		__tm6000_ir_int_stop(ir->rc); + +	tm6000_ir_stop(ir->rc); + +	/* Turn off the led */ +	tm6000_flash_led(dev, 0); +	ir->pwled = 0; + +	rc_unregister_device(ir->rc); + +	kfree(ir); +	dev->ir = NULL; + +	return 0; +} diff --git a/drivers/media/usb/tm6000/tm6000-regs.h b/drivers/media/usb/tm6000/tm6000-regs.h new file mode 100644 index 00000000000..a38c251ed57 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-regs.h @@ -0,0 +1,600 @@ +/* + *  tm6000-regs.h - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.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 version 2 + * + *  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. + */ + +/* + * Define TV Master TM5600/TM6000/TM6010 Request codes + */ +#define REQ_00_SET_IR_VALUE		0 +#define REQ_01_SET_WAKEUP_IRCODE	1 +#define REQ_02_GET_IR_CODE		2 +#define REQ_03_SET_GET_MCU_PIN		3 +#define REQ_04_EN_DISABLE_MCU_INT	4 +#define REQ_05_SET_GET_USBREG		5 +	/* Write: RegNum, Value, 0 */ +	/* Read : RegNum, Value, 1, RegStatus */ +#define REQ_06_SET_GET_USBREG_BIT	6 +#define REQ_07_SET_GET_AVREG		7 +	/* Write: RegNum, Value, 0 */ +	/* Read : RegNum, Value, 1, RegStatus */ +#define REQ_08_SET_GET_AVREG_BIT	8 +#define REQ_09_SET_GET_TUNER_FQ		9 +#define REQ_10_SET_TUNER_SYSTEM		10 +#define REQ_11_SET_EEPROM_ADDR		11 +#define REQ_12_SET_GET_EEPROMBYTE	12 +#define REQ_13_GET_EEPROM_SEQREAD	13 +#define REQ_14_SET_GET_I2C_WR2_RDN	14 +#define REQ_15_SET_GET_I2CBYTE		15 +	/* Write: Subaddr, Slave Addr, value, 0 */ +	/* Read : Subaddr, Slave Addr, value, 1 */ +#define REQ_16_SET_GET_I2C_WR1_RDN	16 +	/* Subaddr, Slave Addr, 0, length */ +#define REQ_17_SET_GET_I2CFP		17 +	/* Write: Slave Addr, register, value */ +	/* Read : Slave Addr, register, 2, data */ +#define REQ_20_DATA_TRANSFER		20 +#define REQ_30_I2C_WRITE		30 +#define REQ_31_I2C_READ			31 +#define REQ_35_AFTEK_TUNER_READ		35 +#define REQ_40_GET_VERSION		40 +#define REQ_50_SET_START		50 +#define REQ_51_SET_STOP			51 +#define REQ_52_TRANSMIT_DATA		52 +#define REQ_53_SPI_INITIAL		53 +#define REQ_54_SPI_SETSTART		54 +#define REQ_55_SPI_INOUTDATA		55 +#define REQ_56_SPI_SETSTOP		56 + +/* + * Define TV Master TM5600/TM6000/TM6010 GPIO lines + */ + +#define TM6000_GPIO_CLK		0x101 +#define TM6000_GPIO_DATA	0x100 + +#define TM6000_GPIO_1		0x102 +#define TM6000_GPIO_2		0x103 +#define TM6000_GPIO_3		0x104 +#define TM6000_GPIO_4		0x300 +#define TM6000_GPIO_5		0x301 +#define TM6000_GPIO_6		0x304 +#define TM6000_GPIO_7		0x305 + +/* tm6010 defines GPIO with different values */ +#define TM6010_GPIO_0      0x0102 +#define TM6010_GPIO_1      0x0103 +#define TM6010_GPIO_2      0x0104 +#define TM6010_GPIO_3      0x0105 +#define TM6010_GPIO_4      0x0106 +#define TM6010_GPIO_5      0x0107 +#define TM6010_GPIO_6      0x0300 +#define TM6010_GPIO_7      0x0301 +#define TM6010_GPIO_9      0x0305 +/* + * Define TV Master TM5600/TM6000/TM6010 URB message codes and length + */ + +enum { +	TM6000_URB_MSG_VIDEO = 1, +	TM6000_URB_MSG_AUDIO, +	TM6000_URB_MSG_VBI, +	TM6000_URB_MSG_PTS, +	TM6000_URB_MSG_ERR, +}; + +/* Define specific TM6000 Video decoder registers */ +#define TM6000_REQ07_RD8_TEST_SEL			0x07, 0xd8 +#define TM6000_REQ07_RD9_A_SIM_SEL			0x07, 0xd9 +#define TM6000_REQ07_RDA_CLK_SEL			0x07, 0xda +#define TM6000_REQ07_RDB_OUT_SEL			0x07, 0xdb +#define TM6000_REQ07_RDC_NSEL_I2S			0x07, 0xdc +#define TM6000_REQ07_RDD_GPIO2_MDRV			0x07, 0xdd +#define TM6000_REQ07_RDE_GPIO1_MDRV			0x07, 0xde +#define TM6000_REQ07_RDF_PWDOWN_ACLK			0x07, 0xdf +#define TM6000_REQ07_RE0_VADC_REF_CTL			0x07, 0xe0 +#define TM6000_REQ07_RE1_VADC_DACLIMP			0x07, 0xe1 +#define TM6000_REQ07_RE2_VADC_STATUS_CTL		0x07, 0xe2 +#define TM6000_REQ07_RE3_VADC_INP_LPF_SEL1		0x07, 0xe3 +#define TM6000_REQ07_RE4_VADC_TARGET1			0x07, 0xe4 +#define TM6000_REQ07_RE5_VADC_INP_LPF_SEL2		0x07, 0xe5 +#define TM6000_REQ07_RE6_VADC_TARGET2			0x07, 0xe6 +#define TM6000_REQ07_RE7_VADC_AGAIN_CTL			0x07, 0xe7 +#define TM6000_REQ07_RE8_VADC_PWDOWN_CTL		0x07, 0xe8 +#define TM6000_REQ07_RE9_VADC_INPUT_CTL1		0x07, 0xe9 +#define TM6000_REQ07_REA_VADC_INPUT_CTL2		0x07, 0xea +#define TM6000_REQ07_REB_VADC_AADC_MODE			0x07, 0xeb +#define TM6000_REQ07_REC_VADC_AADC_LVOL			0x07, 0xec +#define TM6000_REQ07_RED_VADC_AADC_RVOL			0x07, 0xed +#define TM6000_REQ07_REE_VADC_CTRL_SEL_CONTROL		0x07, 0xee +#define TM6000_REQ07_REF_VADC_GAIN_MAP_CTL		0x07, 0xef +#define TM6000_REQ07_RFD_BIST_ERR_VST_LOW		0x07, 0xfd +#define TM6000_REQ07_RFE_BIST_ERR_VST_HIGH		0x07, 0xfe + +/* Define TM6000/TM6010 Video decoder registers */ +#define TM6010_REQ07_R00_VIDEO_CONTROL0			0x07, 0x00 +#define TM6010_REQ07_R01_VIDEO_CONTROL1			0x07, 0x01 +#define TM6010_REQ07_R02_VIDEO_CONTROL2			0x07, 0x02 +#define TM6010_REQ07_R03_YC_SEP_CONTROL			0x07, 0x03 +#define TM6010_REQ07_R04_LUMA_HAGC_CONTROL		0x07, 0x04 +#define TM6010_REQ07_R05_NOISE_THRESHOLD		0x07, 0x05 +#define TM6010_REQ07_R06_AGC_GATE_THRESHOLD		0x07, 0x06 +#define TM6010_REQ07_R07_OUTPUT_CONTROL			0x07, 0x07 +#define TM6010_REQ07_R08_LUMA_CONTRAST_ADJ		0x07, 0x08 +#define TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ		0x07, 0x09 +#define TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ		0x07, 0x0a +#define TM6010_REQ07_R0B_CHROMA_HUE_PHASE_ADJ		0x07, 0x0b +#define TM6010_REQ07_R0C_CHROMA_AGC_CONTROL		0x07, 0x0c +#define TM6010_REQ07_R0D_CHROMA_KILL_LEVEL		0x07, 0x0d +#define TM6010_REQ07_R0F_CHROMA_AUTO_POSITION		0x07, 0x0f +#define TM6010_REQ07_R10_AGC_PEAK_NOMINAL		0x07, 0x10 +#define TM6010_REQ07_R11_AGC_PEAK_CONTROL		0x07, 0x11 +#define TM6010_REQ07_R12_AGC_GATE_STARTH		0x07, 0x12 +#define TM6010_REQ07_R13_AGC_GATE_STARTL		0x07, 0x13 +#define TM6010_REQ07_R14_AGC_GATE_WIDTH			0x07, 0x14 +#define TM6010_REQ07_R15_AGC_BP_DELAY			0x07, 0x15 +#define TM6010_REQ07_R16_LOCK_COUNT			0x07, 0x16 +#define TM6010_REQ07_R17_HLOOP_MAXSTATE			0x07, 0x17 +#define TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3		0x07, 0x18 +#define TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2		0x07, 0x19 +#define TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1		0x07, 0x1a +#define TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0		0x07, 0x1b +#define TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3		0x07, 0x1c +#define TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2		0x07, 0x1d +#define TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1		0x07, 0x1e +#define TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0		0x07, 0x1f +#define TM6010_REQ07_R20_HSYNC_RISING_EDGE_TIME		0x07, 0x20 +#define TM6010_REQ07_R21_HSYNC_PHASE_OFFSET		0x07, 0x21 +#define TM6010_REQ07_R22_HSYNC_PLL_START_TIME		0x07, 0x22 +#define TM6010_REQ07_R23_HSYNC_PLL_END_TIME		0x07, 0x23 +#define TM6010_REQ07_R24_HSYNC_TIP_START_TIME		0x07, 0x24 +#define TM6010_REQ07_R25_HSYNC_TIP_END_TIME		0x07, 0x25 +#define TM6010_REQ07_R26_HSYNC_RISING_EDGE_START	0x07, 0x26 +#define TM6010_REQ07_R27_HSYNC_RISING_EDGE_END		0x07, 0x27 +#define TM6010_REQ07_R28_BACKPORCH_START		0x07, 0x28 +#define TM6010_REQ07_R29_BACKPORCH_END			0x07, 0x29 +#define TM6010_REQ07_R2A_HSYNC_FILTER_START		0x07, 0x2a +#define TM6010_REQ07_R2B_HSYNC_FILTER_END		0x07, 0x2b +#define TM6010_REQ07_R2C_CHROMA_BURST_START		0x07, 0x2c +#define TM6010_REQ07_R2D_CHROMA_BURST_END		0x07, 0x2d +#define TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART		0x07, 0x2e +#define TM6010_REQ07_R2F_ACTIVE_VIDEO_HWIDTH		0x07, 0x2f +#define TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART		0x07, 0x30 +#define TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT		0x07, 0x31 +#define TM6010_REQ07_R32_VSYNC_HLOCK_MIN		0x07, 0x32 +#define TM6010_REQ07_R33_VSYNC_HLOCK_MAX		0x07, 0x33 +#define TM6010_REQ07_R34_VSYNC_AGC_MIN			0x07, 0x34 +#define TM6010_REQ07_R35_VSYNC_AGC_MAX			0x07, 0x35 +#define TM6010_REQ07_R36_VSYNC_VBI_MIN			0x07, 0x36 +#define TM6010_REQ07_R37_VSYNC_VBI_MAX			0x07, 0x37 +#define TM6010_REQ07_R38_VSYNC_THRESHOLD		0x07, 0x38 +#define TM6010_REQ07_R39_VSYNC_TIME_CONSTANT		0x07, 0x39 +#define TM6010_REQ07_R3A_STATUS1			0x07, 0x3a +#define TM6010_REQ07_R3B_STATUS2			0x07, 0x3b +#define TM6010_REQ07_R3C_STATUS3			0x07, 0x3c +#define TM6010_REQ07_R3F_RESET				0x07, 0x3f +#define TM6010_REQ07_R40_TELETEXT_VBI_CODE0		0x07, 0x40 +#define TM6010_REQ07_R41_TELETEXT_VBI_CODE1		0x07, 0x41 +#define TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL		0x07, 0x42 +#define TM6010_REQ07_R43_VBI_DATA_TYPE_LINE7		0x07, 0x43 +#define TM6010_REQ07_R44_VBI_DATA_TYPE_LINE8		0x07, 0x44 +#define TM6010_REQ07_R45_VBI_DATA_TYPE_LINE9		0x07, 0x45 +#define TM6010_REQ07_R46_VBI_DATA_TYPE_LINE10		0x07, 0x46 +#define TM6010_REQ07_R47_VBI_DATA_TYPE_LINE11		0x07, 0x47 +#define TM6010_REQ07_R48_VBI_DATA_TYPE_LINE12		0x07, 0x48 +#define TM6010_REQ07_R49_VBI_DATA_TYPE_LINE13		0x07, 0x49 +#define TM6010_REQ07_R4A_VBI_DATA_TYPE_LINE14		0x07, 0x4a +#define TM6010_REQ07_R4B_VBI_DATA_TYPE_LINE15		0x07, 0x4b +#define TM6010_REQ07_R4C_VBI_DATA_TYPE_LINE16		0x07, 0x4c +#define TM6010_REQ07_R4D_VBI_DATA_TYPE_LINE17		0x07, 0x4d +#define TM6010_REQ07_R4E_VBI_DATA_TYPE_LINE18		0x07, 0x4e +#define TM6010_REQ07_R4F_VBI_DATA_TYPE_LINE19		0x07, 0x4f +#define TM6010_REQ07_R50_VBI_DATA_TYPE_LINE20		0x07, 0x50 +#define TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21		0x07, 0x51 +#define TM6010_REQ07_R52_VBI_DATA_TYPE_LINE22		0x07, 0x52 +#define TM6010_REQ07_R53_VBI_DATA_TYPE_LINE23		0x07, 0x53 +#define TM6010_REQ07_R54_VBI_DATA_TYPE_RLINES		0x07, 0x54 +#define TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN		0x07, 0x55 +#define TM6010_REQ07_R56_VBI_LOOP_FILTER_I_GAIN		0x07, 0x56 +#define TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN		0x07, 0x57 +#define TM6010_REQ07_R58_VBI_CAPTION_DTO1		0x07, 0x58 +#define TM6010_REQ07_R59_VBI_CAPTION_DTO0		0x07, 0x59 +#define TM6010_REQ07_R5A_VBI_TELETEXT_DTO1		0x07, 0x5a +#define TM6010_REQ07_R5B_VBI_TELETEXT_DTO0		0x07, 0x5b +#define TM6010_REQ07_R5C_VBI_WSS625_DTO1		0x07, 0x5c +#define TM6010_REQ07_R5D_VBI_WSS625_DTO0		0x07, 0x5d +#define TM6010_REQ07_R5E_VBI_CAPTION_FRAME_START	0x07, 0x5e +#define TM6010_REQ07_R5F_VBI_WSS625_FRAME_START		0x07, 0x5f +#define TM6010_REQ07_R60_TELETEXT_FRAME_START		0x07, 0x60 +#define TM6010_REQ07_R61_VBI_CCDATA1			0x07, 0x61 +#define TM6010_REQ07_R62_VBI_CCDATA2			0x07, 0x62 +#define TM6010_REQ07_R63_VBI_WSS625_DATA1		0x07, 0x63 +#define TM6010_REQ07_R64_VBI_WSS625_DATA2		0x07, 0x64 +#define TM6010_REQ07_R65_VBI_DATA_STATUS		0x07, 0x65 +#define TM6010_REQ07_R66_VBI_CAPTION_START		0x07, 0x66 +#define TM6010_REQ07_R67_VBI_WSS625_START		0x07, 0x67 +#define TM6010_REQ07_R68_VBI_TELETEXT_START		0x07, 0x68 +#define TM6010_REQ07_R70_HSYNC_DTO_INC_STATUS3		0x07, 0x70 +#define TM6010_REQ07_R71_HSYNC_DTO_INC_STATUS2		0x07, 0x71 +#define TM6010_REQ07_R72_HSYNC_DTO_INC_STATUS1		0x07, 0x72 +#define TM6010_REQ07_R73_HSYNC_DTO_INC_STATUS0		0x07, 0x73 +#define TM6010_REQ07_R74_CHROMA_DTO_INC_STATUS3		0x07, 0x74 +#define TM6010_REQ07_R75_CHROMA_DTO_INC_STATUS2		0x07, 0x75 +#define TM6010_REQ07_R76_CHROMA_DTO_INC_STATUS1		0x07, 0x76 +#define TM6010_REQ07_R77_CHROMA_DTO_INC_STATUS0		0x07, 0x77 +#define TM6010_REQ07_R78_AGC_AGAIN_STATUS		0x07, 0x78 +#define TM6010_REQ07_R79_AGC_DGAIN_STATUS		0x07, 0x79 +#define TM6010_REQ07_R7A_CHROMA_MAG_STATUS		0x07, 0x7a +#define TM6010_REQ07_R7B_CHROMA_GAIN_STATUS1		0x07, 0x7b +#define TM6010_REQ07_R7C_CHROMA_GAIN_STATUS0		0x07, 0x7c +#define TM6010_REQ07_R7D_CORDIC_FREQ_STATUS		0x07, 0x7d +#define TM6010_REQ07_R7F_STATUS_NOISE			0x07, 0x7f +#define TM6010_REQ07_R80_COMB_FILTER_TRESHOLD		0x07, 0x80 +#define TM6010_REQ07_R82_COMB_FILTER_CONFIG		0x07, 0x82 +#define TM6010_REQ07_R83_CHROMA_LOCK_CONFIG		0x07, 0x83 +#define TM6010_REQ07_R84_NOISE_NTSC_C			0x07, 0x84 +#define TM6010_REQ07_R85_NOISE_PAL_C			0x07, 0x85 +#define TM6010_REQ07_R86_NOISE_PHASE_C			0x07, 0x86 +#define TM6010_REQ07_R87_NOISE_PHASE_Y			0x07, 0x87 +#define TM6010_REQ07_R8A_CHROMA_LOOPFILTER_STATE	0x07, 0x8a +#define TM6010_REQ07_R8B_CHROMA_HRESAMPLER		0x07, 0x8b +#define TM6010_REQ07_R8D_CPUMP_DELAY_ADJ		0x07, 0x8d +#define TM6010_REQ07_R8E_CPUMP_ADJ			0x07, 0x8e +#define TM6010_REQ07_R8F_CPUMP_DELAY			0x07, 0x8f + +/* Define TM6000/TM6010 Miscellaneous registers */ +#define TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE		0x07, 0xc0 +#define TM6010_REQ07_RC1_TRESHOLD			0x07, 0xc1 +#define TM6010_REQ07_RC2_HSYNC_WIDTH			0x07, 0xc2 +#define TM6010_REQ07_RC3_HSTART1			0x07, 0xc3 +#define TM6010_REQ07_RC4_HSTART0			0x07, 0xc4 +#define TM6010_REQ07_RC5_HEND1				0x07, 0xc5 +#define TM6010_REQ07_RC6_HEND0				0x07, 0xc6 +#define TM6010_REQ07_RC7_VSTART1			0x07, 0xc7 +#define TM6010_REQ07_RC8_VSTART0			0x07, 0xc8 +#define TM6010_REQ07_RC9_VEND1				0x07, 0xc9 +#define TM6010_REQ07_RCA_VEND0				0x07, 0xca +#define TM6010_REQ07_RCB_DELAY				0x07, 0xcb +/* ONLY for TM6010 */ +#define TM6010_REQ07_RCC_ACTIVE_IF			0x07, 0xcc +#define TM6010_REQ07_RCC_ACTIVE_IF_VIDEO_ENABLE (1 << 5) +#define TM6010_REQ07_RCC_ACTIVE_IF_AUDIO_ENABLE (1 << 6) +#define TM6010_REQ07_RD0_USB_PERIPHERY_CONTROL		0x07, 0xd0 +#define TM6010_REQ07_RD1_ADDR_FOR_REQ1			0x07, 0xd1 +#define TM6010_REQ07_RD2_ADDR_FOR_REQ2			0x07, 0xd2 +#define TM6010_REQ07_RD3_ADDR_FOR_REQ3			0x07, 0xd3 +#define TM6010_REQ07_RD4_ADDR_FOR_REQ4			0x07, 0xd4 +#define TM6010_REQ07_RD5_POWERSAVE			0x07, 0xd5 +#define TM6010_REQ07_RD6_ENDP_REQ1_REQ2			0x07, 0xd6 +#define TM6010_REQ07_RD7_ENDP_REQ3_REQ4			0x07, 0xd7 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RD8_IR				0x07, 0xd8 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RD9_IR_BSIZE			0x07, 0xd9 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RDA_IR_WAKEUP_SEL			0x07, 0xda +/* ONLY for TM6010 */ +#define TM6010_REQ07_RDB_IR_WAKEUP_ADD			0x07, 0xdb +/* ONLY for TM6010 */ +#define TM6010_REQ07_RDC_IR_LEADER1			0x07, 0xdc +/* ONLY for TM6010 */ +#define TM6010_REQ07_RDD_IR_LEADER0			0x07, 0xdd +/* ONLY for TM6010 */ +#define TM6010_REQ07_RDE_IR_PULSE_CNT1			0x07, 0xde +/* ONLY for TM6010 */ +#define TM6010_REQ07_RDF_IR_PULSE_CNT0			0x07, 0xdf +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE0_DVIDEO_SOURCE			0x07, 0xe0 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE0_DVIDEO_SOURCE_IF		0x07, 0xe1 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE2_OUT_SEL2			0x07, 0xe2 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE3_OUT_SEL1			0x07, 0xe3 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE4_OUT_SEL0			0x07, 0xe4 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE5_REMOTE_WAKEUP			0x07, 0xe5 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE7_PUB_GPIO			0x07, 0xe7 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE8_TYPESEL_MOS_I2S		0x07, 0xe8 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RE9_TYPESEL_MOS_TS			0x07, 0xe9 +/* ONLY for TM6010 */ +#define TM6010_REQ07_REA_TYPESEL_MOS_CCIR		0x07, 0xea +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF0_BIST_CRC_RESULT0		0x07, 0xf0 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF1_BIST_CRC_RESULT1		0x07, 0xf1 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF2_BIST_CRC_RESULT2		0x07, 0xf2 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF3_BIST_CRC_RESULT3		0x07, 0xf3 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF4_BIST_ERR_VST2			0x07, 0xf4 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF5_BIST_ERR_VST1			0x07, 0xf5 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF6_BIST_ERR_VST0			0x07, 0xf6 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RF7_BIST				0x07, 0xf7 +/* ONLY for TM6010 */ +#define TM6010_REQ07_RFE_POWER_DOWN			0x07, 0xfe +#define TM6010_REQ07_RFF_SOFT_RESET			0x07, 0xff + +/* Define TM6000/TM6010 USB registers */ +#define TM6010_REQ05_R00_MAIN_CTRL		0x05, 0x00 +#define TM6010_REQ05_R01_DEVADDR		0x05, 0x01 +#define TM6010_REQ05_R02_TEST			0x05, 0x02 +#define TM6010_REQ05_R04_SOFN0			0x05, 0x04 +#define TM6010_REQ05_R05_SOFN1			0x05, 0x05 +#define TM6010_REQ05_R06_SOFTM0			0x05, 0x06 +#define TM6010_REQ05_R07_SOFTM1			0x05, 0x07 +#define TM6010_REQ05_R08_PHY_TEST		0x05, 0x08 +#define TM6010_REQ05_R09_VCTL			0x05, 0x09 +#define TM6010_REQ05_R0A_VSTA			0x05, 0x0a +#define TM6010_REQ05_R0B_CX_CFG			0x05, 0x0b +#define TM6010_REQ05_R0C_ENDP0_REG0		0x05, 0x0c +#define TM6010_REQ05_R10_GMASK			0x05, 0x10 +#define TM6010_REQ05_R11_IMASK0			0x05, 0x11 +#define TM6010_REQ05_R12_IMASK1			0x05, 0x12 +#define TM6010_REQ05_R13_IMASK2			0x05, 0x13 +#define TM6010_REQ05_R14_IMASK3			0x05, 0x14 +#define TM6010_REQ05_R15_IMASK4			0x05, 0x15 +#define TM6010_REQ05_R16_IMASK5			0x05, 0x16 +#define TM6010_REQ05_R17_IMASK6			0x05, 0x17 +#define TM6010_REQ05_R18_IMASK7			0x05, 0x18 +#define TM6010_REQ05_R19_ZEROP0			0x05, 0x19 +#define TM6010_REQ05_R1A_ZEROP1			0x05, 0x1a +#define TM6010_REQ05_R1C_FIFO_EMP0		0x05, 0x1c +#define TM6010_REQ05_R1D_FIFO_EMP1		0x05, 0x1d +#define TM6010_REQ05_R20_IRQ_GROUP		0x05, 0x20 +#define TM6010_REQ05_R21_IRQ_SOURCE0		0x05, 0x21 +#define TM6010_REQ05_R22_IRQ_SOURCE1		0x05, 0x22 +#define TM6010_REQ05_R23_IRQ_SOURCE2		0x05, 0x23 +#define TM6010_REQ05_R24_IRQ_SOURCE3		0x05, 0x24 +#define TM6010_REQ05_R25_IRQ_SOURCE4		0x05, 0x25 +#define TM6010_REQ05_R26_IRQ_SOURCE5		0x05, 0x26 +#define TM6010_REQ05_R27_IRQ_SOURCE6		0x05, 0x27 +#define TM6010_REQ05_R28_IRQ_SOURCE7		0x05, 0x28 +#define TM6010_REQ05_R29_SEQ_ERR0		0x05, 0x29 +#define TM6010_REQ05_R2A_SEQ_ERR1		0x05, 0x2a +#define TM6010_REQ05_R2B_SEQ_ABORT0		0x05, 0x2b +#define TM6010_REQ05_R2C_SEQ_ABORT1		0x05, 0x2c +#define TM6010_REQ05_R2D_TX_ZERO0		0x05, 0x2d +#define TM6010_REQ05_R2E_TX_ZERO1		0x05, 0x2e +#define TM6010_REQ05_R2F_IDLE_CNT		0x05, 0x2f +#define TM6010_REQ05_R30_FNO_P1			0x05, 0x30 +#define TM6010_REQ05_R31_FNO_P2			0x05, 0x31 +#define TM6010_REQ05_R32_FNO_P3			0x05, 0x32 +#define TM6010_REQ05_R33_FNO_P4			0x05, 0x33 +#define TM6010_REQ05_R34_FNO_P5			0x05, 0x34 +#define TM6010_REQ05_R35_FNO_P6			0x05, 0x35 +#define TM6010_REQ05_R36_FNO_P7			0x05, 0x36 +#define TM6010_REQ05_R37_FNO_P8			0x05, 0x37 +#define TM6010_REQ05_R38_FNO_P9			0x05, 0x38 +#define TM6010_REQ05_R30_FNO_P10		0x05, 0x39 +#define TM6010_REQ05_R30_FNO_P11		0x05, 0x3a +#define TM6010_REQ05_R30_FNO_P12		0x05, 0x3b +#define TM6010_REQ05_R30_FNO_P13		0x05, 0x3c +#define TM6010_REQ05_R30_FNO_P14		0x05, 0x3d +#define TM6010_REQ05_R30_FNO_P15		0x05, 0x3e +#define TM6010_REQ05_R40_IN_MAXPS_LOW1		0x05, 0x40 +#define TM6010_REQ05_R41_IN_MAXPS_HIGH1		0x05, 0x41 +#define TM6010_REQ05_R42_IN_MAXPS_LOW2		0x05, 0x42 +#define TM6010_REQ05_R43_IN_MAXPS_HIGH2		0x05, 0x43 +#define TM6010_REQ05_R44_IN_MAXPS_LOW3		0x05, 0x44 +#define TM6010_REQ05_R45_IN_MAXPS_HIGH3		0x05, 0x45 +#define TM6010_REQ05_R46_IN_MAXPS_LOW4		0x05, 0x46 +#define TM6010_REQ05_R47_IN_MAXPS_HIGH4		0x05, 0x47 +#define TM6010_REQ05_R48_IN_MAXPS_LOW5		0x05, 0x48 +#define TM6010_REQ05_R49_IN_MAXPS_HIGH5		0x05, 0x49 +#define TM6010_REQ05_R4A_IN_MAXPS_LOW6		0x05, 0x4a +#define TM6010_REQ05_R4B_IN_MAXPS_HIGH6		0x05, 0x4b +#define TM6010_REQ05_R4C_IN_MAXPS_LOW7		0x05, 0x4c +#define TM6010_REQ05_R4D_IN_MAXPS_HIGH7		0x05, 0x4d +#define TM6010_REQ05_R4E_IN_MAXPS_LOW8		0x05, 0x4e +#define TM6010_REQ05_R4F_IN_MAXPS_HIGH8		0x05, 0x4f +#define TM6010_REQ05_R50_IN_MAXPS_LOW9		0x05, 0x50 +#define TM6010_REQ05_R51_IN_MAXPS_HIGH9		0x05, 0x51 +#define TM6010_REQ05_R40_IN_MAXPS_LOW10		0x05, 0x52 +#define TM6010_REQ05_R41_IN_MAXPS_HIGH10	0x05, 0x53 +#define TM6010_REQ05_R40_IN_MAXPS_LOW11		0x05, 0x54 +#define TM6010_REQ05_R41_IN_MAXPS_HIGH11	0x05, 0x55 +#define TM6010_REQ05_R40_IN_MAXPS_LOW12		0x05, 0x56 +#define TM6010_REQ05_R41_IN_MAXPS_HIGH12	0x05, 0x57 +#define TM6010_REQ05_R40_IN_MAXPS_LOW13		0x05, 0x58 +#define TM6010_REQ05_R41_IN_MAXPS_HIGH13	0x05, 0x59 +#define TM6010_REQ05_R40_IN_MAXPS_LOW14		0x05, 0x5a +#define TM6010_REQ05_R41_IN_MAXPS_HIGH14	0x05, 0x5b +#define TM6010_REQ05_R40_IN_MAXPS_LOW15		0x05, 0x5c +#define TM6010_REQ05_R41_IN_MAXPS_HIGH15	0x05, 0x5d +#define TM6010_REQ05_R60_OUT_MAXPS_LOW1		0x05, 0x60 +#define TM6010_REQ05_R61_OUT_MAXPS_HIGH1	0x05, 0x61 +#define TM6010_REQ05_R62_OUT_MAXPS_LOW2		0x05, 0x62 +#define TM6010_REQ05_R63_OUT_MAXPS_HIGH2	0x05, 0x63 +#define TM6010_REQ05_R64_OUT_MAXPS_LOW3		0x05, 0x64 +#define TM6010_REQ05_R65_OUT_MAXPS_HIGH3	0x05, 0x65 +#define TM6010_REQ05_R66_OUT_MAXPS_LOW4		0x05, 0x66 +#define TM6010_REQ05_R67_OUT_MAXPS_HIGH4	0x05, 0x67 +#define TM6010_REQ05_R68_OUT_MAXPS_LOW5		0x05, 0x68 +#define TM6010_REQ05_R69_OUT_MAXPS_HIGH5	0x05, 0x69 +#define TM6010_REQ05_R6A_OUT_MAXPS_LOW6		0x05, 0x6a +#define TM6010_REQ05_R6B_OUT_MAXPS_HIGH6	0x05, 0x6b +#define TM6010_REQ05_R6C_OUT_MAXPS_LOW7		0x05, 0x6c +#define TM6010_REQ05_R6D_OUT_MAXPS_HIGH7	0x05, 0x6d +#define TM6010_REQ05_R6E_OUT_MAXPS_LOW8		0x05, 0x6e +#define TM6010_REQ05_R6F_OUT_MAXPS_HIGH8	0x05, 0x6f +#define TM6010_REQ05_R70_OUT_MAXPS_LOW9		0x05, 0x70 +#define TM6010_REQ05_R71_OUT_MAXPS_HIGH9	0x05, 0x71 +#define TM6010_REQ05_R60_OUT_MAXPS_LOW10	0x05, 0x72 +#define TM6010_REQ05_R61_OUT_MAXPS_HIGH10	0x05, 0x73 +#define TM6010_REQ05_R60_OUT_MAXPS_LOW11	0x05, 0x74 +#define TM6010_REQ05_R61_OUT_MAXPS_HIGH11	0x05, 0x75 +#define TM6010_REQ05_R60_OUT_MAXPS_LOW12	0x05, 0x76 +#define TM6010_REQ05_R61_OUT_MAXPS_HIGH12	0x05, 0x77 +#define TM6010_REQ05_R60_OUT_MAXPS_LOW13	0x05, 0x78 +#define TM6010_REQ05_R61_OUT_MAXPS_HIGH13	0x05, 0x79 +#define TM6010_REQ05_R60_OUT_MAXPS_LOW14	0x05, 0x7a +#define TM6010_REQ05_R61_OUT_MAXPS_HIGH14	0x05, 0x7b +#define TM6010_REQ05_R60_OUT_MAXPS_LOW15	0x05, 0x7c +#define TM6010_REQ05_R61_OUT_MAXPS_HIGH15	0x05, 0x7d +#define TM6010_REQ05_R80_FIFO0			0x05, 0x80 +#define TM6010_REQ05_R81_FIFO1			0x05, 0x81 +#define TM6010_REQ05_R82_FIFO2			0x05, 0x82 +#define TM6010_REQ05_R83_FIFO3			0x05, 0x83 +#define TM6010_REQ05_R84_FIFO4			0x05, 0x84 +#define TM6010_REQ05_R85_FIFO5			0x05, 0x85 +#define TM6010_REQ05_R86_FIFO6			0x05, 0x86 +#define TM6010_REQ05_R87_FIFO7			0x05, 0x87 +#define TM6010_REQ05_R88_FIFO8			0x05, 0x88 +#define TM6010_REQ05_R89_FIFO9			0x05, 0x89 +#define TM6010_REQ05_R81_FIFO10			0x05, 0x8a +#define TM6010_REQ05_R81_FIFO11			0x05, 0x8b +#define TM6010_REQ05_R81_FIFO12			0x05, 0x8c +#define TM6010_REQ05_R81_FIFO13			0x05, 0x8d +#define TM6010_REQ05_R81_FIFO14			0x05, 0x8e +#define TM6010_REQ05_R81_FIFO15			0x05, 0x8f +#define TM6010_REQ05_R90_CFG_FIFO0		0x05, 0x90 +#define TM6010_REQ05_R91_CFG_FIFO1		0x05, 0x91 +#define TM6010_REQ05_R92_CFG_FIFO2		0x05, 0x92 +#define TM6010_REQ05_R93_CFG_FIFO3		0x05, 0x93 +#define TM6010_REQ05_R94_CFG_FIFO4		0x05, 0x94 +#define TM6010_REQ05_R95_CFG_FIFO5		0x05, 0x95 +#define TM6010_REQ05_R96_CFG_FIFO6		0x05, 0x96 +#define TM6010_REQ05_R97_CFG_FIFO7		0x05, 0x97 +#define TM6010_REQ05_R98_CFG_FIFO8		0x05, 0x98 +#define TM6010_REQ05_R99_CFG_FIFO9		0x05, 0x99 +#define TM6010_REQ05_R91_CFG_FIFO10		0x05, 0x9a +#define TM6010_REQ05_R91_CFG_FIFO11		0x05, 0x9b +#define TM6010_REQ05_R91_CFG_FIFO12		0x05, 0x9c +#define TM6010_REQ05_R91_CFG_FIFO13		0x05, 0x9d +#define TM6010_REQ05_R91_CFG_FIFO14		0x05, 0x9e +#define TM6010_REQ05_R91_CFG_FIFO15		0x05, 0x9f +#define TM6010_REQ05_RA0_CTL_FIFO0		0x05, 0xa0 +#define TM6010_REQ05_RA1_CTL_FIFO1		0x05, 0xa1 +#define TM6010_REQ05_RA2_CTL_FIFO2		0x05, 0xa2 +#define TM6010_REQ05_RA3_CTL_FIFO3		0x05, 0xa3 +#define TM6010_REQ05_RA4_CTL_FIFO4		0x05, 0xa4 +#define TM6010_REQ05_RA5_CTL_FIFO5		0x05, 0xa5 +#define TM6010_REQ05_RA6_CTL_FIFO6		0x05, 0xa6 +#define TM6010_REQ05_RA7_CTL_FIFO7		0x05, 0xa7 +#define TM6010_REQ05_RA8_CTL_FIFO8		0x05, 0xa8 +#define TM6010_REQ05_RA9_CTL_FIFO9		0x05, 0xa9 +#define TM6010_REQ05_RA1_CTL_FIFO10		0x05, 0xaa +#define TM6010_REQ05_RA1_CTL_FIFO11		0x05, 0xab +#define TM6010_REQ05_RA1_CTL_FIFO12		0x05, 0xac +#define TM6010_REQ05_RA1_CTL_FIFO13		0x05, 0xad +#define TM6010_REQ05_RA1_CTL_FIFO14		0x05, 0xae +#define TM6010_REQ05_RA1_CTL_FIFO15		0x05, 0xaf +#define TM6010_REQ05_RB0_BC_LOW_FIFO0		0x05, 0xb0 +#define TM6010_REQ05_RB1_BC_LOW_FIFO1		0x05, 0xb1 +#define TM6010_REQ05_RB2_BC_LOW_FIFO2		0x05, 0xb2 +#define TM6010_REQ05_RB3_BC_LOW_FIFO3		0x05, 0xb3 +#define TM6010_REQ05_RB4_BC_LOW_FIFO4		0x05, 0xb4 +#define TM6010_REQ05_RB5_BC_LOW_FIFO5		0x05, 0xb5 +#define TM6010_REQ05_RB6_BC_LOW_FIFO6		0x05, 0xb6 +#define TM6010_REQ05_RB7_BC_LOW_FIFO7		0x05, 0xb7 +#define TM6010_REQ05_RB8_BC_LOW_FIFO8		0x05, 0xb8 +#define TM6010_REQ05_RB9_BC_LOW_FIFO9		0x05, 0xb9 +#define TM6010_REQ05_RB1_BC_LOW_FIFO10		0x05, 0xba +#define TM6010_REQ05_RB1_BC_LOW_FIFO11		0x05, 0xbb +#define TM6010_REQ05_RB1_BC_LOW_FIFO12		0x05, 0xbc +#define TM6010_REQ05_RB1_BC_LOW_FIFO13		0x05, 0xbd +#define TM6010_REQ05_RB1_BC_LOW_FIFO14		0x05, 0xbe +#define TM6010_REQ05_RB1_BC_LOW_FIFO15		0x05, 0xbf +#define TM6010_REQ05_RC0_DATA_FIFO0		0x05, 0xc0 +#define TM6010_REQ05_RC4_DATA_FIFO1		0x05, 0xc4 +#define TM6010_REQ05_RC8_DATA_FIFO2		0x05, 0xc8 +#define TM6010_REQ05_RCC_DATA_FIFO3		0x05, 0xcc +#define TM6010_REQ05_RD0_DATA_FIFO4		0x05, 0xd0 +#define TM6010_REQ05_RD4_DATA_FIFO5		0x05, 0xd4 +#define TM6010_REQ05_RD8_DATA_FIFO6		0x05, 0xd8 +#define TM6010_REQ05_RDC_DATA_FIFO7		0x05, 0xdc +#define TM6010_REQ05_RE0_DATA_FIFO8		0x05, 0xe0 +#define TM6010_REQ05_RE4_DATA_FIFO9		0x05, 0xe4 +#define TM6010_REQ05_RC4_DATA_FIFO10		0x05, 0xe8 +#define TM6010_REQ05_RC4_DATA_FIFO11		0x05, 0xec +#define TM6010_REQ05_RC4_DATA_FIFO12		0x05, 0xf0 +#define TM6010_REQ05_RC4_DATA_FIFO13		0x05, 0xf4 +#define TM6010_REQ05_RC4_DATA_FIFO14		0x05, 0xf8 +#define TM6010_REQ05_RC4_DATA_FIFO15		0x05, 0xfc + +/* Define TM6010 Audio decoder registers */ +/* This core available only in TM6010 */ +#define TM6010_REQ08_R00_A_VERSION		0x08, 0x00 +#define TM6010_REQ08_R01_A_INIT			0x08, 0x01 +#define TM6010_REQ08_R02_A_FIX_GAIN_CTRL	0x08, 0x02 +#define TM6010_REQ08_R03_A_AUTO_GAIN_CTRL	0x08, 0x03 +#define TM6010_REQ08_R04_A_SIF_AMP_CTRL		0x08, 0x04 +#define TM6010_REQ08_R05_A_STANDARD_MOD		0x08, 0x05 +#define TM6010_REQ08_R06_A_SOUND_MOD		0x08, 0x06 +#define TM6010_REQ08_R07_A_LEFT_VOL		0x08, 0x07 +#define TM6010_REQ08_R08_A_RIGHT_VOL		0x08, 0x08 +#define TM6010_REQ08_R09_A_MAIN_VOL		0x08, 0x09 +#define TM6010_REQ08_R0A_A_I2S_MOD		0x08, 0x0a +#define TM6010_REQ08_R0B_A_ASD_THRES1		0x08, 0x0b +#define TM6010_REQ08_R0C_A_ASD_THRES2		0x08, 0x0c +#define TM6010_REQ08_R0D_A_AMD_THRES		0x08, 0x0d +#define TM6010_REQ08_R0E_A_MONO_THRES1		0x08, 0x0e +#define TM6010_REQ08_R0F_A_MONO_THRES2		0x08, 0x0f +#define TM6010_REQ08_R10_A_MUTE_THRES1		0x08, 0x10 +#define TM6010_REQ08_R11_A_MUTE_THRES2		0x08, 0x11 +#define TM6010_REQ08_R12_A_AGC_U		0x08, 0x12 +#define TM6010_REQ08_R13_A_AGC_ERR_T		0x08, 0x13 +#define TM6010_REQ08_R14_A_AGC_GAIN_INIT	0x08, 0x14 +#define TM6010_REQ08_R15_A_AGC_STEP_THR		0x08, 0x15 +#define TM6010_REQ08_R16_A_AGC_GAIN_MAX		0x08, 0x16 +#define TM6010_REQ08_R17_A_AGC_GAIN_MIN		0x08, 0x17 +#define TM6010_REQ08_R18_A_TR_CTRL		0x08, 0x18 +#define TM6010_REQ08_R19_A_FH_2FH_GAIN		0x08, 0x19 +#define TM6010_REQ08_R1A_A_NICAM_SER_MAX	0x08, 0x1a +#define TM6010_REQ08_R1B_A_NICAM_SER_MIN	0x08, 0x1b +#define TM6010_REQ08_R1E_A_GAIN_DEEMPH_OUT	0x08, 0x1e +#define TM6010_REQ08_R1F_A_TEST_INTF_SEL	0x08, 0x1f +#define TM6010_REQ08_R20_A_TEST_PIN_SEL		0x08, 0x20 +#define TM6010_REQ08_R21_A_AGC_ERR		0x08, 0x21 +#define TM6010_REQ08_R22_A_AGC_GAIN		0x08, 0x22 +#define TM6010_REQ08_R23_A_NICAM_INFO		0x08, 0x23 +#define TM6010_REQ08_R24_A_SER			0x08, 0x24 +#define TM6010_REQ08_R25_A_C1_AMP		0x08, 0x25 +#define TM6010_REQ08_R26_A_C2_AMP		0x08, 0x26 +#define TM6010_REQ08_R27_A_NOISE_AMP		0x08, 0x27 +#define TM6010_REQ08_R28_A_AUDIO_MODE_RES	0x08, 0x28 + +/* Define TM6010 Video ADC registers */ +#define TM6010_REQ08_RE0_ADC_REF		0x08, 0xe0 +#define TM6010_REQ08_RE1_DAC_CLMP		0x08, 0xe1 +#define TM6010_REQ08_RE2_POWER_DOWN_CTRL1	0x08, 0xe2 +#define TM6010_REQ08_RE3_ADC_IN1_SEL		0x08, 0xe3 +#define TM6010_REQ08_RE4_ADC_IN2_SEL		0x08, 0xe4 +#define TM6010_REQ08_RE5_GAIN_PARAM		0x08, 0xe5 +#define TM6010_REQ08_RE6_POWER_DOWN_CTRL2	0x08, 0xe6 +#define TM6010_REQ08_RE7_REG_GAIN_Y		0x08, 0xe7 +#define TM6010_REQ08_RE8_REG_GAIN_C		0x08, 0xe8 +#define TM6010_REQ08_RE9_BIAS_CTRL		0x08, 0xe9 +#define TM6010_REQ08_REA_BUFF_DRV_CTRL		0x08, 0xea +#define TM6010_REQ08_REB_SIF_GAIN_CTRL		0x08, 0xeb +#define TM6010_REQ08_REC_REVERSE_YC_CTRL	0x08, 0xec +#define TM6010_REQ08_RED_GAIN_SEL		0x08, 0xed + +/* Define TM6010 Audio ADC registers */ +#define TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG	0x08, 0xf0 +#define TM6010_REQ08_RF1_AADC_POWER_DOWN	0x08, 0xf1 +#define TM6010_REQ08_RF2_LEFT_CHANNEL_VOL	0x08, 0xf2 +#define TM6010_REQ08_RF3_RIGHT_CHANNEL_VOL	0x08, 0xf3 diff --git a/drivers/media/usb/tm6000/tm6000-stds.c b/drivers/media/usb/tm6000/tm6000-stds.c new file mode 100644 index 00000000000..93a4b2434b6 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-stds.c @@ -0,0 +1,638 @@ +/* + *  tm6000-stds.c - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2007 Mauro Carvalho Chehab + * + *  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 version 2 + * + *  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/kernel.h> +#include "tm6000.h" +#include "tm6000-regs.h" + +static unsigned int tm6010_a_mode; +module_param(tm6010_a_mode, int, 0644); +MODULE_PARM_DESC(tm6010_a_mode, "set tm6010 sif audio mode"); + +struct tm6000_reg_settings { +	unsigned char req; +	unsigned char reg; +	unsigned char value; +}; + + +struct tm6000_std_settings { +	v4l2_std_id id; +	struct tm6000_reg_settings *common; +}; + +static struct tm6000_reg_settings composite_pal_m[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x04 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x83 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x0a }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe0 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x20 }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings composite_pal_nc[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x36 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x02 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x91 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x1f }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x0c }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2c }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings composite_pal[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x32 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x02 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x25 }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0xd5 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x63 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x50 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2c }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings composite_secam[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x38 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x02 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x24 }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x92 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xe8 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xed }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2c }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x2c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x18 }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0xff }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings composite_ntsc[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x00 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0f }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdd }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_std_settings composite_stds[] = { +	{ .id = V4L2_STD_PAL_M, .common = composite_pal_m, }, +	{ .id = V4L2_STD_PAL_Nc, .common = composite_pal_nc, }, +	{ .id = V4L2_STD_PAL, .common = composite_pal, }, +	{ .id = V4L2_STD_SECAM, .common = composite_secam, }, +	{ .id = V4L2_STD_NTSC, .common = composite_ntsc, }, +}; + +static struct tm6000_reg_settings svideo_pal_m[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x05 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x04 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x83 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x0a }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe0 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings svideo_pal_nc[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x37 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x04 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x91 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x1f }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x0c }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings svideo_pal[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x33 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x04 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x30 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x25 }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0xd5 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x63 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x50 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2a }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings svideo_secam[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x39 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x03 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x24 }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x92 }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xe8 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xed }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2a }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x2c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x18 }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0xff }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_reg_settings svideo_ntsc[] = { +	{ TM6010_REQ07_R3F_RESET, 0x01 }, +	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x01 }, +	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0f }, +	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f }, +	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x03 }, +	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x30 }, +	{ TM6010_REQ07_R17_HLOOP_MAXSTATE, 0x8b }, +	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e }, +	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b }, +	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 }, +	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 }, +	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c }, +	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc }, +	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc }, +	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd }, +	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 }, +	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 }, +	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 }, +	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c }, +	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c }, +	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 }, +	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f }, +	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdd }, +	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 }, +	{ TM6010_REQ07_R3F_RESET, 0x00 }, +	{ 0, 0, 0 } +}; + +static struct tm6000_std_settings svideo_stds[] = { +	{ .id = V4L2_STD_PAL_M, .common = svideo_pal_m, }, +	{ .id = V4L2_STD_PAL_Nc, .common = svideo_pal_nc, }, +	{ .id = V4L2_STD_PAL, .common = svideo_pal, }, +	{ .id = V4L2_STD_SECAM, .common = svideo_secam, }, +	{ .id = V4L2_STD_NTSC, .common = svideo_ntsc, }, +}; + +static int tm6000_set_audio_std(struct tm6000_core *dev) +{ +	uint8_t areg_02 = 0x04; /* GC1 Fixed gain 0dB */ +	uint8_t areg_05 = 0x01; /* Auto 4.5 = M Japan, Auto 6.5 = DK */ +	uint8_t areg_06 = 0x02; /* Auto de-emphasis, mannual channel mode */ + +	if (dev->radio) { +		tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x00); +		tm6000_set_reg(dev, TM6010_REQ08_R02_A_FIX_GAIN_CTRL, 0x04); +		tm6000_set_reg(dev, TM6010_REQ08_R03_A_AUTO_GAIN_CTRL, 0x00); +		tm6000_set_reg(dev, TM6010_REQ08_R04_A_SIF_AMP_CTRL, 0x80); +		tm6000_set_reg(dev, TM6010_REQ08_R05_A_STANDARD_MOD, 0x0c); +		/* set mono or stereo */ +		if (dev->amode == V4L2_TUNER_MODE_MONO) +			tm6000_set_reg(dev, TM6010_REQ08_R06_A_SOUND_MOD, 0x00); +		else if (dev->amode == V4L2_TUNER_MODE_STEREO) +			tm6000_set_reg(dev, TM6010_REQ08_R06_A_SOUND_MOD, 0x02); +		tm6000_set_reg(dev, TM6010_REQ08_R09_A_MAIN_VOL, 0x18); +		tm6000_set_reg(dev, TM6010_REQ08_R0C_A_ASD_THRES2, 0x0a); +		tm6000_set_reg(dev, TM6010_REQ08_R0D_A_AMD_THRES, 0x40); +		tm6000_set_reg(dev, TM6010_REQ08_RF1_AADC_POWER_DOWN, 0xfe); +		tm6000_set_reg(dev, TM6010_REQ08_R1E_A_GAIN_DEEMPH_OUT, 0x13); +		tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x80); +		tm6000_set_reg(dev, TM6010_REQ07_RFE_POWER_DOWN, 0xff); +		return 0; +	} + +	/* +	 * STD/MN shouldn't be affected by tm6010_a_mode, as there's just one +	 * audio standard for each V4L2_STD type. +	 */ +	if ((dev->norm & V4L2_STD_NTSC) == V4L2_STD_NTSC_M_KR) { +		areg_05 |= 0x04; +	} else if ((dev->norm & V4L2_STD_NTSC) == V4L2_STD_NTSC_M_JP) { +		areg_05 |= 0x43; +	} else if (dev->norm & V4L2_STD_MN) { +		areg_05 |= 0x22; +	} else switch (tm6010_a_mode) { +	/* auto */ +	case 0: +		if ((dev->norm & V4L2_STD_SECAM) == V4L2_STD_SECAM_L) +			areg_05 |= 0x00; +		else	/* Other PAL/SECAM standards */ +			areg_05 |= 0x10; +		break; +	/* A2 */ +	case 1: +		if (dev->norm & V4L2_STD_DK) +			areg_05 = 0x09; +		else +			areg_05 = 0x05; +		break; +	/* NICAM */ +	case 2: +		if (dev->norm & V4L2_STD_DK) { +			areg_05 = 0x06; +		} else if (dev->norm & V4L2_STD_PAL_I) { +			areg_05 = 0x08; +		} else if (dev->norm & V4L2_STD_SECAM_L) { +			areg_05 = 0x0a; +			areg_02 = 0x02; +		} else { +			areg_05 = 0x07; +		} +		break; +	/* other */ +	case 3: +		if (dev->norm & V4L2_STD_DK) { +			areg_05 = 0x0b; +		} else { +			areg_05 = 0x02; +		} +		break; +	} + +	tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x00); +	tm6000_set_reg(dev, TM6010_REQ08_R02_A_FIX_GAIN_CTRL, areg_02); +	tm6000_set_reg(dev, TM6010_REQ08_R03_A_AUTO_GAIN_CTRL, 0x00); +	tm6000_set_reg(dev, TM6010_REQ08_R04_A_SIF_AMP_CTRL, 0xa0); +	tm6000_set_reg(dev, TM6010_REQ08_R05_A_STANDARD_MOD, areg_05); +	tm6000_set_reg(dev, TM6010_REQ08_R06_A_SOUND_MOD, areg_06); +	tm6000_set_reg(dev, TM6010_REQ08_R07_A_LEFT_VOL, 0x00); +	tm6000_set_reg(dev, TM6010_REQ08_R08_A_RIGHT_VOL, 0x00); +	tm6000_set_reg(dev, TM6010_REQ08_R09_A_MAIN_VOL, 0x08); +	tm6000_set_reg(dev, TM6010_REQ08_R0A_A_I2S_MOD, 0x91); +	tm6000_set_reg(dev, TM6010_REQ08_R0B_A_ASD_THRES1, 0x20); +	tm6000_set_reg(dev, TM6010_REQ08_R0C_A_ASD_THRES2, 0x12); +	tm6000_set_reg(dev, TM6010_REQ08_R0D_A_AMD_THRES, 0x20); +	tm6000_set_reg(dev, TM6010_REQ08_R0E_A_MONO_THRES1, 0xf0); +	tm6000_set_reg(dev, TM6010_REQ08_R0F_A_MONO_THRES2, 0x80); +	tm6000_set_reg(dev, TM6010_REQ08_R10_A_MUTE_THRES1, 0xc0); +	tm6000_set_reg(dev, TM6010_REQ08_R11_A_MUTE_THRES2, 0x80); +	tm6000_set_reg(dev, TM6010_REQ08_R12_A_AGC_U, 0x12); +	tm6000_set_reg(dev, TM6010_REQ08_R13_A_AGC_ERR_T, 0xfe); +	tm6000_set_reg(dev, TM6010_REQ08_R14_A_AGC_GAIN_INIT, 0x20); +	tm6000_set_reg(dev, TM6010_REQ08_R15_A_AGC_STEP_THR, 0x14); +	tm6000_set_reg(dev, TM6010_REQ08_R16_A_AGC_GAIN_MAX, 0xfe); +	tm6000_set_reg(dev, TM6010_REQ08_R17_A_AGC_GAIN_MIN, 0x01); +	tm6000_set_reg(dev, TM6010_REQ08_R18_A_TR_CTRL, 0xa0); +	tm6000_set_reg(dev, TM6010_REQ08_R19_A_FH_2FH_GAIN, 0x32); +	tm6000_set_reg(dev, TM6010_REQ08_R1A_A_NICAM_SER_MAX, 0x64); +	tm6000_set_reg(dev, TM6010_REQ08_R1B_A_NICAM_SER_MIN, 0x20); +	tm6000_set_reg(dev, REQ_08_SET_GET_AVREG_BIT, 0x1c, 0x00); +	tm6000_set_reg(dev, REQ_08_SET_GET_AVREG_BIT, 0x1d, 0x00); +	tm6000_set_reg(dev, TM6010_REQ08_R1E_A_GAIN_DEEMPH_OUT, 0x13); +	tm6000_set_reg(dev, TM6010_REQ08_R1F_A_TEST_INTF_SEL, 0x00); +	tm6000_set_reg(dev, TM6010_REQ08_R20_A_TEST_PIN_SEL, 0x00); +	tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x80); + +	return 0; +} + +void tm6000_get_std_res(struct tm6000_core *dev) +{ +	/* Currently, those are the only supported resoltions */ +	if (dev->norm & V4L2_STD_525_60) +		dev->height = 480; +	else +		dev->height = 576; + +	dev->width = 720; +} + +static int tm6000_load_std(struct tm6000_core *dev, struct tm6000_reg_settings *set) +{ +	int i, rc; + +	/* Load board's initialization table */ +	for (i = 0; set[i].req; i++) { +		rc = tm6000_set_reg(dev, set[i].req, set[i].reg, set[i].value); +		if (rc < 0) { +			printk(KERN_ERR "Error %i while setting " +			       "req %d, reg %d to value %d\n", +			       rc, set[i].req, set[i].reg, set[i].value); +			return rc; +		} +	} + +	return 0; +} + +int tm6000_set_standard(struct tm6000_core *dev) +{ +	struct tm6000_input *input; +	int i, rc = 0; +	u8 reg_07_fe = 0x8a; +	u8 reg_08_f1 = 0xfc; +	u8 reg_08_e2 = 0xf0; +	u8 reg_08_e6 = 0x0f; + +	tm6000_get_std_res(dev); + +	if (!dev->radio) +		input = &dev->vinput[dev->input]; +	else +		input = &dev->rinput; + +	if (dev->dev_type == TM6010) { +		switch (input->vmux) { +		case TM6000_VMUX_VIDEO_A: +			tm6000_set_reg(dev, TM6010_REQ08_RE3_ADC_IN1_SEL, 0xf4); +			tm6000_set_reg(dev, TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf1); +			tm6000_set_reg(dev, TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xe0); +			tm6000_set_reg(dev, TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2); +			tm6000_set_reg(dev, TM6010_REQ08_RED_GAIN_SEL, 0xe8); +			reg_07_fe |= 0x01; +			break; +		case TM6000_VMUX_VIDEO_B: +			tm6000_set_reg(dev, TM6010_REQ08_RE3_ADC_IN1_SEL, 0xf8); +			tm6000_set_reg(dev, TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf1); +			tm6000_set_reg(dev, TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xe0); +			tm6000_set_reg(dev, TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2); +			tm6000_set_reg(dev, TM6010_REQ08_RED_GAIN_SEL, 0xe8); +			reg_07_fe |= 0x01; +			break; +		case TM6000_VMUX_VIDEO_AB: +			tm6000_set_reg(dev, TM6010_REQ08_RE3_ADC_IN1_SEL, 0xfc); +			tm6000_set_reg(dev, TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf8); +			reg_08_e6 = 0x00; +			tm6000_set_reg(dev, TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf2); +			tm6000_set_reg(dev, TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xf0); +			tm6000_set_reg(dev, TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2); +			tm6000_set_reg(dev, TM6010_REQ08_RED_GAIN_SEL, 0xe0); +			break; +		default: +			break; +		} +		switch (input->amux) { +		case TM6000_AMUX_ADC1: +			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, +				0x00, 0x0f); +			/* Mux overflow workaround */ +			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL, +				0x10, 0xf0); +			break; +		case TM6000_AMUX_ADC2: +			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, +				0x08, 0x0f); +			/* Mux overflow workaround */ +			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL, +				0x10, 0xf0); +			break; +		case TM6000_AMUX_SIF1: +			reg_08_e2 |= 0x02; +			reg_08_e6 = 0x08; +			reg_07_fe |= 0x40; +			reg_08_f1 |= 0x02; +			tm6000_set_reg(dev, TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf3); +			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, +				0x02, 0x0f); +			/* Mux overflow workaround */ +			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL, +				0x30, 0xf0); +			break; +		case TM6000_AMUX_SIF2: +			reg_08_e2 |= 0x02; +			reg_08_e6 = 0x08; +			reg_07_fe |= 0x40; +			reg_08_f1 |= 0x02; +			tm6000_set_reg(dev, TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf7); +			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, +				0x02, 0x0f); +			/* Mux overflow workaround */ +			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL, +				0x30, 0xf0); +			break; +		default: +			break; +		} +		tm6000_set_reg(dev, TM6010_REQ08_RE2_POWER_DOWN_CTRL1, reg_08_e2); +		tm6000_set_reg(dev, TM6010_REQ08_RE6_POWER_DOWN_CTRL2, reg_08_e6); +		tm6000_set_reg(dev, TM6010_REQ08_RF1_AADC_POWER_DOWN, reg_08_f1); +		tm6000_set_reg(dev, TM6010_REQ07_RFE_POWER_DOWN, reg_07_fe); +	} else { +		switch (input->vmux) { +		case TM6000_VMUX_VIDEO_A: +			tm6000_set_reg(dev, TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x10); +			tm6000_set_reg(dev, TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x00); +			tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x0f); +			tm6000_set_reg(dev, +			    REQ_03_SET_GET_MCU_PIN, input->v_gpio, 0); +			break; +		case TM6000_VMUX_VIDEO_B: +			tm6000_set_reg(dev, TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x00); +			tm6000_set_reg(dev, TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x00); +			tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x0f); +			tm6000_set_reg(dev, +			    REQ_03_SET_GET_MCU_PIN, input->v_gpio, 0); +			break; +		case TM6000_VMUX_VIDEO_AB: +			tm6000_set_reg(dev, TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x10); +			tm6000_set_reg(dev, TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x10); +			tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x00); +			tm6000_set_reg(dev, +			    REQ_03_SET_GET_MCU_PIN, input->v_gpio, 1); +			break; +		default: +			break; +		} +		switch (input->amux) { +		case TM6000_AMUX_ADC1: +			tm6000_set_reg_mask(dev, +				TM6000_REQ07_REB_VADC_AADC_MODE, 0x00, 0x0f); +			break; +		case TM6000_AMUX_ADC2: +			tm6000_set_reg_mask(dev, +				TM6000_REQ07_REB_VADC_AADC_MODE, 0x04, 0x0f); +			break; +		default: +			break; +		} +	} +	if (input->type == TM6000_INPUT_SVIDEO) { +		for (i = 0; i < ARRAY_SIZE(svideo_stds); i++) { +			if (dev->norm & svideo_stds[i].id) { +				rc = tm6000_load_std(dev, svideo_stds[i].common); +				goto ret; +			} +		} +		return -EINVAL; +	} else { +		for (i = 0; i < ARRAY_SIZE(composite_stds); i++) { +			if (dev->norm & composite_stds[i].id) { +				rc = tm6000_load_std(dev, composite_stds[i].common); +				goto ret; +			} +		} +		return -EINVAL; +	} + +ret: +	if (rc < 0) +		return rc; + +	if ((dev->dev_type == TM6010) && +	    ((input->amux == TM6000_AMUX_SIF1) || +	    (input->amux == TM6000_AMUX_SIF2))) +		tm6000_set_audio_std(dev); + +	msleep(40); + +	return 0; +} diff --git a/drivers/media/usb/tm6000/tm6000-usb-isoc.h b/drivers/media/usb/tm6000/tm6000-usb-isoc.h new file mode 100644 index 00000000000..99d15a55aa0 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-usb-isoc.h @@ -0,0 +1,50 @@ +/* + *  tm6000-buf.c - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.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 version 2 + * + *  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/videodev2.h> + +#define TM6000_URB_MSG_LEN 180 + +struct usb_isoc_ctl { +		/* max packet size of isoc transaction */ +	int				max_pkt_size; + +		/* number of allocated urbs */ +	int				num_bufs; + +		/* urb for isoc transfers */ +	struct urb			**urb; + +		/* transfer buffers for isoc transfer */ +	char				**transfer_buffer; + +		/* Last buffer command and region */ +	u8				cmd; +	int				pos, size, pktsize; + +		/* Last field: ODD or EVEN? */ +	int				vfield, field; + +		/* Stores incomplete commands */ +	u32				tmp_buf; +	int				tmp_buf_len; + +		/* Stores already requested buffers */ +	struct tm6000_buffer		*buf; +}; diff --git a/drivers/media/usb/tm6000/tm6000-video.c b/drivers/media/usb/tm6000/tm6000-video.c new file mode 100644 index 00000000000..e6b3d5d83d4 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000-video.c @@ -0,0 +1,1760 @@ +/* + *   tm6000-video.c - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + * + *  Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + *	- Fixed module load/unload + * + *  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 version 2 + * + *  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/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/random.h> +#include <linux/usb.h> +#include <linux/videodev2.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/tuner.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/highmem.h> +#include <linux/freezer.h> + +#include "tm6000-regs.h" +#include "tm6000.h" + +#define BUFFER_TIMEOUT     msecs_to_jiffies(2000)  /* 2 seconds */ + +/* Limits minimum and default number of buffers */ +#define TM6000_MIN_BUF 4 +#define TM6000_DEF_BUF 8 + +#define TM6000_NUM_URB_BUF 8 + +#define TM6000_MAX_ISO_PACKETS	46	/* Max number of ISO packets */ + +/* Declare static vars that will be used as parameters */ +static unsigned int vid_limit = 16;	/* Video memory limit, in Mb */ +static int video_nr = -1;		/* /dev/videoN, -1 for autodetect */ +static int radio_nr = -1;		/* /dev/radioN, -1 for autodetect */ +static bool keep_urb;			/* keep urb buffers allocated */ + +/* Debug level */ +int tm6000_debug; +EXPORT_SYMBOL_GPL(tm6000_debug); + +static struct tm6000_fmt format[] = { +	{ +		.name     = "4:2:2, packed, YVY2", +		.fourcc   = V4L2_PIX_FMT_YUYV, +		.depth    = 16, +	}, { +		.name     = "4:2:2, packed, UYVY", +		.fourcc   = V4L2_PIX_FMT_UYVY, +		.depth    = 16, +	}, { +		.name     = "A/V + VBI mux packet", +		.fourcc   = V4L2_PIX_FMT_TM6000, +		.depth    = 16, +	} +}; + +/* ------------------------------------------------------------------ + *	DMA and thread functions + * ------------------------------------------------------------------ + */ + +#define norm_maxw(a) 720 +#define norm_maxh(a) 576 + +#define norm_minw(a) norm_maxw(a) +#define norm_minh(a) norm_maxh(a) + +/* + * video-buf generic routine to get the next available buffer + */ +static inline void get_next_buf(struct tm6000_dmaqueue *dma_q, +			       struct tm6000_buffer   **buf) +{ +	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); + +	if (list_empty(&dma_q->active)) { +		dprintk(dev, V4L2_DEBUG_QUEUE, "No active queue to serve\n"); +		*buf = NULL; +		return; +	} + +	*buf = list_entry(dma_q->active.next, +			struct tm6000_buffer, vb.queue); +} + +/* + * Announces that a buffer were filled and request the next + */ +static inline void buffer_filled(struct tm6000_core *dev, +				 struct tm6000_dmaqueue *dma_q, +				 struct tm6000_buffer *buf) +{ +	/* Advice that buffer was filled */ +	dprintk(dev, V4L2_DEBUG_ISOC, "[%p/%d] wakeup\n", buf, buf->vb.i); +	buf->vb.state = VIDEOBUF_DONE; +	buf->vb.field_count++; +	v4l2_get_timestamp(&buf->vb.ts); + +	list_del(&buf->vb.queue); +	wake_up(&buf->vb.done); +} + +/* + * Identify the tm5600/6000 buffer header type and properly handles + */ +static int copy_streams(u8 *data, unsigned long len, +			struct urb *urb) +{ +	struct tm6000_dmaqueue  *dma_q = urb->context; +	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); +	u8 *ptr = data, *endp = data+len; +	unsigned long header = 0; +	int rc = 0; +	unsigned int cmd, cpysize, pktsize, size, field, block, line, pos = 0; +	struct tm6000_buffer *vbuf = NULL; +	char *voutp = NULL; +	unsigned int linewidth; + +	if (!dev->radio) { +		/* get video buffer */ +		get_next_buf(dma_q, &vbuf); + +		if (!vbuf) +			return rc; +		voutp = videobuf_to_vmalloc(&vbuf->vb); + +		if (!voutp) +			return 0; +	} + +	for (ptr = data; ptr < endp;) { +		if (!dev->isoc_ctl.cmd) { +			/* Header */ +			if (dev->isoc_ctl.tmp_buf_len > 0) { +				/* from last urb or packet */ +				header = dev->isoc_ctl.tmp_buf; +				if (4 - dev->isoc_ctl.tmp_buf_len > 0) { +					memcpy((u8 *)&header + +						dev->isoc_ctl.tmp_buf_len, +						ptr, +						4 - dev->isoc_ctl.tmp_buf_len); +					ptr += 4 - dev->isoc_ctl.tmp_buf_len; +				} +				dev->isoc_ctl.tmp_buf_len = 0; +			} else { +				if (ptr + 3 >= endp) { +					/* have incomplete header */ +					dev->isoc_ctl.tmp_buf_len = endp - ptr; +					memcpy(&dev->isoc_ctl.tmp_buf, ptr, +						dev->isoc_ctl.tmp_buf_len); +					return rc; +				} +				/* Seek for sync */ +				for (; ptr < endp - 3; ptr++) { +					if (*(ptr + 3) == 0x47) +						break; +				} +				/* Get message header */ +				header = *(unsigned long *)ptr; +				ptr += 4; +			} + +			/* split the header fields */ +			size = ((header & 0x7e) << 1); +			if (size > 0) +				size -= 4; +			block = (header >> 7) & 0xf; +			field = (header >> 11) & 0x1; +			line  = (header >> 12) & 0x1ff; +			cmd   = (header >> 21) & 0x7; +			/* Validates haeder fields */ +			if (size > TM6000_URB_MSG_LEN) +				size = TM6000_URB_MSG_LEN; +			pktsize = TM6000_URB_MSG_LEN; +			/* +			 * calculate position in buffer and change the buffer +			 */ +			switch (cmd) { +			case TM6000_URB_MSG_VIDEO: +				if (!dev->radio) { +					if ((dev->isoc_ctl.vfield != field) && +						(field == 1)) { +						/* +						 * Announces that a new buffer +						 * were filled +						 */ +						buffer_filled(dev, dma_q, vbuf); +						dprintk(dev, V4L2_DEBUG_ISOC, +							"new buffer filled\n"); +						get_next_buf(dma_q, &vbuf); +						if (!vbuf) +							return rc; +						voutp = videobuf_to_vmalloc(&vbuf->vb); +						if (!voutp) +							return rc; +						memset(voutp, 0, vbuf->vb.size); +					} +					linewidth = vbuf->vb.width << 1; +					pos = ((line << 1) - field - 1) * +					linewidth + block * TM6000_URB_MSG_LEN; +					/* Don't allow to write out of the buffer */ +					if (pos + size > vbuf->vb.size) +						cmd = TM6000_URB_MSG_ERR; +					dev->isoc_ctl.vfield = field; +				} +				break; +			case TM6000_URB_MSG_VBI: +				break; +			case TM6000_URB_MSG_AUDIO: +			case TM6000_URB_MSG_PTS: +				size = pktsize; /* Size is always 180 bytes */ +				break; +			} +		} else { +			/* Continue the last copy */ +			cmd = dev->isoc_ctl.cmd; +			size = dev->isoc_ctl.size; +			pos = dev->isoc_ctl.pos; +			pktsize = dev->isoc_ctl.pktsize; +			field = dev->isoc_ctl.field; +		} +		cpysize = (endp - ptr > size) ? size : endp - ptr; +		if (cpysize) { +			/* copy data in different buffers */ +			switch (cmd) { +			case TM6000_URB_MSG_VIDEO: +				/* Fills video buffer */ +				if (vbuf) +					memcpy(&voutp[pos], ptr, cpysize); +				break; +			case TM6000_URB_MSG_AUDIO: { +				int i; +				for (i = 0; i < cpysize; i += 2) +					swab16s((u16 *)(ptr + i)); + +				tm6000_call_fillbuf(dev, TM6000_AUDIO, ptr, cpysize); +				break; +			} +			case TM6000_URB_MSG_VBI: +				/* Need some code to copy vbi buffer */ +				break; +			case TM6000_URB_MSG_PTS: { +				/* Need some code to copy pts */ +				u32 pts; +				pts = *(u32 *)ptr; +				dprintk(dev, V4L2_DEBUG_ISOC, "field %d, PTS %x", +					field, pts); +				break; +			} +			} +		} +		if (ptr + pktsize > endp) { +			/* +			 * End of URB packet, but cmd processing is not +			 * complete. Preserve the state for a next packet +			 */ +			dev->isoc_ctl.pos = pos + cpysize; +			dev->isoc_ctl.size = size - cpysize; +			dev->isoc_ctl.cmd = cmd; +			dev->isoc_ctl.field = field; +			dev->isoc_ctl.pktsize = pktsize - (endp - ptr); +			ptr += endp - ptr; +		} else { +			dev->isoc_ctl.cmd = 0; +			ptr += pktsize; +		} +	} +	return 0; +} + +/* + * Identify the tm5600/6000 buffer header type and properly handles + */ +static int copy_multiplexed(u8 *ptr, unsigned long len, +			struct urb *urb) +{ +	struct tm6000_dmaqueue  *dma_q = urb->context; +	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); +	unsigned int pos = dev->isoc_ctl.pos, cpysize; +	int rc = 1; +	struct tm6000_buffer *buf; +	char *outp = NULL; + +	get_next_buf(dma_q, &buf); +	if (buf) +		outp = videobuf_to_vmalloc(&buf->vb); + +	if (!outp) +		return 0; + +	while (len > 0) { +		cpysize = min(len, buf->vb.size-pos); +		memcpy(&outp[pos], ptr, cpysize); +		pos += cpysize; +		ptr += cpysize; +		len -= cpysize; +		if (pos >= buf->vb.size) { +			pos = 0; +			/* Announces that a new buffer were filled */ +			buffer_filled(dev, dma_q, buf); +			dprintk(dev, V4L2_DEBUG_ISOC, "new buffer filled\n"); +			get_next_buf(dma_q, &buf); +			if (!buf) +				break; +			outp = videobuf_to_vmalloc(&(buf->vb)); +			if (!outp) +				return rc; +			pos = 0; +		} +	} + +	dev->isoc_ctl.pos = pos; +	return rc; +} + +static inline void print_err_status(struct tm6000_core *dev, +				     int packet, int status) +{ +	char *errmsg = "Unknown"; + +	switch (status) { +	case -ENOENT: +		errmsg = "unlinked synchronuously"; +		break; +	case -ECONNRESET: +		errmsg = "unlinked asynchronuously"; +		break; +	case -ENOSR: +		errmsg = "Buffer error (overrun)"; +		break; +	case -EPIPE: +		errmsg = "Stalled (device not responding)"; +		break; +	case -EOVERFLOW: +		errmsg = "Babble (bad cable?)"; +		break; +	case -EPROTO: +		errmsg = "Bit-stuff error (bad cable?)"; +		break; +	case -EILSEQ: +		errmsg = "CRC/Timeout (could be anything)"; +		break; +	case -ETIME: +		errmsg = "Device does not respond"; +		break; +	} +	if (packet < 0) { +		dprintk(dev, V4L2_DEBUG_QUEUE, "URB status %d [%s].\n", +			status, errmsg); +	} else { +		dprintk(dev, V4L2_DEBUG_QUEUE, "URB packet %d, status %d [%s].\n", +			packet, status, errmsg); +	} +} + + +/* + * Controls the isoc copy of each urb packet + */ +static inline int tm6000_isoc_copy(struct urb *urb) +{ +	struct tm6000_dmaqueue  *dma_q = urb->context; +	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); +	int i, len = 0, rc = 1, status; +	char *p; + +	if (urb->status < 0) { +		print_err_status(dev, -1, urb->status); +		return 0; +	} + +	for (i = 0; i < urb->number_of_packets; i++) { +		status = urb->iso_frame_desc[i].status; + +		if (status < 0) { +			print_err_status(dev, i, status); +			continue; +		} + +		len = urb->iso_frame_desc[i].actual_length; + +		if (len > 0) { +			p = urb->transfer_buffer + urb->iso_frame_desc[i].offset; +			if (!urb->iso_frame_desc[i].status) { +				if ((dev->fourcc) == V4L2_PIX_FMT_TM6000) { +					rc = copy_multiplexed(p, len, urb); +					if (rc <= 0) +						return rc; +				} else { +					copy_streams(p, len, urb); +				} +			} +		} +	} +	return rc; +} + +/* ------------------------------------------------------------------ + *	URB control + * ------------------------------------------------------------------ + */ + +/* + * IRQ callback, called by URB callback + */ +static void tm6000_irq_callback(struct urb *urb) +{ +	struct tm6000_dmaqueue  *dma_q = urb->context; +	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); +	int i; + +	switch (urb->status) { +	case 0: +	case -ETIMEDOUT: +		break; + +	case -ECONNRESET: +	case -ENOENT: +	case -ESHUTDOWN: +		return; + +	default: +		tm6000_err("urb completion error %d.\n", urb->status); +		break; +	} + +	spin_lock(&dev->slock); +	tm6000_isoc_copy(urb); +	spin_unlock(&dev->slock); + +	/* Reset urb buffers */ +	for (i = 0; i < urb->number_of_packets; i++) { +		urb->iso_frame_desc[i].status = 0; +		urb->iso_frame_desc[i].actual_length = 0; +	} + +	urb->status = usb_submit_urb(urb, GFP_ATOMIC); +	if (urb->status) +		tm6000_err("urb resubmit failed (error=%i)\n", +			urb->status); +} + +/* + * Allocate URB buffers + */ +static int tm6000_alloc_urb_buffers(struct tm6000_core *dev) +{ +	int num_bufs = TM6000_NUM_URB_BUF; +	int i; + +	if (dev->urb_buffer != NULL) +		return 0; + +	dev->urb_buffer = kmalloc(sizeof(void *)*num_bufs, GFP_KERNEL); +	if (!dev->urb_buffer) { +		tm6000_err("cannot allocate memory for urb buffers\n"); +		return -ENOMEM; +	} + +	dev->urb_dma = kmalloc(sizeof(dma_addr_t *)*num_bufs, GFP_KERNEL); +	if (!dev->urb_dma) { +		tm6000_err("cannot allocate memory for urb dma pointers\n"); +		return -ENOMEM; +	} + +	for (i = 0; i < num_bufs; i++) { +		dev->urb_buffer[i] = usb_alloc_coherent( +					dev->udev, dev->urb_size, +					GFP_KERNEL, &dev->urb_dma[i]); +		if (!dev->urb_buffer[i]) { +			tm6000_err("unable to allocate %i bytes for transfer buffer %i\n", +				    dev->urb_size, i); +			return -ENOMEM; +		} +		memset(dev->urb_buffer[i], 0, dev->urb_size); +	} + +	return 0; +} + +/* + * Free URB buffers + */ +static int tm6000_free_urb_buffers(struct tm6000_core *dev) +{ +	int i; + +	if (dev->urb_buffer == NULL) +		return 0; + +	for (i = 0; i < TM6000_NUM_URB_BUF; i++) { +		if (dev->urb_buffer[i]) { +			usb_free_coherent(dev->udev, +					dev->urb_size, +					dev->urb_buffer[i], +					dev->urb_dma[i]); +			dev->urb_buffer[i] = NULL; +		} +	} +	kfree(dev->urb_buffer); +	kfree(dev->urb_dma); +	dev->urb_buffer = NULL; +	dev->urb_dma = NULL; + +	return 0; +} + +/* + * Stop and Deallocate URBs + */ +static void tm6000_uninit_isoc(struct tm6000_core *dev) +{ +	struct urb *urb; +	int i; + +	dev->isoc_ctl.buf = NULL; +	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { +		urb = dev->isoc_ctl.urb[i]; +		if (urb) { +			usb_kill_urb(urb); +			usb_unlink_urb(urb); +			usb_free_urb(urb); +			dev->isoc_ctl.urb[i] = NULL; +		} +		dev->isoc_ctl.transfer_buffer[i] = NULL; +	} + +	if (!keep_urb) +		tm6000_free_urb_buffers(dev); + +	kfree(dev->isoc_ctl.urb); +	kfree(dev->isoc_ctl.transfer_buffer); + +	dev->isoc_ctl.urb = NULL; +	dev->isoc_ctl.transfer_buffer = NULL; +	dev->isoc_ctl.num_bufs = 0; +} + +/* + * Assign URBs and start IRQ + */ +static int tm6000_prepare_isoc(struct tm6000_core *dev) +{ +	struct tm6000_dmaqueue *dma_q = &dev->vidq; +	int i, j, sb_size, pipe, size, max_packets; +	int num_bufs = TM6000_NUM_URB_BUF; +	struct urb *urb; + +	/* De-allocates all pending stuff */ +	tm6000_uninit_isoc(dev); +	/* Stop interrupt USB pipe */ +	tm6000_ir_int_stop(dev); + +	usb_set_interface(dev->udev, +			  dev->isoc_in.bInterfaceNumber, +			  dev->isoc_in.bAlternateSetting); + +	/* Start interrupt USB pipe */ +	tm6000_ir_int_start(dev); + +	pipe = usb_rcvisocpipe(dev->udev, +			       dev->isoc_in.endp->desc.bEndpointAddress & +			       USB_ENDPOINT_NUMBER_MASK); + +	size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); + +	if (size > dev->isoc_in.maxsize) +		size = dev->isoc_in.maxsize; + +	dev->isoc_ctl.max_pkt_size = size; + +	max_packets = TM6000_MAX_ISO_PACKETS; +	sb_size = max_packets * size; +	dev->urb_size = sb_size; + +	dev->isoc_ctl.num_bufs = num_bufs; + +	dev->isoc_ctl.urb = kmalloc(sizeof(void *)*num_bufs, GFP_KERNEL); +	if (!dev->isoc_ctl.urb) { +		tm6000_err("cannot alloc memory for usb buffers\n"); +		return -ENOMEM; +	} + +	dev->isoc_ctl.transfer_buffer = kmalloc(sizeof(void *)*num_bufs, +				   GFP_KERNEL); +	if (!dev->isoc_ctl.transfer_buffer) { +		tm6000_err("cannot allocate memory for usbtransfer\n"); +		kfree(dev->isoc_ctl.urb); +		return -ENOMEM; +	} + +	dprintk(dev, V4L2_DEBUG_QUEUE, "Allocating %d x %d packets" +		    " (%d bytes) of %d bytes each to handle %u size\n", +		    max_packets, num_bufs, sb_size, +		    dev->isoc_in.maxsize, size); + + +	if (!dev->urb_buffer && tm6000_alloc_urb_buffers(dev) < 0) { +		tm6000_err("cannot allocate memory for urb buffers\n"); + +		/* call free, as some buffers might have been allocated */ +		tm6000_free_urb_buffers(dev); +		kfree(dev->isoc_ctl.urb); +		kfree(dev->isoc_ctl.transfer_buffer); +		return -ENOMEM; +	} + +	/* allocate urbs and transfer buffers */ +	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { +		urb = usb_alloc_urb(max_packets, GFP_KERNEL); +		if (!urb) { +			tm6000_err("cannot alloc isoc_ctl.urb %i\n", i); +			tm6000_uninit_isoc(dev); +			usb_free_urb(urb); +			return -ENOMEM; +		} +		dev->isoc_ctl.urb[i] = urb; + +		urb->transfer_dma = dev->urb_dma[i]; +		dev->isoc_ctl.transfer_buffer[i] = dev->urb_buffer[i]; + +		usb_fill_bulk_urb(urb, dev->udev, pipe, +				  dev->isoc_ctl.transfer_buffer[i], sb_size, +				  tm6000_irq_callback, dma_q); +		urb->interval = dev->isoc_in.endp->desc.bInterval; +		urb->number_of_packets = max_packets; +		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + +		for (j = 0; j < max_packets; j++) { +			urb->iso_frame_desc[j].offset = size * j; +			urb->iso_frame_desc[j].length = size; +		} +	} + +	return 0; +} + +static int tm6000_start_thread(struct tm6000_core *dev) +{ +	struct tm6000_dmaqueue *dma_q = &dev->vidq; +	int i; + +	dma_q->frame = 0; +	dma_q->ini_jiffies = jiffies; + +	init_waitqueue_head(&dma_q->wq); + +	/* submit urbs and enables IRQ */ +	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { +		int rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC); +		if (rc) { +			tm6000_err("submit of urb %i failed (error=%i)\n", i, +				   rc); +			tm6000_uninit_isoc(dev); +			return rc; +		} +	} + +	return 0; +} + +/* ------------------------------------------------------------------ + *	Videobuf operations + * ------------------------------------------------------------------ + */ + +static int +buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size) +{ +	struct tm6000_fh *fh = vq->priv_data; + +	*size = fh->fmt->depth * fh->width * fh->height >> 3; +	if (0 == *count) +		*count = TM6000_DEF_BUF; + +	if (*count < TM6000_MIN_BUF) +		*count = TM6000_MIN_BUF; + +	while (*size * *count > vid_limit * 1024 * 1024) +		(*count)--; + +	return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct tm6000_buffer *buf) +{ +	struct tm6000_fh *fh = vq->priv_data; +	struct tm6000_core   *dev = fh->dev; +	unsigned long flags; + +	if (in_interrupt()) +		BUG(); + +	/* We used to wait for the buffer to finish here, but this didn't work +	   because, as we were keeping the state as VIDEOBUF_QUEUED, +	   videobuf_queue_cancel marked it as finished for us. +	   (Also, it could wedge forever if the hardware was misconfigured.) + +	   This should be safe; by the time we get here, the buffer isn't +	   queued anymore. If we ever start marking the buffers as +	   VIDEOBUF_ACTIVE, it won't be, though. +	*/ +	spin_lock_irqsave(&dev->slock, flags); +	if (dev->isoc_ctl.buf == buf) +		dev->isoc_ctl.buf = NULL; +	spin_unlock_irqrestore(&dev->slock, flags); + +	videobuf_vmalloc_free(&buf->vb); +	buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +static int +buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, +						enum v4l2_field field) +{ +	struct tm6000_fh     *fh  = vq->priv_data; +	struct tm6000_buffer *buf = container_of(vb, struct tm6000_buffer, vb); +	struct tm6000_core   *dev = fh->dev; +	int rc = 0; + +	BUG_ON(NULL == fh->fmt); + + +	/* FIXME: It assumes depth=2 */ +	/* The only currently supported format is 16 bits/pixel */ +	buf->vb.size = fh->fmt->depth*fh->width*fh->height >> 3; +	if (0 != buf->vb.baddr  &&  buf->vb.bsize < buf->vb.size) +		return -EINVAL; + +	if (buf->fmt       != fh->fmt    || +	    buf->vb.width  != fh->width  || +	    buf->vb.height != fh->height || +	    buf->vb.field  != field) { +		buf->fmt       = fh->fmt; +		buf->vb.width  = fh->width; +		buf->vb.height = fh->height; +		buf->vb.field  = field; +		buf->vb.state = VIDEOBUF_NEEDS_INIT; +	} + +	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { +		rc = videobuf_iolock(vq, &buf->vb, NULL); +		if (rc != 0) +			goto fail; +	} + +	if (!dev->isoc_ctl.num_bufs) { +		rc = tm6000_prepare_isoc(dev); +		if (rc < 0) +			goto fail; + +		rc = tm6000_start_thread(dev); +		if (rc < 0) +			goto fail; + +	} + +	buf->vb.state = VIDEOBUF_PREPARED; +	return 0; + +fail: +	free_buffer(vq, buf); +	return rc; +} + +static void +buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ +	struct tm6000_buffer    *buf     = container_of(vb, struct tm6000_buffer, vb); +	struct tm6000_fh        *fh      = vq->priv_data; +	struct tm6000_core      *dev     = fh->dev; +	struct tm6000_dmaqueue  *vidq    = &dev->vidq; + +	buf->vb.state = VIDEOBUF_QUEUED; +	list_add_tail(&buf->vb.queue, &vidq->active); +} + +static void buffer_release(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ +	struct tm6000_buffer   *buf  = container_of(vb, struct tm6000_buffer, vb); + +	free_buffer(vq, buf); +} + +static struct videobuf_queue_ops tm6000_video_qops = { +	.buf_setup      = buffer_setup, +	.buf_prepare    = buffer_prepare, +	.buf_queue      = buffer_queue, +	.buf_release    = buffer_release, +}; + +/* ------------------------------------------------------------------ + *	IOCTL handling + * ------------------------------------------------------------------ + */ + +static bool is_res_read(struct tm6000_core *dev, struct tm6000_fh *fh) +{ +	/* Is the current fh handling it? if so, that's OK */ +	if (dev->resources == fh && dev->is_res_read) +		return true; + +	return false; +} + +static bool is_res_streaming(struct tm6000_core *dev, struct tm6000_fh *fh) +{ +	/* Is the current fh handling it? if so, that's OK */ +	if (dev->resources == fh) +		return true; + +	return false; +} + +static bool res_get(struct tm6000_core *dev, struct tm6000_fh *fh, +		   bool is_res_read) +{ +	/* Is the current fh handling it? if so, that's OK */ +	if (dev->resources == fh && dev->is_res_read == is_res_read) +		return true; + +	/* is it free? */ +	if (dev->resources) +		return false; + +	/* grab it */ +	dev->resources = fh; +	dev->is_res_read = is_res_read; +	dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: get\n"); +	return true; +} + +static void res_free(struct tm6000_core *dev, struct tm6000_fh *fh) +{ +	/* Is the current fh handling it? if so, that's OK */ +	if (dev->resources != fh) +		return; + +	dev->resources = NULL; +	dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: put\n"); +} + +/* ------------------------------------------------------------------ + *	IOCTL vidioc handling + * ------------------------------------------------------------------ + */ +static int vidioc_querycap(struct file *file, void  *priv, +					struct v4l2_capability *cap) +{ +	struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev; +	struct video_device *vdev = video_devdata(file); + +	strlcpy(cap->driver, "tm6000", sizeof(cap->driver)); +	strlcpy(cap->card, "Trident TVMaster TM5600/6000/6010", sizeof(cap->card)); +	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info)); +	if (dev->tuner_type != TUNER_ABSENT) +		cap->device_caps |= V4L2_CAP_TUNER; +	if (vdev->vfl_type == VFL_TYPE_GRABBER) +		cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE | +				V4L2_CAP_STREAMING | +				V4L2_CAP_READWRITE; +	else +		cap->device_caps |= V4L2_CAP_RADIO; +	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS | +		V4L2_CAP_RADIO | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE; + +	return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv, +					struct v4l2_fmtdesc *f) +{ +	if (f->index >= ARRAY_SIZE(format)) +		return -EINVAL; + +	strlcpy(f->description, format[f->index].name, sizeof(f->description)); +	f->pixelformat = format[f->index].fourcc; +	return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, +					struct v4l2_format *f) +{ +	struct tm6000_fh  *fh = priv; + +	f->fmt.pix.width        = fh->width; +	f->fmt.pix.height       = fh->height; +	f->fmt.pix.field        = fh->vb_vidq.field; +	f->fmt.pix.pixelformat  = fh->fmt->fourcc; +	f->fmt.pix.colorspace   = V4L2_COLORSPACE_SMPTE170M; +	f->fmt.pix.bytesperline = +		(f->fmt.pix.width * fh->fmt->depth) >> 3; +	f->fmt.pix.sizeimage = +		f->fmt.pix.height * f->fmt.pix.bytesperline; +	f->fmt.pix.priv = 0; + +	return 0; +} + +static struct tm6000_fmt *format_by_fourcc(unsigned int fourcc) +{ +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(format); i++) +		if (format[i].fourcc == fourcc) +			return format+i; +	return NULL; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, +			struct v4l2_format *f) +{ +	struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev; +	struct tm6000_fmt *fmt; +	enum v4l2_field field; + +	fmt = format_by_fourcc(f->fmt.pix.pixelformat); +	if (NULL == fmt) { +		dprintk(dev, V4L2_DEBUG_IOCTL_ARG, "Fourcc format (0x%08x)" +				" invalid.\n", f->fmt.pix.pixelformat); +		return -EINVAL; +	} + +	field = f->fmt.pix.field; + +	field = V4L2_FIELD_INTERLACED; + +	tm6000_get_std_res(dev); + +	f->fmt.pix.width  = dev->width; +	f->fmt.pix.height = dev->height; + +	f->fmt.pix.width &= ~0x01; + +	f->fmt.pix.field = field; +	f->fmt.pix.priv = 0; + +	f->fmt.pix.bytesperline = +		(f->fmt.pix.width * fmt->depth) >> 3; +	f->fmt.pix.sizeimage = +		f->fmt.pix.height * f->fmt.pix.bytesperline; +	f->fmt.pix.colorspace   = V4L2_COLORSPACE_SMPTE170M; + +	return 0; +} + +/*FIXME: This seems to be generic enough to be at videodev2 */ +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, +					struct v4l2_format *f) +{ +	struct tm6000_fh  *fh = priv; +	struct tm6000_core *dev = fh->dev; +	int ret = vidioc_try_fmt_vid_cap(file, fh, f); +	if (ret < 0) +		return ret; + +	fh->fmt           = format_by_fourcc(f->fmt.pix.pixelformat); +	fh->width         = f->fmt.pix.width; +	fh->height        = f->fmt.pix.height; +	fh->vb_vidq.field = f->fmt.pix.field; +	fh->type          = f->type; + +	dev->fourcc       = f->fmt.pix.pixelformat; + +	tm6000_set_fourcc_format(dev); + +	return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, +			   struct v4l2_requestbuffers *p) +{ +	struct tm6000_fh  *fh = priv; + +	return videobuf_reqbufs(&fh->vb_vidq, p); +} + +static int vidioc_querybuf(struct file *file, void *priv, +			    struct v4l2_buffer *p) +{ +	struct tm6000_fh  *fh = priv; + +	return videobuf_querybuf(&fh->vb_vidq, p); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ +	struct tm6000_fh  *fh = priv; + +	return videobuf_qbuf(&fh->vb_vidq, p); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ +	struct tm6000_fh  *fh = priv; + +	return videobuf_dqbuf(&fh->vb_vidq, p, +				file->f_flags & O_NONBLOCK); +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ +	struct tm6000_fh *fh = priv; +	struct tm6000_core *dev = fh->dev; + +	if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return -EINVAL; +	if (i != fh->type) +		return -EINVAL; + +	if (!res_get(dev, fh, false)) +		return -EBUSY; +	return videobuf_streamon(&fh->vb_vidq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ +	struct tm6000_fh *fh = priv; +	struct tm6000_core *dev = fh->dev; + +	if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return -EINVAL; + +	if (i != fh->type) +		return -EINVAL; + +	videobuf_streamoff(&fh->vb_vidq); +	res_free(dev, fh); + +	return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm) +{ +	int rc = 0; +	struct tm6000_fh *fh = priv; +	struct tm6000_core *dev = fh->dev; + +	dev->norm = norm; +	rc = tm6000_init_analog_mode(dev); + +	fh->width  = dev->width; +	fh->height = dev->height; + +	if (rc < 0) +		return rc; + +	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, dev->norm); + +	return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm) +{ +	struct tm6000_fh *fh = priv; +	struct tm6000_core *dev = fh->dev; + +	*norm = dev->norm; +	return 0; +} + +static const char *iname[] = { +	[TM6000_INPUT_TV] = "Television", +	[TM6000_INPUT_COMPOSITE1] = "Composite 1", +	[TM6000_INPUT_COMPOSITE2] = "Composite 2", +	[TM6000_INPUT_SVIDEO] = "S-Video", +}; + +static int vidioc_enum_input(struct file *file, void *priv, +				struct v4l2_input *i) +{ +	struct tm6000_fh   *fh = priv; +	struct tm6000_core *dev = fh->dev; +	unsigned int n; + +	n = i->index; +	if (n >= 3) +		return -EINVAL; + +	if (!dev->vinput[n].type) +		return -EINVAL; + +	i->index = n; + +	if (dev->vinput[n].type == TM6000_INPUT_TV) +		i->type = V4L2_INPUT_TYPE_TUNER; +	else +		i->type = V4L2_INPUT_TYPE_CAMERA; + +	strcpy(i->name, iname[dev->vinput[n].type]); + +	i->std = TM6000_STD; + +	return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ +	struct tm6000_fh   *fh = priv; +	struct tm6000_core *dev = fh->dev; + +	*i = dev->input; + +	return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ +	struct tm6000_fh   *fh = priv; +	struct tm6000_core *dev = fh->dev; +	int rc = 0; + +	if (i >= 3) +		return -EINVAL; +	if (!dev->vinput[i].type) +		return -EINVAL; + +	dev->input = i; + +	rc = vidioc_s_std(file, priv, dev->norm); + +	return rc; +} + +/* --- controls ---------------------------------------------- */ + +static int tm6000_s_ctrl(struct v4l2_ctrl *ctrl) +{ +	struct tm6000_core *dev = container_of(ctrl->handler, struct tm6000_core, ctrl_handler); +	u8  val = ctrl->val; + +	switch (ctrl->id) { +	case V4L2_CID_CONTRAST: +		tm6000_set_reg(dev, TM6010_REQ07_R08_LUMA_CONTRAST_ADJ, val); +		return 0; +	case V4L2_CID_BRIGHTNESS: +		tm6000_set_reg(dev, TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ, val); +		return 0; +	case V4L2_CID_SATURATION: +		tm6000_set_reg(dev, TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ, val); +		return 0; +	case V4L2_CID_HUE: +		tm6000_set_reg(dev, TM6010_REQ07_R0B_CHROMA_HUE_PHASE_ADJ, val); +		return 0; +	} +	return -EINVAL; +} + +static const struct v4l2_ctrl_ops tm6000_ctrl_ops = { +	.s_ctrl = tm6000_s_ctrl, +}; + +static int tm6000_radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ +	struct tm6000_core *dev = container_of(ctrl->handler, +			struct tm6000_core, radio_ctrl_handler); +	u8  val = ctrl->val; + +	switch (ctrl->id) { +	case V4L2_CID_AUDIO_MUTE: +		dev->ctl_mute = val; +		tm6000_tvaudio_set_mute(dev, val); +		return 0; +	case V4L2_CID_AUDIO_VOLUME: +		dev->ctl_volume = val; +		tm6000_set_volume(dev, val); +		return 0; +	} +	return -EINVAL; +} + +static const struct v4l2_ctrl_ops tm6000_radio_ctrl_ops = { +	.s_ctrl = tm6000_radio_s_ctrl, +}; + +static int vidioc_g_tuner(struct file *file, void *priv, +				struct v4l2_tuner *t) +{ +	struct tm6000_fh   *fh  = priv; +	struct tm6000_core *dev = fh->dev; + +	if (UNSET == dev->tuner_type) +		return -ENOTTY; +	if (0 != t->index) +		return -EINVAL; + +	strcpy(t->name, "Television"); +	t->type       = V4L2_TUNER_ANALOG_TV; +	t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO; +	t->rangehigh  = 0xffffffffUL; +	t->rxsubchans = V4L2_TUNER_SUB_STEREO; + +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t); + +	t->audmode = dev->amode; + +	return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, +				const struct v4l2_tuner *t) +{ +	struct tm6000_fh   *fh  = priv; +	struct tm6000_core *dev = fh->dev; + +	if (UNSET == dev->tuner_type) +		return -ENOTTY; +	if (0 != t->index) +		return -EINVAL; + +	if (t->audmode > V4L2_TUNER_MODE_STEREO) +		dev->amode = V4L2_TUNER_MODE_STEREO; +	else +		dev->amode = t->audmode; +	dprintk(dev, 3, "audio mode: %x\n", t->audmode); + +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t); + +	return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, +				struct v4l2_frequency *f) +{ +	struct tm6000_fh   *fh  = priv; +	struct tm6000_core *dev = fh->dev; + +	if (UNSET == dev->tuner_type) +		return -ENOTTY; +	if (f->tuner) +		return -EINVAL; + +	f->frequency = dev->freq; + +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, f); + +	return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, +				const struct v4l2_frequency *f) +{ +	struct tm6000_fh   *fh  = priv; +	struct tm6000_core *dev = fh->dev; + +	if (UNSET == dev->tuner_type) +		return -ENOTTY; +	if (f->tuner != 0) +		return -EINVAL; + +	dev->freq = f->frequency; +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, f); + +	return 0; +} + +static int radio_g_tuner(struct file *file, void *priv, +					struct v4l2_tuner *t) +{ +	struct tm6000_fh *fh = file->private_data; +	struct tm6000_core *dev = fh->dev; + +	if (0 != t->index) +		return -EINVAL; + +	memset(t, 0, sizeof(*t)); +	strcpy(t->name, "Radio"); +	t->type = V4L2_TUNER_RADIO; +	t->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; +	t->rxsubchans = V4L2_TUNER_SUB_STEREO; +	t->audmode = V4L2_TUNER_MODE_STEREO; + +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t); + +	return 0; +} + +static int radio_s_tuner(struct file *file, void *priv, +					const struct v4l2_tuner *t) +{ +	struct tm6000_fh *fh = file->private_data; +	struct tm6000_core *dev = fh->dev; + +	if (0 != t->index) +		return -EINVAL; +	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t); +	return 0; +} + +/* ------------------------------------------------------------------ +	File operations for the device +   ------------------------------------------------------------------*/ + +static int __tm6000_open(struct file *file) +{ +	struct video_device *vdev = video_devdata(file); +	struct tm6000_core *dev = video_drvdata(file); +	struct tm6000_fh *fh; +	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	int rc; +	int radio = 0; + +	dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: open called (dev=%s)\n", +		video_device_node_name(vdev)); + +	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; +	} + +	/* If more than one user, mutex should be added */ +	dev->users++; + +	dprintk(dev, V4L2_DEBUG_OPEN, "open dev=%s type=%s users=%d\n", +		video_device_node_name(vdev), v4l2_type_names[type], +		dev->users); + +	/* allocate + initialize per filehandle data */ +	fh = kzalloc(sizeof(*fh), GFP_KERNEL); +	if (NULL == fh) { +		dev->users--; +		return -ENOMEM; +	} + +	v4l2_fh_init(&fh->fh, vdev); +	file->private_data = fh; +	fh->dev      = dev; +	fh->radio    = radio; +	dev->radio   = radio; +	fh->type     = type; +	dev->fourcc  = format[0].fourcc; + +	fh->fmt      = format_by_fourcc(dev->fourcc); + +	tm6000_get_std_res(dev); + +	fh->width = dev->width; +	fh->height = dev->height; + +	dprintk(dev, V4L2_DEBUG_OPEN, "Open: fh=0x%08lx, dev=0x%08lx, " +						"dev->vidq=0x%08lx\n", +			(unsigned long)fh, (unsigned long)dev, +			(unsigned long)&dev->vidq); +	dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty " +				"queued=%d\n", list_empty(&dev->vidq.queued)); +	dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty " +				"active=%d\n", list_empty(&dev->vidq.active)); + +	/* initialize hardware on analog mode */ +	rc = tm6000_init_analog_mode(dev); +	if (rc < 0) +		return rc; + +	dev->mode = TM6000_MODE_ANALOG; + +	if (!fh->radio) { +		videobuf_queue_vmalloc_init(&fh->vb_vidq, &tm6000_video_qops, +				NULL, &dev->slock, +				fh->type, +				V4L2_FIELD_INTERLACED, +				sizeof(struct tm6000_buffer), fh, &dev->lock); +	} else { +		dprintk(dev, V4L2_DEBUG_OPEN, "video_open: setting radio device\n"); +		tm6000_set_audio_rinput(dev); +		v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_radio); +		tm6000_prepare_isoc(dev); +		tm6000_start_thread(dev); +	} +	v4l2_fh_add(&fh->fh); + +	return 0; +} + +static int tm6000_open(struct file *file) +{ +	struct video_device *vdev = video_devdata(file); +	int res; + +	mutex_lock(vdev->lock); +	res = __tm6000_open(file); +	mutex_unlock(vdev->lock); +	return res; +} + +static ssize_t +tm6000_read(struct file *file, char __user *data, size_t count, loff_t *pos) +{ +	struct tm6000_fh *fh = file->private_data; +	struct tm6000_core *dev = fh->dev; + +	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { +		int res; + +		if (!res_get(fh->dev, fh, true)) +			return -EBUSY; + +		if (mutex_lock_interruptible(&dev->lock)) +			return -ERESTARTSYS; +		res = videobuf_read_stream(&fh->vb_vidq, data, count, pos, 0, +					file->f_flags & O_NONBLOCK); +		mutex_unlock(&dev->lock); +		return res; +	} +	return 0; +} + +static unsigned int +__tm6000_poll(struct file *file, struct poll_table_struct *wait) +{ +	unsigned long req_events = poll_requested_events(wait); +	struct tm6000_fh        *fh = file->private_data; +	struct tm6000_buffer    *buf; +	int res = 0; + +	if (v4l2_event_pending(&fh->fh)) +		res = POLLPRI; +	else if (req_events & POLLPRI) +		poll_wait(file, &fh->fh.wait, wait); +	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type) +		return res | POLLERR; + +	if (!!is_res_streaming(fh->dev, fh)) +		return res | POLLERR; + +	if (!is_res_read(fh->dev, fh)) { +		/* streaming capture */ +		if (list_empty(&fh->vb_vidq.stream)) +			return res | POLLERR; +		buf = list_entry(fh->vb_vidq.stream.next, struct tm6000_buffer, vb.stream); +		poll_wait(file, &buf->vb.done, wait); +		if (buf->vb.state == VIDEOBUF_DONE || +		    buf->vb.state == VIDEOBUF_ERROR) +			return res | POLLIN | POLLRDNORM; +	} else if (req_events & (POLLIN | POLLRDNORM)) { +		/* read() capture */ +		return res | videobuf_poll_stream(file, &fh->vb_vidq, wait); +	} +	return res; +} + +static unsigned int tm6000_poll(struct file *file, struct poll_table_struct *wait) +{ +	struct tm6000_fh *fh = file->private_data; +	struct tm6000_core *dev = fh->dev; +	unsigned int res; + +	mutex_lock(&dev->lock); +	res = __tm6000_poll(file, wait); +	mutex_unlock(&dev->lock); +	return res; +} + +static int tm6000_release(struct file *file) +{ +	struct tm6000_fh         *fh = file->private_data; +	struct tm6000_core      *dev = fh->dev; +	struct video_device    *vdev = video_devdata(file); + +	dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: close called (dev=%s, users=%d)\n", +		video_device_node_name(vdev), dev->users); + +	mutex_lock(&dev->lock); +	dev->users--; + +	res_free(dev, fh); + +	if (!dev->users) { +		tm6000_uninit_isoc(dev); + +		/* Stop interrupt USB pipe */ +		tm6000_ir_int_stop(dev); + +		usb_reset_configuration(dev->udev); + +		if (dev->int_in.endp) +			usb_set_interface(dev->udev, +					dev->isoc_in.bInterfaceNumber, 2); +		else +			usb_set_interface(dev->udev, +					dev->isoc_in.bInterfaceNumber, 0); + +		/* Start interrupt USB pipe */ +		tm6000_ir_int_start(dev); + +		if (!fh->radio) +			videobuf_mmap_free(&fh->vb_vidq); +	} +	v4l2_fh_del(&fh->fh); +	v4l2_fh_exit(&fh->fh); +	kfree(fh); +	mutex_unlock(&dev->lock); + +	return 0; +} + +static int tm6000_mmap(struct file *file, struct vm_area_struct * vma) +{ +	struct tm6000_fh *fh = file->private_data; +	struct tm6000_core *dev = fh->dev; +	int res; + +	if (mutex_lock_interruptible(&dev->lock)) +		return -ERESTARTSYS; +	res = videobuf_mmap_mapper(&fh->vb_vidq, vma); +	mutex_unlock(&dev->lock); +	return res; +} + +static struct v4l2_file_operations tm6000_fops = { +	.owner = THIS_MODULE, +	.open = tm6000_open, +	.release = tm6000_release, +	.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ +	.read = tm6000_read, +	.poll = tm6000_poll, +	.mmap = tm6000_mmap, +}; + +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_s_std             = vidioc_s_std, +	.vidioc_g_std             = vidioc_g_std, +	.vidioc_enum_input        = vidioc_enum_input, +	.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_frequency       = vidioc_g_frequency, +	.vidioc_s_frequency       = vidioc_s_frequency, +	.vidioc_streamon          = vidioc_streamon, +	.vidioc_streamoff         = vidioc_streamoff, +	.vidioc_reqbufs           = vidioc_reqbufs, +	.vidioc_querybuf          = vidioc_querybuf, +	.vidioc_qbuf              = vidioc_qbuf, +	.vidioc_dqbuf             = vidioc_dqbuf, +	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event, +	.vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device tm6000_template = { +	.name		= "tm6000", +	.fops           = &tm6000_fops, +	.ioctl_ops      = &video_ioctl_ops, +	.release	= video_device_release, +	.tvnorms        = TM6000_STD, +}; + +static const struct v4l2_file_operations radio_fops = { +	.owner		= THIS_MODULE, +	.open		= tm6000_open, +	.poll		= v4l2_ctrl_poll, +	.release	= tm6000_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, +}; + +static struct video_device tm6000_radio_template = { +	.name			= "tm6000", +	.fops			= &radio_fops, +	.ioctl_ops		= &radio_ioctl_ops, +}; + +/* ----------------------------------------------------------------- + *	Initialization and module stuff + * ------------------------------------------------------------------ + */ + +static struct video_device *vdev_init(struct tm6000_core *dev, +		const struct video_device +		*template, const char *type_name) +{ +	struct video_device *vfd; + +	vfd = video_device_alloc(); +	if (NULL == vfd) +		return NULL; + +	*vfd = *template; +	vfd->v4l2_dev = &dev->v4l2_dev; +	vfd->release = video_device_release; +	vfd->debug = tm6000_debug; +	vfd->lock = &dev->lock; +	set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); + +	snprintf(vfd->name, sizeof(vfd->name), "%s %s", dev->name, type_name); + +	video_set_drvdata(vfd, dev); +	return vfd; +} + +int tm6000_v4l2_register(struct tm6000_core *dev) +{ +	int ret = 0; + +	v4l2_ctrl_handler_init(&dev->ctrl_handler, 6); +	v4l2_ctrl_handler_init(&dev->radio_ctrl_handler, 2); +	v4l2_ctrl_new_std(&dev->radio_ctrl_handler, &tm6000_radio_ctrl_ops, +			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); +	v4l2_ctrl_new_std(&dev->radio_ctrl_handler, &tm6000_radio_ctrl_ops, +			V4L2_CID_AUDIO_VOLUME, -15, 15, 1, 0); +	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops, +			V4L2_CID_BRIGHTNESS, 0, 255, 1, 54); +	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops, +			V4L2_CID_CONTRAST, 0, 255, 1, 119); +	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops, +			V4L2_CID_SATURATION, 0, 255, 1, 112); +	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops, +			V4L2_CID_HUE, -128, 127, 1, 0); +	v4l2_ctrl_add_handler(&dev->ctrl_handler, +			&dev->radio_ctrl_handler, NULL); + +	if (dev->radio_ctrl_handler.error) +		ret = dev->radio_ctrl_handler.error; +	if (!ret && dev->ctrl_handler.error) +		ret = dev->ctrl_handler.error; +	if (ret) +		goto free_ctrl; + +	dev->vfd = vdev_init(dev, &tm6000_template, "video"); + +	if (!dev->vfd) { +		printk(KERN_INFO "%s: can't register video device\n", +		       dev->name); +		ret = -ENOMEM; +		goto free_ctrl; +	} +	dev->vfd->ctrl_handler = &dev->ctrl_handler; + +	/* init video dma queues */ +	INIT_LIST_HEAD(&dev->vidq.active); +	INIT_LIST_HEAD(&dev->vidq.queued); + +	ret = video_register_device(dev->vfd, VFL_TYPE_GRABBER, video_nr); + +	if (ret < 0) { +		printk(KERN_INFO "%s: can't register video device\n", +		       dev->name); +		video_device_release(dev->vfd); +		dev->vfd = NULL; +		goto free_ctrl; +	} + +	printk(KERN_INFO "%s: registered device %s\n", +	       dev->name, video_device_node_name(dev->vfd)); + +	if (dev->caps.has_radio) { +		dev->radio_dev = vdev_init(dev, &tm6000_radio_template, +							   "radio"); +		if (!dev->radio_dev) { +			printk(KERN_INFO "%s: can't register radio device\n", +			       dev->name); +			ret = -ENXIO; +			goto unreg_video; +		} + +		dev->radio_dev->ctrl_handler = &dev->radio_ctrl_handler; +		ret = video_register_device(dev->radio_dev, VFL_TYPE_RADIO, +					    radio_nr); +		if (ret < 0) { +			printk(KERN_INFO "%s: can't register radio device\n", +			       dev->name); +			video_device_release(dev->radio_dev); +			goto unreg_video; +		} + +		printk(KERN_INFO "%s: registered device %s\n", +		       dev->name, video_device_node_name(dev->radio_dev)); +	} + +	printk(KERN_INFO "Trident TVMaster TM5600/TM6000/TM6010 USB2 board (Load status: %d)\n", ret); +	return ret; + +unreg_video: +	video_unregister_device(dev->vfd); +free_ctrl: +	v4l2_ctrl_handler_free(&dev->ctrl_handler); +	v4l2_ctrl_handler_free(&dev->radio_ctrl_handler); +	return ret; +} + +int tm6000_v4l2_unregister(struct tm6000_core *dev) +{ +	video_unregister_device(dev->vfd); + +	/* if URB buffers are still allocated free them now */ +	tm6000_free_urb_buffers(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; +	} + +	return 0; +} + +int tm6000_v4l2_exit(void) +{ +	return 0; +} + +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr, "Allow changing video device number"); + +module_param_named(debug, tm6000_debug, int, 0444); +MODULE_PARM_DESC(debug, "activates debug info"); + +module_param(vid_limit, int, 0644); +MODULE_PARM_DESC(vid_limit, "capture memory limit in megabytes"); + +module_param(keep_urb, bool, 0); +MODULE_PARM_DESC(keep_urb, "Keep urb buffers allocated even when the device is closed by the user"); diff --git a/drivers/media/usb/tm6000/tm6000.h b/drivers/media/usb/tm6000/tm6000.h new file mode 100644 index 00000000000..08bd0740dd2 --- /dev/null +++ b/drivers/media/usb/tm6000/tm6000.h @@ -0,0 +1,409 @@ +/* + *  tm6000.h - driver for TM5600/TM6000/TM6010 USB video capture devices + * + *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + * + *  Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + *	- DVB-T support + * + *  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 version 2 + * + *  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/videodev2.h> +#include <media/v4l2-common.h> +#include <media/videobuf-vmalloc.h> +#include "tm6000-usb-isoc.h" +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fh.h> + +#include <linux/dvb/frontend.h> +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dmxdev.h" + +/* Inputs */ +enum tm6000_itype { +	TM6000_INPUT_TV	= 1, +	TM6000_INPUT_COMPOSITE1, +	TM6000_INPUT_COMPOSITE2, +	TM6000_INPUT_SVIDEO, +	TM6000_INPUT_DVB, +	TM6000_INPUT_RADIO, +}; + +enum tm6000_mux { +	TM6000_VMUX_VIDEO_A = 1, +	TM6000_VMUX_VIDEO_B, +	TM6000_VMUX_VIDEO_AB, +	TM6000_AMUX_ADC1, +	TM6000_AMUX_ADC2, +	TM6000_AMUX_SIF1, +	TM6000_AMUX_SIF2, +	TM6000_AMUX_I2S, +}; + +enum tm6000_devtype { +	TM6000 = 0, +	TM5600, +	TM6010, +}; + +struct tm6000_input { +	enum tm6000_itype	type; +	enum tm6000_mux		vmux; +	enum tm6000_mux		amux; +	unsigned int		v_gpio; +	unsigned int		a_gpio; +}; + +/* ------------------------------------------------------------------ + *	Basic structures + * ------------------------------------------------------------------ + */ + +struct tm6000_fmt { +	char  *name; +	u32   fourcc;          /* v4l2 format id */ +	int   depth; +}; + +/* buffer for one video frame */ +struct tm6000_buffer { +	/* common v4l buffer stuff -- must be first */ +	struct videobuf_buffer vb; + +	struct tm6000_fmt      *fmt; +}; + +struct tm6000_dmaqueue { +	struct list_head       active; +	struct list_head       queued; + +	/* thread for generating video stream*/ +	struct task_struct         *kthread; +	wait_queue_head_t          wq; +	/* Counters to control fps rate */ +	int                        frame; +	int                        ini_jiffies; +}; + +/* device states */ +enum tm6000_core_state { +	DEV_INITIALIZED   = 0x01, +	DEV_DISCONNECTED  = 0x02, +	DEV_MISCONFIGURED = 0x04, +}; + +/* io methods */ +enum tm6000_io_method { +	IO_NONE, +	IO_READ, +	IO_MMAP, +}; + +enum tm6000_mode { +	TM6000_MODE_UNKNOWN = 0, +	TM6000_MODE_ANALOG, +	TM6000_MODE_DIGITAL, +}; + +struct tm6000_gpio { +	int		tuner_reset; +	int		tuner_on; +	int		demod_reset; +	int		demod_on; +	int		power_led; +	int		dvb_led; +	int		ir; +}; + +struct tm6000_capabilities { +	unsigned int    has_tuner:1; +	unsigned int    has_tda9874:1; +	unsigned int    has_dvb:1; +	unsigned int    has_zl10353:1; +	unsigned int    has_eeprom:1; +	unsigned int    has_remote:1; +	unsigned int    has_radio:1; +}; + +struct tm6000_dvb { +	struct dvb_adapter	adapter; +	struct dvb_demux	demux; +	struct dvb_frontend	*frontend; +	struct dmxdev		dmxdev; +	unsigned int		streams; +	struct urb		*bulk_urb; +	struct mutex		mutex; +}; + +struct snd_tm6000_card { +	struct snd_card			*card; +	spinlock_t			reg_lock; +	struct tm6000_core		*core; +	struct snd_pcm_substream	*substream; + +	/* temporary data for buffer fill processing */ +	unsigned			buf_pos; +	unsigned			period_pos; +}; + +struct tm6000_endpoint { +	struct usb_host_endpoint	*endp; +	__u8				bInterfaceNumber; +	__u8				bAlternateSetting; +	unsigned			maxsize; +}; + +#define TM6000_QUIRK_NO_USB_DELAY (1 << 0) + +struct tm6000_core { +	/* generic device properties */ +	char				name[30];	/* name (including minor) of the device */ +	int				model;		/* index in the device_data struct */ +	int				devno;		/* marks the number of this device */ +	enum tm6000_devtype		dev_type;	/* type of device */ +	unsigned char			eedata[256];	/* Eeprom data */ +	unsigned			eedata_size;	/* Size of the eeprom info */ + +	v4l2_std_id                     norm;           /* Current norm */ +	int				width, height;	/* Selected resolution */ + +	enum tm6000_core_state		state; + +	/* Device Capabilities*/ +	struct tm6000_capabilities	caps; + +	/* Used to load alsa/dvb */ +        struct work_struct		request_module_wk; + +	/* Tuner configuration */ +	int				tuner_type;		/* type of the tuner */ +	int				tuner_addr;		/* tuner address */ + +	struct tm6000_gpio		gpio; + +	char				*ir_codes; + +	__u8				radio; + +	/* Demodulator configuration */ +	int				demod_addr;	/* demodulator address */ + +	int				audio_bitrate; +	/* i2c i/o */ +	struct i2c_adapter		i2c_adap; +	struct i2c_client		i2c_client; + + +	/* extension */ +	struct list_head		devlist; + +	/* video for linux */ +	int				users; + +	/* various device info */ +	struct tm6000_fh		*resources;	/* Points to fh that is streaming */ +	bool				is_res_read; + +	struct video_device		*vfd; +	struct video_device		*radio_dev; +	struct tm6000_dmaqueue		vidq; +	struct v4l2_device		v4l2_dev; +	struct v4l2_ctrl_handler	ctrl_handler; +	struct v4l2_ctrl_handler	radio_ctrl_handler; + +	int				input; +	struct tm6000_input		vinput[3];	/* video input */ +	struct tm6000_input		rinput;		/* radio input */ + +	int				freq; +	unsigned int			fourcc; + +	enum tm6000_mode		mode; + +	int				ctl_mute;             /* audio */ +	int				ctl_volume; +	int				amode; + +	/* DVB-T support */ +	struct tm6000_dvb		*dvb; + +	/* audio support */ +	struct snd_tm6000_card		*adev; +	struct work_struct		wq_trigger;   /* Trigger to start/stop audio for alsa module */ +	atomic_t			stream_started;  /* stream should be running if true */ + +	struct tm6000_IR		*ir; + +	/* locks */ +	struct mutex			lock; +	struct mutex			usb_lock; + +	/* usb transfer */ +	struct usb_device		*udev;		/* the usb device */ + +	struct tm6000_endpoint		bulk_in, bulk_out, isoc_in, isoc_out; +	struct tm6000_endpoint		int_in, int_out; + +	/* scaler!=0 if scaler is active*/ +	int				scaler; + +		/* Isoc control struct */ +	struct usb_isoc_ctl          isoc_ctl; + +	spinlock_t                   slock; + +	/* urb dma buffers */ +	char				**urb_buffer; +	dma_addr_t			*urb_dma; +	unsigned int			urb_size; + +	unsigned long quirks; +}; + +enum tm6000_ops_type { +	TM6000_AUDIO = 0x10, +	TM6000_DVB = 0x20, +}; + +struct tm6000_ops { +	struct list_head	next; +	char			*name; +	enum tm6000_ops_type	type; +	int (*init)(struct tm6000_core *); +	int (*fini)(struct tm6000_core *); +	int (*fillbuf)(struct tm6000_core *, char *buf, int size); +}; + +struct tm6000_fh { +	struct v4l2_fh		     fh; +	struct tm6000_core           *dev; +	unsigned int                 radio; + +	/* video capture */ +	struct tm6000_fmt            *fmt; +	unsigned int                 width, height; +	struct videobuf_queue        vb_vidq; + +	enum v4l2_buf_type           type; +}; + +#define TM6000_STD	(V4L2_STD_PAL|V4L2_STD_PAL_N|V4L2_STD_PAL_Nc|    \ +			V4L2_STD_PAL_M|V4L2_STD_PAL_60|V4L2_STD_NTSC_M| \ +			V4L2_STD_NTSC_M_JP|V4L2_STD_SECAM) + +/* In tm6000-cards.c */ + +int tm6000_tuner_callback(void *ptr, int component, int command, int arg); +int tm6000_xc5000_callback(void *ptr, int component, int command, int arg); +int tm6000_cards_setup(struct tm6000_core *dev); +void tm6000_flash_led(struct tm6000_core *dev, u8 state); + +/* In tm6000-core.c */ + +int tm6000_read_write_usb(struct tm6000_core *dev, u8 reqtype, u8 req, +			   u16 value, u16 index, u8 *buf, u16 len); +int tm6000_get_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index); +int tm6000_get_reg16(struct tm6000_core *dev, u8 req, u16 value, u16 index); +int tm6000_get_reg32(struct tm6000_core *dev, u8 req, u16 value, u16 index); +int tm6000_set_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index); +int tm6000_set_reg_mask(struct tm6000_core *dev, u8 req, u16 value, +						u16 index, u16 mask); +int tm6000_i2c_reset(struct tm6000_core *dev, u16 tsleep); +int tm6000_init(struct tm6000_core *dev); +int tm6000_reset(struct tm6000_core *dev); + +int tm6000_init_analog_mode(struct tm6000_core *dev); +int tm6000_init_digital_mode(struct tm6000_core *dev); +int tm6000_set_audio_bitrate(struct tm6000_core *dev, int bitrate); +int tm6000_set_audio_rinput(struct tm6000_core *dev); +int tm6000_tvaudio_set_mute(struct tm6000_core *dev, u8 mute); +void tm6000_set_volume(struct tm6000_core *dev, int vol); + +int tm6000_v4l2_register(struct tm6000_core *dev); +int tm6000_v4l2_unregister(struct tm6000_core *dev); +int tm6000_v4l2_exit(void); +void tm6000_set_fourcc_format(struct tm6000_core *dev); + +void tm6000_remove_from_devlist(struct tm6000_core *dev); +void tm6000_add_into_devlist(struct tm6000_core *dev); +int tm6000_register_extension(struct tm6000_ops *ops); +void tm6000_unregister_extension(struct tm6000_ops *ops); +void tm6000_init_extension(struct tm6000_core *dev); +void tm6000_close_extension(struct tm6000_core *dev); +int tm6000_call_fillbuf(struct tm6000_core *dev, enum tm6000_ops_type type, +			char *buf, int size); + + +/* In tm6000-stds.c */ +void tm6000_get_std_res(struct tm6000_core *dev); +int tm6000_set_standard(struct tm6000_core *dev); + +/* In tm6000-i2c.c */ +int tm6000_i2c_register(struct tm6000_core *dev); +int tm6000_i2c_unregister(struct tm6000_core *dev); + +/* In tm6000-queue.c */ + +int tm6000_v4l2_mmap(struct file *filp, struct vm_area_struct *vma); + +int tm6000_vidioc_streamon(struct file *file, void *priv, +			   enum v4l2_buf_type i); +int tm6000_vidioc_streamoff(struct file *file, void *priv, +			    enum v4l2_buf_type i); +int tm6000_vidioc_reqbufs(struct file *file, void *priv, +			  struct v4l2_requestbuffers *rb); +int tm6000_vidioc_querybuf(struct file *file, void *priv, +			   struct v4l2_buffer *b); +int tm6000_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *b); +int tm6000_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b); +ssize_t tm6000_v4l2_read(struct file *filp, char __user * buf, size_t count, +			 loff_t *f_pos); +unsigned int tm6000_v4l2_poll(struct file *file, +			      struct poll_table_struct *wait); +int tm6000_queue_init(struct tm6000_core *dev); + +/* In tm6000-alsa.c */ +/*int tm6000_audio_init(struct tm6000_core *dev, int idx);*/ + +/* In tm6000-input.c */ +int tm6000_ir_init(struct tm6000_core *dev); +int tm6000_ir_fini(struct tm6000_core *dev); +void tm6000_ir_wait(struct tm6000_core *dev, u8 state); +int tm6000_ir_int_start(struct tm6000_core *dev); +void tm6000_ir_int_stop(struct tm6000_core *dev); + +/* Debug stuff */ + +extern int tm6000_debug; + +#define dprintk(dev, level, fmt, arg...) do {\ +	if (tm6000_debug & level) \ +		printk(KERN_INFO "(%lu) %s %s :"fmt, jiffies, \ +			 dev->name, __func__ , ##arg); } while (0) + +#define V4L2_DEBUG_REG		0x0004 +#define V4L2_DEBUG_I2C		0x0008 +#define V4L2_DEBUG_QUEUE	0x0010 +#define V4L2_DEBUG_ISOC		0x0020 +#define V4L2_DEBUG_RES_LOCK	0x0040	/* Resource locking */ +#define V4L2_DEBUG_OPEN		0x0080	/* video open/close debug */ + +#define tm6000_err(fmt, arg...) do {\ +	printk(KERN_ERR "tm6000 %s :"fmt, \ +		__func__ , ##arg); } while (0)  | 
