mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Markus Rechberger" <mrechberger@gmail.com>
To: "Linux Kernel Mailing List" <linux-kernel@vger.kernel.org>,
	em28xx <em28xx@mcentral.de>
Cc: acano@fastmail.fm, "Andre Kelmanson" <akelmanson@gmail.com>,
	"Bouwsma Barry" <freebeer.bouwsma@gmail.com>,
	"Dan Kreiser" <kreiser@informatik.hu-berlin.de>,
	"Frank Neuber" <fn@kernelport.de>,
	"Jelle de Jong" <jelledejong@powercraft.nl>,
	"John Stowers" <john.stowers.lists@gmail.com>,
	"Lukas Kuna" <lukas.kuna@evkanet.net>,
	"Stefan Vonolfen" <stefan.vonolfen@gmail.com>,
	"Stephan Berberig" <s.berberig@arcor.de>,
	"Thomas Giesecke" <thomas.giesecke@ibgmbh-naumburg.de>,
	"Vitaly Wool" <vwool@ru.mvista.com>,
	"Zhenyu Wang" <zhen78@gmail.com>
Subject: [PATCH 1/7] Adding empia base driver
Date: Wed, 22 Oct 2008 23:14:36 +0200	[thread overview]
Message-ID: <d9def9db0810221414w5348acf3re31a033ea7179462@mail.gmail.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 1284 bytes --]

    em2880-dvb:
    * supporting the digital part of Empia based devices, which
includes ATSC, ISDB-T and DVB-T

    em28xx-aad.c:
    * alternative audio driver, can be used instead of em28xx-audio if
alsa is not available
    or not compiled into the kernel, it provides a raw interface to
the PCM samples

    em28xx-audio.c:
    * em28xx alsa driver and audio driver for FM radio

    em28xx-audioep.c:
    * em28xx alsa driver for devices which are set to vendor specific
audio on interface 1,
    in that case snd-usb-audio will not attach to the interface and
em28xx-audioep will be needed

    em28xx-cards.c:
    * card definition and initial setup of devices.

    em28xx-core.c:
    * core videohandling and VBI frame slicing

    em28xx-i2c.c:
    * i2c setup and GPIO setup handling of the devices (including
em2888 based ones)

    em28xx-input.c:
    * currently mostly disabled since the linuxtv input handling is
broken by design and racy

    em28xx-keymaps.c:
    * keymap references of some remotes (could be merged into
ir-common, although as mentioned
    this should be in userland done by lirc).

    em28xx-video.c:
    * inode handling for analog TV, radio and VBI, also some device probing

    em28xx-webcam.c:
    * videology webcam specific i2c commands

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: em28xx-01.diff --]
[-- Type: text/x-diff; name=em28xx-01.diff, Size: 428709 bytes --]

commit a18816f6b91629eb6038aed4815e4fb94ddc0a6b
Author: Markus Rechberger <mrechberger@sundtek.de>
Date:   Wed Oct 22 21:56:45 2008 +0200

    Adding empia base driver
    
    em2880-dvb:
    * supporting the digital part of Empia based devices, which includes ATSC, ISDB-T and DVB-T
    
    em28xx-aad.c:
    * alternative audio driver, can be used instead of em28xx-audio if alsa is not available
    or not compiled into the kernel, it provides a raw interface to the PCM samples
    
    em28xx-audio.c:
    * em28xx alsa driver and audio driver for FM radio
    
    em28xx-audioep.c:
    * em28xx alsa driver for devices which are set to vendor specific audio on interface 1,
    in that case snd-usb-audio will not attach to the interface and em28xx-audioep will be needed
    
    em28xx-cards.c:
    * card definition and initial setup of devices.
    
    em28xx-core.c:
    * core videohandling and VBI frame slicing
    
    em28xx-i2c.c:
    * i2c setup and GPIO setup handling of the devices (including em2888 based ones)
    
    em28xx-input.c:
    * currently mostly disabled since the linuxtv input handling is broken by design and racy
    
    em28xx-keymaps.c:
    * keymap references of some remotes (could be merged into ir-common, although as mentioned
    this should be in userland done by lirc).
    
    em28xx-video.c:
    * inode handling for analog TV, radio and VBI, also some device probing
    
    em28xx-webcam.c:
    * videology webcam specific i2c commands

Signed-off-by: Markus Rechberger <mrechberger@sundtek.de>

diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index 47102c2..a726c59 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -783,6 +783,8 @@ source "drivers/media/video/pvrusb2/Kconfig"
 
 source "drivers/media/video/em28xx/Kconfig"
 
+source "drivers/media/video/empia/Kconfig"
+
 source "drivers/media/video/usbvision/Kconfig"
 
 source "drivers/media/video/usbvideo/Kconfig"
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index 16962f3..9357368 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_MEYE) += meye.o
 obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
 obj-$(CONFIG_VIDEO_CX88) += cx88/
 obj-$(CONFIG_VIDEO_EM28XX) += em28xx/
+obj-$(CONFIG_VIDEO_EMPIA) += empia/
 obj-$(CONFIG_VIDEO_USBVISION) += usbvision/
 obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o
 obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/
diff --git a/drivers/media/video/empia/Kconfig b/drivers/media/video/empia/Kconfig
new file mode 100644
index 0000000..6bd0ac3
--- /dev/null
+++ b/drivers/media/video/empia/Kconfig
@@ -0,0 +1,61 @@
+config VIDEO_EMPIA
+	tristate "Empia EM28xx USB video capture support"
+	depends on VIDEO_DEV && I2C
+	select VIDEO_BUF
+	select VIDEO_TUNER
+	select VIDEO_SAA711X if VIDEO_HELPER_CHIPS_AUTO
+	select VIDEO_TVP5150 if VIDEO_HELPER_CHIPS_AUTO
+	---help---
+	  This em28xx driver has been worked on since 2006 and constantly
+	  improved to support the latest available devices including
+	  different TV standards as well as VBI.
+
+	  the difference between this and the other em28xx driver is that
+	  it's well tested and supports more devices.
+
+config VIDEO_EMPIA_DTV
+	tristate "Empia EM288x/EM275x DVB-T support"
+	depends on VIDEO_EMPIA && DVB_CORE
+	select FW_LOADER
+	select DVB_TUNER_MT2060
+	select DVB_TUNER_QT1010
+	select DVB_LGDT3304
+	select DVB_MT352 if !DVB_FE_CUSTOMISE
+	select DVB_ZL10353 if !DVB_FE_CUSTOMISE
+	select DVB_LGDT330X if !DVB_FE_CUSTOMISE
+	select DVB_LGDT3304 if !DVB_FE_CUSTOMISE
+	---help---
+	  Support for Empia em288x/em2870 DVB-T/ATSC and ISDB-T extension
+
+	  If unsure say N.
+
+config VIDEO_EMPIA_AUDIO
+	tristate "Empia EM28xx USB Audio support"
+	depends on VIDEO_EMPIA
+	---help---
+	  This is an audio driver for Em28x[1/2] based usb video devices,
+	  this will be used by Hauppauge HVR 900 for analogue TV
+	  instead of snd-usb-audio.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called em28xx
+
+config VIDEO_EMPIA_AUDIOEP
+	tristate "Empia EM28xx USB Audio support (second configuration)"
+	depends on VIDEO_EMPIA
+	---help---
+	  This is another audiodriver for em28xx based devices which have
+	  a vendor specific audio interface on interface 1.
+
+	  choose yes if unsure, the driver will only attach if your
+	  Empia device has such a configuration
+
+config VIDEO_EMPIA_AAD_AUDIO
+	tristate "Empia EM28xx USB Audio support (not alsa compatible)"
+	depends on VIDEO_EMPIA
+	---help---
+	  This driver adds vendor specific audio support to em28xx based devices
+	  It can be used as an alternative if alsa is not available or enabled
+	  in the kernel (eg. to support audio with OSS).
+
+	  choose no if unsure
diff --git a/drivers/media/video/empia/Makefile b/drivers/media/video/empia/Makefile
new file mode 100644
index 0000000..9ee2376
--- /dev/null
+++ b/drivers/media/video/empia/Makefile
@@ -0,0 +1,19 @@
+em28xx-objs     := em28xx-video.o em28xx-i2c.o em28xx-cards.o em28xx-core.o \
+		   em28xx-input.o em28xx-webcam.o em28xx-keymaps.o
+
+em28xx-dvb-objs   := em2880-dvb.o 
+
+obj-$(CONFIG_VIDEO_EMPIA) += em28xx.o
+obj-$(CONFIG_VIDEO_EMPIA_AUDIO) += em28xx-audio.o
+obj-$(CONFIG_VIDEO_EMPIA_AUDIOEP) += em28xx-audioep.o
+obj-$(CONFIG_VIDEO_EMPIA_AAD_AUDIO) += em28xx-aad.o
+obj-$(CONFIG_VIDEO_EMPIA_DTV) += em28xx-dvb.o
+obj-$(CONFIG_VIDEO_EMPIA) += cx25843/
+obj-$(CONFIG_VIDEO_EMPIA_DTV) += lgdt3304/
+obj-$(CONFIG_VIDEO_EMPIA) += xc3028/
+obj-$(CONFIG_VIDEO_EMPIA) += xc5000/
+
+EXTRA_CFLAGS += -Idrivers/media/video
+EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core
+EXTRA_CFLAGS += -Idrivers/media/dvb/frontends
+EXTRA_CFLAGS += -Idrivers/media/common/tuners
diff --git a/drivers/media/video/empia/em2880-dvb.c b/drivers/media/video/empia/em2880-dvb.c
new file mode 100644
index 0000000..bd38018
--- /dev/null
+++ b/drivers/media/video/empia/em2880-dvb.c
@@ -0,0 +1,1139 @@
+/*
+ *  Empiatech em2880 DVB-T extension
+ *
+ *  Copyright (C) 2006/2007/2008 Markus Rechberger <mrechberger@sundtek.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; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/suspend.h>
+#include <linux/dvb/frontend.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+
+#include "em28xx.h"
+
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#include "dvb_frontend.h"
+
+#include "zl10353.h"
+#include "mt352.h"
+#include "xc5000/xc5000_control.h"
+#ifdef MICRONAS_DRX3975D
+#include "drx3973d/drx3973d_demod.h"
+#endif
+#include "lgdt3304/lgdt3304.h"
+#include "qt1010.h"
+#include "mt2060.h"
+#ifdef ADIMTV102
+#include "adimtv102/adimtv102.h"
+#endif
+
+#include "lgdt330x.h"
+#include "sharp/s921_module.h"
+
+#define EM2880_DVB_NUM_PACKETS 64
+#define EM2880_URB_COUNT 32
+
+#if defined DVB_DEFINE_MOD_OPT_ADAPTER_NR
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+#endif
+
+
+#define xc3028_offset_8mhz 2750000;
+#define xc3028_offset_7mhz 2250000;
+#define xc3028_offset_6mhz 2750000;
+#define xc3028_offset_atsc 1750000;
+
+
+MODULE_DESCRIPTION("Empiatech em2880 DVB-T extension");
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_LICENSE("GPL");
+
+#ifdef MICRONAS_DRX3975D
+DRX3973DData_t DRX3973DData_g = {
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	DRX_UIO_MODE_DISABLE,
+	DRX_UIO_MODE_DISABLE,
+	{
+		DRX3973D_AGC_CTRL_AUTO,
+		0,
+		0,
+		0,
+		0,
+		0
+	},
+	{
+		DRX3973D_AGC_CTRL_AUTO,
+		0,
+		0,
+		0,
+		0,
+		0
+	},
+	DRX3973D_IFFILTER_SAW,
+	FALSE,
+	DRX_BANDWIDTH_8MHZ,
+	FALSE,
+	DRX3973D_APPENV_PORTABLE,
+	TRUE,
+	FALSE,
+#ifndef _CH_
+	{
+		"01234567890",
+		"01234567890"
+	},
+	{
+		{
+			DRX_MODULE_UNKNOWN,
+			(char *)(NULL),
+			0,
+			0,
+			0,
+			(char *)(NULL)
+		},
+		{
+			DRX_MODULE_UNKNOWN,
+			(char *)(NULL),
+			0,
+			0,
+			0,
+			(char *)(NULL)
+		}
+	},
+	{
+		{
+			(pDRXVersion_t)(NULL),
+			(pDRXVersionList_t)(NULL)
+		},
+		{
+			(pDRXVersion_t)(NULL),
+			(pDRXVersionList_t)(NULL)
+		}
+	},
+	DRX3973D_SPIN_UNKNOWN,
+	{
+	},
+	FALSE,
+
+#if (DRXD_TYPE_B)
+	DRX3973D_I2C_INIT_ASEL
+#endif
+
+
+
+#endif
+};
+
+/**
+ * \var DRX3973DDefaultAddr_g
+ * \brief Default I2C address and device identifier.
+ */
+I2CDeviceAddr_t DRX3973DDefaultAddr_g = {
+#define DRX3973D_DEF_I2C_ADDR (0xe0)>>1
+	DRX3973D_DEF_I2C_ADDR,     /* i2c address */
+#define DRX3973D_DEF_DEMOD_DEV_ID   (1)
+	DRX3973D_DEF_DEMOD_DEV_ID,  /* device id */
+	NULL       /* private data structure */
+};
+
+/**
+ * \var DRX3973DDefaultCommAttr_g
+ * \brief Default common attributes of a drx3973d demodulator instance.
+ */
+DRXCommonAttr_t DRX3973DDefaultCommAttr_g = {
+	(pu8_t)NULL,
+	0,
+	TRUE,
+	4560,
+	48000L,
+	12000L,
+	0L,
+	FALSE,
+	TRUE,
+	TRUE,
+	TRUE,
+	FALSE,
+	FALSE,
+	FALSE,
+	FALSE,
+	FALSE,
+	FALSE,
+	32000000UL,
+	FALSE,
+	(pDRXScanParam_t)(NULL),
+	0,
+	0,
+	FALSE,
+	0L,
+	0L,
+	100,
+	DRX_LOCKED,
+	FALSE,
+	DRX_POWER_DOWN,
+	1,
+	0L,
+	0L
+};
+
+#endif
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "em2880-dvb debug level (default off)");
+
+#define dprintk(lvl, fmt, args...) if (debug >= lvl) do {\
+	printk(fmt, ##args); } while (0)
+
+
+static int em2880_set_alternate(struct em2880_dvb *dvb_dev);
+
+/* ------------------------------------------------------------------ */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static void em2880_complete_irq(struct urb *urb, struct pt_regs *ptregs)
+{
+#else
+static void em2880_complete_irq(struct urb *urb)
+{
+#endif
+	struct em2880_dvb *dvb_dev = urb->context;
+	int i;
+	int status;
+
+	if (dvb_dev == NULL) {
+		dprintk(1, "em2880-dev.c: device not valid!\n");
+		return;
+	}
+
+	if (debug && urb->status < 0) {
+		printk(KERN_ERR"%s:%u:%s(): status = %i\n", __FILE__,
+				__LINE__, __func__, urb->status);
+	}
+
+	switch (urb->status) {
+		case -ESHUTDOWN:
+			return;
+			break;
+	}
+
+	if (urb->status == -ENOENT)
+		return;
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		if (urb->iso_frame_desc[i].status != 0) {
+			dprintk(1, "em2880-dvb.c: status != 0\n");
+			continue;
+		} else if (urb->iso_frame_desc[i].actual_length > 0)
+			dvb_dmx_swfilter(&dvb_dev->demux,
+					urb->transfer_buffer +
+					urb->iso_frame_desc[i].offset,
+					urb->iso_frame_desc[i]
+					.actual_length);
+	}
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status)
+		dprintk(1, "resubmitting urb failed!\n");
+}
+
+static void em2880_stop_stream(struct em2880_dvb *dvb_dev)
+{
+	int i;
+	int arg;
+	for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) {
+		if (dvb_dev->urb[i]) {
+			usb_kill_urb(dvb_dev->urb[i]);
+			if (dvb_dev->transfer_buffer[i]) {
+				usb_buffer_free(dvb_dev->udev,
+						(EM2880_DVB_NUM_PACKETS *
+						 dvb_dev->dtv_packetsize),
+						dvb_dev->transfer_buffer[i],
+						dvb_dev->urb[i]->transfer_dma);
+			}
+			usb_free_urb(dvb_dev->urb[i]);
+		}
+		dvb_dev->urb[i] = NULL;
+		dvb_dev->transfer_buffer[i] = NULL;
+	}
+	arg=EM28XX_REG_OFF;
+	dvb_dev->em28xx_dev->em28xx_gpio_control(dvb_dev->em28xx_dev, EM28XX_LED1_ON, &arg);
+
+}
+
+static int em2880_start_stream(struct em2880_dvb *dvb_dev)
+{
+	int i, errCode;
+	int arg;
+	const int sb_size = EM2880_DVB_NUM_PACKETS * dvb_dev->dtv_packetsize;
+
+	dprintk(1, "em2880-dvb.c: got start stream request %s\n", __func__);
+	arg=EM28XX_REG_ON;
+	dvb_dev->em28xx_dev->em28xx_gpio_control(dvb_dev->em28xx_dev, EM28XX_LED1_ON, &arg);
+
+	em2880_set_alternate(dvb_dev);
+
+	/* allocate urbs */
+
+	for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) {
+		struct urb *urb;
+		int j, k;
+		/* allocate transfer buffer */
+		urb = usb_alloc_urb(EM2880_DVB_NUM_PACKETS, GFP_KERNEL);
+		if (!urb) {
+			dprintk(1, "cannot alloc urb %i\n", i);
+			return -ENOMEM;
+		}
+		dvb_dev->transfer_buffer[i] =
+			usb_buffer_alloc(dvb_dev->udev, sb_size,
+					GFP_KERNEL, &urb->transfer_dma);
+
+		if (!dvb_dev->transfer_buffer[i]) {
+			dprintk(1, "unable to allocate %i bytes for transfer"
+				   "buffer %i\n", sb_size, i);
+
+			return -ENOMEM;
+		}
+		memset(dvb_dev->transfer_buffer[i], 0, sb_size);
+		urb->dev = dvb_dev->udev;
+		urb->context = dvb_dev;
+		urb->pipe = usb_rcvisocpipe(dvb_dev->udev, 0x84);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->interval = 1;
+		urb->transfer_buffer = dvb_dev->transfer_buffer[i];
+		urb->complete = em2880_complete_irq;
+		urb->number_of_packets = EM2880_DVB_NUM_PACKETS;
+		urb->transfer_buffer_length = sb_size;
+		for (j = k = 0; j < EM2880_DVB_NUM_PACKETS;
+				j++, k += dvb_dev->dtv_packetsize) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+				dvb_dev->dtv_packetsize;
+		}
+		dvb_dev->urb[i] = urb;
+	}
+
+	/* submit urbs */
+	for (i = 0; i < EM2880_DVB_NUM_BUFS; i++) {
+		errCode = usb_submit_urb(dvb_dev->urb[i], GFP_KERNEL);
+		if (errCode) {
+			dprintk(1, "submit of urb %i failed (error=%i)\n",
+					i,
+					errCode);
+			return errCode;
+		}
+	}
+	return 0;
+}
+
+static int em2880_set_alternate(struct em2880_dvb *dvb_dev)
+{
+	int errCode;
+	errCode = usb_set_interface(dvb_dev->udev, dvb_dev->em28xx_dev->usb_interface, 1);
+
+	return errCode;
+}
+
+static int em2880_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_demux *demux = dvbdmxfeed->demux;
+	struct em2880_dvb *dvb_dev = demux->priv;
+	dprintk(1, "em2880-dvb.c: got start feed request %s\n", __func__);
+
+	if (mutex_lock_interruptible(&dvb_dev->sem))
+		return -ERESTARTSYS;
+
+	if (dvb_dev->streaming == 0)
+		em2880_start_stream(dvb_dev);
+
+	dvb_dev->streaming++;
+	mutex_unlock(&dvb_dev->sem);
+	return 0;
+}
+
+static int em2880_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_demux *demux = dvbdmxfeed->demux;
+	struct em2880_dvb *dvb_dev = demux->priv;
+
+	dprintk(1, "em2880-dvb.c: got stop feed request %s\n", __func__);
+
+	if (mutex_lock_interruptible(&dvb_dev->sem))
+		return -ERESTARTSYS;
+	if (0 == --dvb_dev->streaming)
+		em2880_stop_stream(dvb_dev);
+	mutex_unlock(&dvb_dev->sem);
+	return 0;
+}
+
+static int em28xx_ts_bus_ctrl_int(struct em28xx *dev, int acquire)
+{
+	u8 gpio;
+	gpio = dev->em28xx_read_reg(dev, 04);
+	gpio &= ~((u8)0x3);
+
+	if (acquire == 1)
+		gpio |= 1;
+
+	return dev->em28xx_write_regs(dev, 0x04, &gpio, 1);
+}
+
+static int em28xx_ts_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	return dev->em28xx_acquire(dev, EM28XX_DVBT, acquire);
+}
+
+static struct zl10353_config em2880_zl10353_dev = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.parallel_ts = 1,
+	.if2 = 45600
+};
+
+struct bcode {
+	int reg;
+	char *txt;
+	int len;
+	int delay;
+};
+
+
+static int mt352_pinnacle_init(struct dvb_frontend *fe)
+{
+	int i;
+	struct bcode zlconf[]={
+			{0x1e,"\x8a\x2c",2,0},
+			{0x1e,"\x89\x38",2,0},
+			{0x1e,"\x50\x80",2,0},
+			{0x1e,"\x8e\x40",2,0},
+			{0x1e,"\x69\x00",2,0},
+			{0x1e,"\x6a\xff",2,0},
+			{0x1e,"\x6b\xff",2,0},
+			{0x1e,"\x6c\x00",2,0},
+			{0x1e,"\x6d\xff",2,0},
+			{0x1e,"\x6e\x00",2,0},
+			{0x1e,"\x6f\x40",2,0},
+			{0x1e,"\x70\x40",2,0},
+			{0x1e,"\x68\xa0",2,0},
+
+			{0x1e,"\x56\x31",2,0}, // set input frequency
+			{0x1e,"\x57\xb8",2,0},
+			{0x1e,"\x75\x33",2,0},
+
+			{0x1e,"\x7c\x00",2,0},
+			{0x1e,"\x7d\x4d",2,0},
+			{0x1e,"\xb5\x7a",2,0},
+			{0x1e,"\x51\x40",2,0},
+			{0x1e,"\x52\x80",2,0},
+			{0x1e,"\x53\x50",2,0},
+			{0x1e,"\x5d\x01",2,0},
+			{}
+	};
+	for(i=0;zlconf[i].txt;i++)
+		fe->ops.write(fe, zlconf[i].txt,zlconf[i].len);
+	return 0;
+}
+
+
+static struct mt352_config em2880_mt352_dev = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	//.parallel_ts = 1,
+	.if2 = 45600,
+	.demod_init = mt352_pinnacle_init,
+};
+
+
+static struct zl10353_config em2880_zl10353_pinnacle = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static struct zl10353_config em2880_kworld_355u_dev = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static struct lgdt330x_config em2880_lgdt3303_dev = {
+	.demod_address = 0x0e,
+	.demod_chip = LGDT3303
+};
+
+static struct mt2060_config em2870_mt2060_config = {
+	.i2c_address = 0x60
+};
+
+static struct qt1010_config em2870_qt1010_config = {
+	.i2c_address = 0x62
+};
+
+static int kworld355u_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
+{
+	struct zl10353_state {
+		struct i2c_adapter *i2c;
+		struct dvb_frontend frontend;
+
+		struct zl10353_config config;
+
+		enum fe_bandwidth bandwidth;
+	};
+	struct zl10353_state *state = fe->demodulator_priv;
+
+	struct em28xx *dev = state->i2c->algo_data;
+
+	return em28xx_ts_bus_ctrl_int(dev, enable);
+}
+
+static struct lgdt3304_config lgdt3304_atsc_config = {
+	.i2c_address = 0x0e
+};
+
+static int em28xx_set_params(struct dvb_frontend *fe,
+		struct dvb_frontend_parameters *params)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+	int i;
+	unsigned long frequency = params->frequency;
+	struct dvb_ofdm_parameters *op = &params->u.ofdm;
+
+	if (!dev->tuner)
+		return -EINVAL;
+
+	switch (fe->ops.info.type) {
+	case FE_OFDM:
+		if (dev->dvbnorm->bandwidth !=
+				op->bandwidth) {
+			for (i = 0; dev->board->dvbnorms[i].tv_mode || dev->board->dvbnorms[i].index; i++) {
+				if (dev->board->dvbnorms[i].bandwidth == op->bandwidth) {
+					switch (dev->tuner_type) {
+					case TUNER_XCEIVE_XC3028:
+					{
+						struct xc3028_init_cmd cmd;
+						dvbdev->bw_index = i;
+						cmd.new_tv_mode_ptr = dev->board->dvbnorms[i].tv_mode;
+						cmd.new_channel_map_ptr = dev->board->dvbnorms[i].channelmap;
+						dev->dvbnorm = &dev->board->dvbnorms[i];
+						if (dev->tuner->tuner_cmd)
+							dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+						break;
+					}
+					case TUNER_XCEIVE_XC5000:
+					{
+						struct xc_std_conf cmd;
+						dvbdev->bw_index = i;
+						cmd.index = dev->board->dvbnorms[i].index;
+						dev->dvbnorm = &dev->board->dvbnorms[i];
+						dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+						break;
+					}
+					}
+					break;
+				}
+			}
+		}
+
+		if (dev->tuner->set_frequency) {
+			switch (params->u.ofdm.bandwidth) {
+			case BANDWIDTH_AUTO:
+			case BANDWIDTH_8_MHZ:
+				frequency -= xc3028_offset_8mhz;
+				break;
+			case BANDWIDTH_7_MHZ:
+				frequency -= xc3028_offset_7mhz;
+				break;
+			case BANDWIDTH_6_MHZ:
+				frequency -= xc3028_offset_6mhz;
+				break;
+			default:
+				return -EINVAL;
+			}
+			dev->tuner->set_frequency(dev->tuner, frequency);
+			dev->dctl_freq = frequency;
+		}
+		break;
+	case FE_QAM:
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		{
+			struct xc3028_init_cmd cmd;
+			dvbdev->bw_index = 0;
+			cmd.new_tv_mode_ptr = dev->board->qamnorms[0].tv_mode;
+			cmd.new_channel_map_ptr = dev->board->qamnorms[0].channelmap;
+			dev->qamnorm = &dev->board->qamnorms[0];
+			if (dev->tuner->tuner_cmd)
+				dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+			break;
+		}
+		case TUNER_XCEIVE_XC5000:
+		{
+			struct xc_std_conf cmd;
+			dvbdev->bw_index = 0;
+			cmd.index = dev->board->atscnorms[0].index;
+			dev->atscnorm = &dev->board->atscnorms[0];
+			dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+			break;
+		}
+		}
+		frequency -= xc3028_offset_atsc;
+
+		dev->tuner->set_frequency(dev->tuner, frequency);
+		dev->dctl_freq = frequency;
+		break;
+	case FE_ATSC:
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		{
+			struct xc3028_init_cmd cmd;
+			dvbdev->bw_index = 0;
+			cmd.new_tv_mode_ptr = dev->board->atscnorms[0].tv_mode;
+			cmd.new_channel_map_ptr = dev->board->atscnorms[0].channelmap;
+			dev->atscnorm = &dev->board->atscnorms[0];
+			if (dev->tuner->tuner_cmd)
+				dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+			break;
+		}
+		case TUNER_XCEIVE_XC5000:
+		{
+			struct xc_std_conf cmd;
+			dvbdev->bw_index = 0;
+			cmd.index = dev->board->atscnorms[0].index;
+			dev->atscnorm = &dev->board->atscnorms[0];
+			dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+			break;
+		}
+		}
+
+		frequency -= xc3028_offset_atsc;
+		dev->tuner->set_frequency(dev->tuner, frequency);
+		dev->dctl_freq = frequency;
+		break;
+	case FE_QPSK:
+		printk(KERN_INFO"FE_QPSK currently not supported\n");
+		break;
+	}
+
+	return 0;
+}
+
+static int em28xx_get_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	*frequency = dev->dctl_freq;
+	return 0;
+}
+
+static int em28xx_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+	*bandwidth = (u32)dev->board->dvbnorms[dvbdev->bw_index].bandwidth;
+	return 0;
+}
+
+static int em28xx_dvb_init(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	int gpio_arg;
+
+	printk(KERN_INFO"em28xx_dvb_init\n");
+
+	if (dev->mode != V4L2_TUNER_DIGITAL_TV) {
+		printk(KERN_INFO"switching over from %d\n", dev->mode);
+		gpio_arg = EM28XX_REG_OFF;
+		dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+
+		gpio_arg = EM28XX_REG_ON;
+		dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL);
+		mdelay(100);
+	}
+	dev->mode = V4L2_TUNER_DIGITAL_TV;
+	msleep(100);
+	switch (dev->tuner_type) {
+	case TUNER_XCEIVE_XC3028:
+	{
+		struct xc3028_init_cmd cmd;
+		if (dev->dev_modes & EM28XX_DVBT) {
+			cmd.new_tv_mode_ptr = dev->dvbnorm->tv_mode;
+			cmd.new_channel_map_ptr = dev->dvbnorm->channelmap;
+		} else if (dev->dev_modes & EM28XX_ATSC) {
+			cmd.new_tv_mode_ptr = dev->atscnorm->tv_mode;
+			cmd.new_channel_map_ptr = dev->atscnorm->channelmap;
+		}
+			
+		if (dev->tuner && dev->tuner->tuner_cmd)
+			dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+		break;
+	}
+	case TUNER_XCEIVE_XC5000:
+	{
+		struct xc_std_conf cmd;
+		if (dev->dev_modes & EM28XX_DVBT) {
+			cmd.index = dev->dvbnorm->index;
+		} else if (dev->dev_modes & EM28XX_ATSC) {
+			cmd.index = dev->atscnorm->index;
+		}
+
+		printk(KERN_INFO"initializing: %d\n", dev->dvbnorm->index);
+		dev->tuner->tuner_cmd(dev->tuner, XC5000_INIT_TUNER, NULL);
+
+		if (dev->tuner && dev->tuner->tuner_cmd)
+			dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+	}
+	}
+
+	return 0;
+}
+
+static int em28xx_s921_init(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+	switch (dev->em_type) {
+	case EM2875:
+		dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1);
+		break;
+	default:
+		break;
+	}
+	if (dvbdev->init_override)
+		dvbdev->init_override(fe);
+
+	return 0;
+}
+
+static int em28xx_zl10353_init(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+	struct em2880_dvb *dvbdev = dev->dvb_dev;
+
+	int gpio_arg;
+
+//	if (dev->mode != V4L2_TUNER_DIGITAL_TV) {
+		dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1);
+		dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x77", 1);
+
+		gpio_arg = EM28XX_REG_OFF;
+		dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+		gpio_arg = EM28XX_REG_ON;
+		dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg);
+		dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL);
+		mdelay(100);
+//	}
+	dev->mode = V4L2_TUNER_DIGITAL_TV;
+	msleep(100);
+
+	if (dvbdev->init_override)
+		dvbdev->init_override(fe);
+
+	switch (dev->em_type) {
+	case EM2888:
+		dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1);
+		/* TODO move this to zl10353, though keep backward compatibility */
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x50\x0b", 2);
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x51\x64", 2);
+		break;
+	case EM2875:
+		dev->em28xx_write_regs_req(dev, 0x00, 0x5f, "\x01", 1);
+		break;
+	default:
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x50\x0b", 2);
+		dev->em28xx_write_regs_req(dev, 0x02, 0x1e, "\x51\x44", 2);
+		break;
+	}
+	return 0;
+}
+
+
+static int em28xx_zl10353_sleep(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+//	int gpio_arg = EM28XX_REG_OFF;
+	if (dev->mode == V4L2_TUNER_DIGITAL_TV && dev->powersaving) {
+//		dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+		printk(KERN_INFO"powered down zl10353\n");
+	}
+	return 0;
+}
+
+
+static int em28xx_dvb_sleep(struct dvb_frontend *fe)
+{
+	struct em28xx *dev = fe->dvb->priv;
+#if 0
+	int gpio_arg;
+#endif
+	if (dev->mode == V4L2_TUNER_DIGITAL_TV && dev->powersaving) {
+//		dev->tuner->shutdown(dev->tuner);
+#if 0
+		gpio_arg = EM28XX_REG_OFF;
+		dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+		printk(KERN_INFO"powered down xc3028\n");
+#endif
+	}
+	return 0;
+}
+
+#ifdef ADIMTV102
+struct adimtv102_config dmbt_adim_config = {
+	.i2c_address = 0xc2>>1
+};
+
+#endif
+
+struct s921_config sharp_isdbt_config = {
+	.i2c_address = 0x30>>1
+};
+
+static int em2880_dvb_init(struct em28xx *dev)
+{
+	struct em2880_dvb *dvb_dev;
+	int err;
+	u16 if1 = 1220;
+	int gpio_arg;
+	int i;
+	struct dvb_demux *dvbdemux;
+
+	printk(KERN_INFO"em2880-dvb.c: DVB Init\n");
+	if (NULL == dev) {
+		dprintk(1, "em2880-dvb.c: no device attached?\n");
+		return -ENOMEM;
+	}
+
+	dvb_dev = kzalloc(sizeof(struct em2880_dvb), GFP_KERNEL);
+	if (!dvb_dev) {
+		dprintk(1, "out of memory!\n");
+		return -ENOMEM;
+	}
+
+	mutex_init(&dvb_dev->sem);
+
+	dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1);
+	dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x77", 1);
+
+
+	if (dev->dvbnorm == NULL) {
+		kfree(dvb_dev);
+		return -EINVAL;
+	}
+
+	gpio_arg = EM28XX_REG_OFF;
+	dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+	gpio_arg = EM28XX_REG_ON;
+	dev->em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+	dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+	dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg);
+	dev->em28xx_gpio_control(dev, EM28XX_DEMOD1_RESET, NULL);
+	mdelay(100);
+	dev->mode = V4L2_TUNER_DIGITAL_TV;
+
+
+	switch (dev->model) {
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+	case EM2882_BOARD_PINNACLE_HYBRID_PRO:
+#ifdef MICRONAS_DRX3975D
+		dvb_dev->frontend = dvb_attach(drx3973d_attach,
+				&DRX3973DDefaultCommAttr_g,
+				&DRX3973DDefaultAddr_g,
+				&DRX3973DData_g,
+				&dev->i2c_adap);
+#endif
+		break;
+	case EM2883_BOARD_TERRATEC_HYBRID_XS_FM:
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2882_BOARD_TERRATEC_HYBRID_XS:
+	case EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H:	
+	case EM2870_BOARD_TERRATEC_XS:
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+	case EM2880_BOARD_MSI_DIGIVOX_AD:
+	case EM2880_BOARD_MSI_DIGIVOX_AD_II:
+	case EM2870_BOARD_KWORLD_350U:
+	case EM2883_BOARD_KWORLD_HYBRID_E323:
+	case EM2888_BOARD_KWORLD_HYBRID_E329:
+	case EM2888_BOARD_EMPIA_HYBRID:
+	case EM2880_BOARD_TERRATEC_PRODIGY_XS:
+		dvb_dev->frontend = dvb_attach(zl10353_attach,
+				&em2880_zl10353_dev, &dev->i2c_adap);
+		if (dvb_dev->frontend == NULL)
+		dvb_dev->frontend = dvb_attach(mt352_attach,
+				&em2880_mt352_dev, &dev->i2c_adap);
+ 		
+		break;
+	case EM2870_BOARD_TERRATEC_XS_MT2060:
+		dvb_dev->frontend = dvb_attach(zl10353_attach,
+				&em2880_zl10353_pinnacle, &dev->i2c_adap);
+		break;
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+	case EM2883_BOARD_PINNACLE_PCTV_HD_PRO:
+	case EM2883_BOARD_KWORLD_HYBRID_A316:
+	case EM2883_BOARD_ATI_TVWONDER600:
+		dvb_dev->frontend = dvb_attach(lgdt330x_attach,
+				&em2880_lgdt3303_dev, &dev->i2c_adap);
+		break;
+	case EM2870_BOARD_KWORLD_355U:
+	case EM2870_BOARD_COMPRO_VIDEOMATE:
+		em28xx_ts_bus_ctrl_int(dev, 0);
+		/* fall through */
+	case EM2870_BOARD_PINNACLE_PCTV_DVB:
+		dvb_dev->frontend = dvb_attach(zl10353_attach,
+				&em2880_kworld_355u_dev,
+				&dev->i2c_adap);
+		break;
+	case EM2883_BOARD_EMPIA_HYBRID_ATSC:
+	case EM2883_BOARD_EQUINUX_TUBESTICK_ATSC:
+		dvb_dev->frontend = dvb_attach(lgdt3304_attach,
+				&lgdt3304_atsc_config, &dev->i2c_adap);
+		break;
+	case EM2875_BOARD_SAMPLE_ISDBT:
+		
+#if 1
+		dvb_dev->frontend = dvb_attach(s921_attach,
+				&sharp_isdbt_config, &dev->i2c_adap);
+#endif
+		if (dvb_dev->frontend) {
+			dvb_dev->frontend->tuner_priv = dev;
+			dvb_dev->init_override = dvb_dev->frontend->ops.init;
+			dvb_dev->frontend->ops.init = em28xx_s921_init;
+		} else {
+			printk("failed to initialize s921\n");
+		}
+		break;
+	case EM2879_BOARD_SAMPLE_DMB:
+#if 0
+		dvb_dev->frontend = dvb_attach(dmb_attach,
+				&sharp_dmb_config, &dev->i2c_adap);
+#endif
+		break;
+	default:
+		printk(KERN_INFO"em2880-dvb.c: unsupported device\n");
+		kfree(dvb_dev);
+		return -EINVAL;
+	}
+
+	if (dvb_dev->frontend && dev->model != EM2875_BOARD_SAMPLE_ISDBT) {
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		case TUNER_XCEIVE_XC5000:
+			dvb_dev->frontend->tuner_priv = dev;
+			dvb_dev->frontend->ops.tuner_ops.set_params =
+				em28xx_set_params;
+			dvb_dev->frontend->ops.tuner_ops.get_frequency =
+				em28xx_get_frequency;
+			dvb_dev->frontend->ops.tuner_ops.get_bandwidth =
+				em28xx_get_bandwidth;
+
+			/* xc3028 powersaving mode */
+			dvb_dev->frontend->ops.tuner_ops.init =
+				em28xx_dvb_init;
+			dvb_dev->frontend->ops.tuner_ops.sleep =
+				em28xx_dvb_sleep;
+			break;
+		case TUNER_MT2060:
+			dvb_attach(mt2060_attach, dvb_dev->frontend,
+					&dev->i2c_adap, &em2870_mt2060_config,
+					if1);
+			break;
+		case TUNER_QT1010:
+			dvb_dev->frontend->ops.i2c_gate_ctrl =
+				kworld355u_i2c_gate_ctrl;
+			dvb_attach(qt1010_attach, dvb_dev->frontend,
+					&dev->i2c_adap, &em2870_qt1010_config);
+			break;
+		case TUNER_ADIMTV102:
+#ifdef ADIMTV102
+			dvb_attach(adimtv102_attach, dvb_dev->frontend, &dev->i2c_adap, &dmbt_adim_config);
+#endif
+			break;
+		default:
+			printk(KERN_INFO"unsupported tuner (%d)\n",
+					dev->tuner_type);
+		}
+
+		/* zl10353 powersaving */
+		dvb_dev->init_override = dvb_dev->frontend->ops.init;
+
+		dvb_dev->frontend->ops.sleep = em28xx_zl10353_sleep;
+		dvb_dev->frontend->ops.init = em28xx_zl10353_init;
+	}
+
+	if (NULL == dvb_dev->frontend) {
+		printk(KERN_INFO"em2880-dvb.c: failed initializing zl10353"
+		       "DVB-T demodulator\n");
+		printk(KERN_INFO"em2880-dvb.c: retrying with mt352 DVB-T"
+				"demodulator\n");
+		if (NULL == dvb_dev->frontend) {
+			printk(KERN_INFO"em2880-dvb.c: no luck with mt352"
+			       "demodulator, not attaching em2880-dvb\n");
+			kfree(dvb_dev);
+			return -ENODEV;
+		}
+	}
+
+	/* this is not backward compatible */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
+	dvb_dev->frontend->ops.ts_bus_ctrl = em28xx_ts_bus_ctrl;
+#endif
+
+	dvb_register_adapter(&dvb_dev->adapter, "em2880 DVB-T", THIS_MODULE,
+			&dev->udev->dev
+#if defined DVB_DEFINE_MOD_OPT_ADAPTER_NR
+			,adapter_nr
+#endif
+	);
+
+	
+	for (i=0; i<dev->uif->altsetting[1].desc.bNumEndpoints; i++) {
+		if (dev->uif->altsetting[1].endpoint[i].desc.bEndpointAddress == 0x84) {
+			dvb_dev->dtv_packetsize = dev->uif->altsetting[1].
+					endpoint[i].desc.wMaxPacketSize;
+			break;
+		}
+	}
+
+	dvb_dev->adapter.priv = dev;
+
+	dvb_register_frontend(&dvb_dev->adapter, dvb_dev->frontend);
+
+	dvb_dev->demux.priv = dvb_dev;
+	dvb_dev->demux.filternum = 256;
+	dvb_dev->demux.feednum = 256;
+	dvb_dev->demux.start_feed = em2880_start_feed;
+	dvb_dev->demux.stop_feed = em2880_stop_feed;
+	dvb_dev->demux.dmx.capabilities = DMX_TS_FILTERING |
+					DMX_SECTION_FILTERING |
+					DMX_MEMORY_BASED_FILTERING;
+
+	err = dvb_dmx_init(&dvb_dev->demux);
+	if (err < 0) {
+		dprintk(1, "dvb_dmx_init failed!\n");
+		kfree(dvb_dev);
+		return -1;
+	}
+
+	dvb_dev->dmxdev.filternum = dvb_dev->demux.filternum;
+	dvb_dev->dmxdev.demux = &dvb_dev->demux.dmx;
+	dvb_dev->dmxdev.capabilities = 0;
+
+	err = dvb_dmxdev_init(&dvb_dev->dmxdev, &dvb_dev->adapter);
+
+	if (err < 0) {
+		dprintk(1, "dvb_dmxdev failed!\n");
+		dvb_dmxdev_release(&dvb_dev->dmxdev);
+		kfree(dvb_dev);
+		return -1;
+	}
+
+	dvb_dev->udev = dev->udev;
+	dvb_dev->em28xx_dev = dev; /* FIXME get rid of this */
+
+	dev->dvb_dev = dvb_dev;
+
+	dvbdemux = &dvb_dev->demux;
+	dvb_net_init(&dvb_dev->adapter, &dvb_dev->dvbnet, &dvbdemux->dmx);
+
+	return 0;
+}
+
+static int em2880_dvb_fini(struct em28xx *dev)
+{
+	struct em2880_dvb *dvb_dev = dev->dvb_dev;
+	if (!dev) {
+		dprintk(1, "fini already called!\n");
+		return -1;
+	}
+	if (!dev->dvb_dev) {
+		dprintk(1, "dvb_dev not initialized!\n");
+		return -1;
+	}
+	dvb_dev = dev->dvb_dev;
+
+	dprintk(1, "releasing dvb device!\n");
+
+	dvb_net_release(&dvb_dev->dvbnet);
+	dvb_unregister_frontend(dvb_dev->frontend);
+	dvb_frontend_detach(dvb_dev->frontend);
+
+	dvb_dev->demux.dmx.close(&dvb_dev->demux.dmx);
+	dvb_dmxdev_release(&dvb_dev->dmxdev);
+	dvb_dmx_release(&dvb_dev->demux);
+
+	dvb_unregister_adapter(&dvb_dev->adapter);
+
+	dprintk(1, "dvb_fini\n");
+	if (dev->dvb_dev) {
+		dprintk(1, "freeing dvb_dev\n");
+		kfree(dev->dvb_dev);
+		dev->dvb_dev = NULL;
+	}
+	return 0;
+}
+
+static struct em28xx_ops dvb_ops = {
+	.id	= EM28XX_DVBT | EM28XX_ATSC | EM28XX_ISDB | EM28XX_DMB,
+	.name	= "Em2880 DVB Extension",
+	.init	= em2880_dvb_init,
+	.fini	= em2880_dvb_fini,
+};
+
+static int __init em2880_dvb_register(void)
+{
+	return em28xx_register_extension(&dvb_ops);
+}
+
+static void __exit em2880_dvb_unregister(void)
+{
+	em28xx_unregister_extension(&dvb_ops);
+}
+
+module_init(em2880_dvb_register);
+module_exit(em2880_dvb_unregister);
+
+/* ------------------------------------------------------------------ */
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/empia/em28xx-aad.c b/drivers/media/video/empia/em28xx-aad.c
new file mode 100644
index 0000000..7c6dcb6
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-aad.c
@@ -0,0 +1,404 @@
+/* 
+ * Em28xx-aad Empia Alternative Audio Driver
+ *
+ * This is a vendor specific driver which provides a vendor specific
+ * audio interface for Empia based devices.
+ *
+ * Copyright (C) 2008 Empia Technology Inc.
+ * Copyright (C) 2008 Sundtek Ltd.
+ *
+ */
+
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/wait.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <asm/uaccess.h>
+
+#include "em28xx-aad.h"
+#include "em28xx.h"
+
+static unsigned long em28xx_aad_devices; /* map */
+static int em28xx_aad_register(struct em28xx *dev);
+
+void em28xx_aad_unregister(struct em28xx_aad_info **int_aad);
+
+static int em28xx_maxdevs;
+
+static LIST_HEAD(em28xx_aad_devlist);
+
+/* ringbuffer implementation, taken from empia radio application */
+
+#define AAD_BUFFER_SIZE 20000
+
+static struct aad_rb_info *aad_rb_init(void);
+static struct aad_rb_info *aad_rb_init(void) {
+	struct aad_rb_info *rb = kzalloc(sizeof(struct aad_rb_info), GFP_KERNEL);
+        rb->left = rb->right = rb->pos = 0;
+	rb->bufsize = AAD_BUFFER_SIZE;
+        rb->buffer = kzalloc(AAD_BUFFER_SIZE*sizeof(u8), GFP_KERNEL);
+        if (rb->buffer == NULL)
+            return 0;
+	spin_lock_init(&rb->__aad_lock);
+	return rb;
+};
+
+void aad_rb_free(struct aad_rb_info **rb) {
+	kfree((*rb)->buffer);
+	kfree((*rb));
+}
+
+static inline u32 aad_rb_get_buffer_size(struct aad_rb_info *rb) {
+	if (rb->left > rb->right)
+		return (rb->bufsize - rb->left + rb->right);
+	else if(rb->left < rb->right)
+		return (rb->right - rb->left);
+	else if(rb->left == rb->right)
+		return 0;
+	return 0;
+}
+
+static void aad_rb_reset_buffer(struct aad_rb_info *rb) {
+        unsigned long flags;
+        spin_lock_irqsave(&rb->__aad_lock, flags);
+        rb->left = rb->right = rb->pos = 0;
+        spin_unlock_irqrestore(&rb->__aad_lock, flags);
+}
+
+static inline u32 aad_rb_free_buffer_size(struct aad_rb_info *rb) {
+	u32 cur_buffsize = aad_rb_get_buffer_size(rb);
+	if (rb->bufsize - cur_buffsize > 0)
+		return rb->bufsize - cur_buffsize - 1;
+	return rb->bufsize - 1;
+}
+
+static int aad_rb_write_data(struct aad_rb_info *rb, u8 *data, ssize_t size) {
+	int len = size;
+	//int len2;
+	int free;
+	unsigned long flags;
+	/* lock */
+        spin_lock_irqsave(&rb->__aad_lock, flags);
+	free = aad_rb_free_buffer_size(rb);
+
+	if (size > free)
+		size = free - free % sizeof(u32); /* alignment */
+
+	if (free == 0) {
+                if(printk_ratelimit()) 
+                    printk("dropping data!\n");
+		/* unlock */
+                spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		return 0;
+	}
+	if (rb->right + size >= rb->bufsize - 1) {
+		len = rb->bufsize - rb->right;
+		memcpy(&rb->buffer[rb->right], data, len);
+		memcpy(rb->buffer, data+len, size - len);
+		rb->right = size - len;
+	} else {
+		memcpy(&rb->buffer[rb->right], data, size);
+		rb->right += size;
+	}
+	/* unlock */
+        spin_unlock_irqrestore(&rb->__aad_lock, flags);
+	return 0;
+}
+
+/* this is a bit complicated since we cannot do memcpy/copy_to_user within the
+ * spinlocked block, it might work fine with most systems although the target
+ * systems just blew up */
+
+static int aad_rb_read_data(struct em28xx_aad_info *aad, u8 *buffer, ssize_t size) {
+	int len;
+	int bufsize;
+	unsigned int left;
+        unsigned long flags;
+        struct aad_rb_info *rb = aad->__aad_rb[aad->__curr_rb];
+	/* lock */
+        spin_lock_irqsave(&rb->__aad_lock, flags);
+
+
+	if (aad_rb_get_buffer_size(rb) == 0) {
+		/* unlock */
+                spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		return 0;
+	}
+
+	bufsize = aad_rb_get_buffer_size(rb);
+	if (size > bufsize)
+		size = bufsize;
+
+	size -= size % 8; /* alignment */
+
+        aad->__curr_rb ^= 1; /* switch rb buffer so the irq can write to the other buffer during
+                                the copy routine */
+
+        aad->__write_rb = aad->__aad_rb[aad->__curr_rb];
+
+	if (rb->left + size >= rb->bufsize -1) {
+		len = rb->bufsize - rb->left;
+		left = rb->left;
+		rb->left = size - len;
+        	spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		copy_to_user(buffer, &rb->buffer[left], len);
+		copy_to_user(&buffer[len], rb->buffer, size - len);
+	} else {
+		left = rb->left;
+		rb->left += size;
+        	spin_unlock_irqrestore(&rb->__aad_lock, flags);
+		copy_to_user(buffer, &rb->buffer[left], size);
+	}
+
+        /* reset our backed up ringbuffer */
+	
+        aad_rb_reset_buffer(rb);
+	/* unlock */
+	return size;
+}
+
+/* end ringbuffer implementation */
+
+/* usb code */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static void em28xx_aad_isocirq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_aad_isocirq(struct urb *urb)
+#endif
+{
+    struct em28xx_aad_info *aad = urb->context;
+    u8 *cp;
+    int i;
+    for(i=0;i<urb->number_of_packets;i++){
+        int length=urb->iso_frame_desc[i].actual_length;
+        cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+        if(!length)
+            continue;
+
+
+        if (urb->iso_frame_desc[i].status)
+            continue;
+
+        aad_rb_write_data(aad->__write_rb, cp, length);
+
+    }
+    urb->status = 0;
+
+    if(usb_submit_urb(urb, GFP_ATOMIC))
+        printk("unable to submit urb\n");
+}
+
+static int aad_audio_isoc_deinit(struct em28xx_aad_info *aad)
+{
+    int i;
+    for(i=0;i<AAD_AUDIO_BUFS;i++){
+        usb_kill_urb(aad->urb[i]);
+        usb_free_urb(aad->urb[i]);
+        aad->urb[i]=NULL;
+    }
+    return 0;
+}
+
+static int aad_audio_isoc_init(struct em28xx_aad_info *aad)
+{
+    int i;
+    int errCode;
+    const int sb_size=AAD_NUM_AUDIO_PACKETS * AAD_AUDIO_MAX_PACKET_SIZE;
+
+    printk("aad_isoc audio init\n");
+
+    for(i=0;i<AAD_AUDIO_BUFS;i++){
+        struct urb *urb;
+        int j,k;
+        aad->transfer_buffer[i]=kmalloc(sb_size,GFP_ATOMIC);
+
+        if(!aad->transfer_buffer[i])
+            return -ENOMEM;
+
+        memset(aad->transfer_buffer[i],0x80,sb_size);
+        urb = usb_alloc_urb(AAD_NUM_AUDIO_PACKETS,GFP_ATOMIC);
+
+        if(urb){
+            urb->dev=aad->__aad_udev;
+            urb->context=aad;
+            urb->pipe=usb_rcvisocpipe(aad->__aad_udev, 0x83);
+            urb->transfer_flags = URB_ISO_ASAP;
+            urb->transfer_buffer = aad->transfer_buffer[i];
+            urb->interval=1;
+            urb->complete = em28xx_aad_isocirq;
+            urb->number_of_packets = AAD_NUM_AUDIO_PACKETS;
+            urb->transfer_buffer_length = sb_size;
+            for(j=k=0; j<AAD_NUM_AUDIO_PACKETS;j++,k+=AAD_AUDIO_MAX_PACKET_SIZE){
+                urb->iso_frame_desc[j].offset = k;
+                urb->iso_frame_desc[j].length=AAD_AUDIO_MAX_PACKET_SIZE;
+            }
+            aad->urb[i]=urb;
+        } else
+            return -ENOMEM;
+
+    }
+    for(i=0;i<AAD_AUDIO_BUFS;i++){
+        errCode = usb_submit_urb(aad->urb[i], GFP_ATOMIC);
+        if (errCode){
+            aad_audio_isoc_deinit(aad);
+            return errCode;
+        }
+    }
+    return 0;
+}
+
+/* cheap usercheck */
+static int aad_users;
+
+static int aad_open(struct inode *inode, struct file *file) {
+	struct em28xx_aad_info *aad = NULL;
+	struct list_head *list;
+
+	if (aad_users != 0) 
+		return -EBUSY;
+	aad_users++;
+        /* TODO: support multiple devices here */
+	list_for_each(list, &em28xx_aad_devlist) {
+		aad = list_entry(list, struct em28xx_aad_info, __aad_devlist);
+		break;
+	}
+
+	if (aad == NULL)
+		return -EINVAL;
+
+	printk("found device entry!\n");
+
+	file->private_data = aad;
+
+        aad_rb_reset_buffer(aad->__write_rb);
+        aad->__aad_callback(aad->__cb_priv, EM28XX_ENABLE_AUDIO, NULL);
+        aad_audio_isoc_init(aad);
+	return 0;
+}
+
+static ssize_t aad_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
+	struct em28xx_aad_info *aad = NULL;
+	ssize_t retval;
+	aad = filp->private_data;
+	retval = aad_rb_read_data(aad, buf, count);
+	return retval;
+}
+
+static int aad_release(struct inode *inode, struct file *file) {
+        struct em28xx_aad_info *aad;
+        aad = file->private_data;
+	aad_users--;
+        aad_audio_isoc_deinit(aad);
+	return 0;
+}
+
+static struct file_operations em28xx_aad_fops = {
+        .owner   = THIS_MODULE,
+        .open    = aad_open,
+        .read    = aad_read,
+        .release = aad_release,
+};
+
+static int em28xx_aad_register(struct em28xx *dev) {
+	struct em28xx_aad_info *aad;
+	int id;
+	aad = kzalloc(sizeof(struct em28xx_aad_info), GFP_KERNEL);
+	id = find_first_zero_bit(&em28xx_aad_devices, sizeof(u32));
+	em28xx_aad_devices |= (1<<id);
+	aad->__id = id;
+        aad->__aad_udev = dev->udev;
+        aad->__aad_callback = dev->em28xx_aad_control;
+        aad->__cb_priv = dev;
+        dev->aad = aad;
+	snprintf(aad->__aad_devname, 50, "aad%d", id);
+	printk("em28xx-aad: registered /dev/%s\n",aad->__aad_devname);
+
+	aad->__aad_major = register_chrdev(0, aad->__aad_devname, &em28xx_aad_fops);
+	aad->__aad_class = class_create(THIS_MODULE, aad->__aad_devname);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
+	aad->__aad_cdev  = class_device_create(aad->__aad_class, NULL, MKDEV(aad->__aad_major, 0), NULL, aad->__aad_devname);
+#else
+/* XXX now in 2.6.27 device_create == device_create_drvdata ... */
+/* dvb-core/dvbdev.c uses d_c_drvdata already, imitate that since I don't
+ * know any better...  XXX  */
+        aad->__aad_cdev  = device_create_drvdata(aad->__aad_class, NULL,
+                MKDEV(aad->__aad_major, 0), NULL, aad->__aad_devname);
+#endif
+
+	INIT_LIST_HEAD(&aad->__aad_devlist);
+	list_add_tail(&aad->__aad_devlist, &em28xx_aad_devlist);
+
+        /* we cannot copy out from the ringpuffer while it is spinlocked with 2.6.17 */
+	aad->__aad_rb[0] = aad_rb_init();
+	aad->__aad_rb[1] = aad_rb_init();
+        aad->__write_rb = aad->__aad_rb[0];
+        aad->__curr_rb = 0;
+	return 0;
+}
+
+void em28xx_aad_unregister(struct em28xx_aad_info **aad_int) {
+	struct em28xx_aad_info *aad = (*aad_int);
+	em28xx_aad_devices &= ~ (1<<aad->__id);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
+	class_device_destroy(aad->__aad_class, MKDEV(aad->__aad_major, 0));
+#else
+        device_destroy(aad->__aad_class, MKDEV(aad->__aad_major, 0));
+#endif
+	unregister_chrdev(0, aad->__aad_devname);
+	class_destroy(aad->__aad_class);
+	aad_rb_free(&aad->__aad_rb[0]);
+	aad_rb_free(&aad->__aad_rb[1]);
+        list_del(&aad->__aad_devlist);
+
+	kfree(aad);
+}
+
+static int em28xx_aad_fini(struct em28xx *dev) {
+    em28xx_aad_unregister(&dev->aad);
+    dev->aad = NULL;
+    return 0;
+}
+
+static struct em28xx_ops audio_ops = {
+	.id	= EM28XX_AUDIO,
+	.name	= "Em28xx Alternative Audio Driver",
+	.init	= em28xx_aad_register,
+	.fini	= em28xx_aad_fini,
+};
+
+static int __init  em28xx_aad_init(void) {
+	printk("initializing Empia Audio Driver\n");
+	printk("Copyright (C) 2008 Empia Technology Inc\n");
+	printk("Copyright (C) 2008 Sundtek Ltd.\n");
+
+	em28xx_maxdevs = sizeof(int);
+	return em28xx_register_extension(&audio_ops);
+}
+
+static void __exit em28xx_aad_exit(void) {
+	printk("releasing Empia Audio Driver\n");
+	em28xx_unregister_extension(&audio_ops);
+}
+
+MODULE_AUTHOR("Empia Technology Inc./Sundtek Ltd.");
+MODULE_DESCRIPTION("Alternative Audio Driver for Empia based devices");
+MODULE_LICENSE("GPL");
+
+module_init(em28xx_aad_init);
+module_exit(em28xx_aad_exit);
diff --git a/drivers/media/video/empia/em28xx-aad.h b/drivers/media/video/empia/em28xx-aad.h
new file mode 100644
index 0000000..fd39ed1
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-aad.h
@@ -0,0 +1,46 @@
+#ifndef __EM28XX_AAD_H
+#define __EM28XX_AAD_H
+
+#include <linux/poll.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/compiler.h> /* need __user */
+
+#define MAX_AAD_DEVS 10
+
+struct aad_rb_info {
+    spinlock_t __aad_lock;
+    u32 left;
+    u32 right;
+    u32 pos;
+    u32 bufsize;
+    u8 *buffer;
+};
+
+#define AAD_AUDIO_BUFS 3
+#define AAD_NUM_AUDIO_PACKETS 64
+#define AAD_AUDIO_MAX_PACKET_SIZE 196 /* static value */
+
+struct em28xx_aad_info {
+    u32 __id;
+    u8 __aad_devname[50];
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
+    struct class_device *__aad_cdev;
+#else
+    struct device *__aad_cdev;
+#endif
+    int __aad_major;
+    struct class *__aad_class;
+    struct list_head __aad_devlist;
+    struct aad_rb_info *__aad_rb[2];
+    struct aad_rb_info *__write_rb;
+    u8 __curr_rb;
+    struct usb_device *__aad_udev;
+    u8 *transfer_buffer[AAD_AUDIO_BUFS];
+    struct urb *urb[AAD_AUDIO_BUFS];
+    int (*__aad_callback)(void *, unsigned int cmd, void *arg);
+    void *__cb_priv;
+};
+
+#endif
diff --git a/drivers/media/video/empia/em28xx-audio.c b/drivers/media/video/empia/em28xx-audio.c
new file mode 100644
index 0000000..a1d06a7
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-audio.c
@@ -0,0 +1,630 @@
+/*
+ *  Empiatech em28x1 audio extension
+ *
+ *  Copyright (C) 2006 Markus Rechberger <mrechberger@sundtek.de>
+ *
+ *  This driver is based on my previous au600 usb pstn audio driver
+ *  and inherits all the copyrights
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/init.h>
+#include <linux/sound.h>
+#include <linux/spinlock.h>
+#include <linux/soundcard.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/proc_fs.h>
+#include <linux/moduleparam.h>
+#include <linux/wait.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <media/v4l2-common.h>
+#include "em28xx.h"
+#include "xc5000/xc5000_control.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static int em28xx_cmd(struct em28xx *dev, int cmd,int arg);
+
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_pcm_alloc_vmalloc_buffer(snd_pcm_substream_t *subs, size_t size)
+#else
+static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size)
+#endif
+{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_runtime_t *runtime = subs->runtime;
+#else
+	struct snd_pcm_runtime *runtime = subs->runtime;
+#endif
+	if(runtime->dma_area){
+		if(runtime->dma_bytes > size)
+			return 0;
+		vfree(runtime->dma_area);
+	}
+	runtime->dma_area = vmalloc(size);
+	if(!runtime ->dma_area)
+		return -ENOMEM;
+	runtime->dma_bytes = size;
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_hardware_t snd_em28xx_hw_capture = {
+#else
+static struct snd_pcm_hardware snd_em28xx_hw_capture = {
+#endif
+	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 
+		SNDRV_PCM_INFO_MMAP | 
+		SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_INTERLEAVED  | 
+		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,
+	.buffer_bytes_max = 1024*1024, 
+	.period_bytes_min = 64, 
+	.period_bytes_max = 512*1024,
+	.periods_min = 2,
+	.periods_max = 1024,
+};
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_open(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_capture_open(struct snd_pcm_substream *substream)
+#endif
+{
+	int ret = 0;
+	int mode;
+	int arg;
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_runtime_t *runtime = substream->runtime;
+#else
+	struct snd_pcm_runtime *runtime = substream->runtime;
+#endif
+	printk("opening radio device and trying to acquire exclusive lock\n");
+        switch(dev->mode){
+	case V4L2_TUNER_DIGITAL_TV:
+		/* digital has no support for analog audio */
+		ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); 
+		if (ret != 0 ) {
+			printk("device is already in use by DVB-T\n");
+			return -EINVAL;
+		} else {
+			struct v4l2_tuner tuner;
+			struct v4l2_routing arouting;
+			printk("switching device to FM mode\n");
+
+			mode = V4L2_TUNER_RADIO;
+			memset(&tuner, 0x0, sizeof(struct v4l2_tuner));
+			tuner.type = V4L2_TUNER_RADIO;
+
+			dev->mode = mode;
+		
+			arg = EM28XX_REG_ON;
+			dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_LED1_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &arg);
+			arg = EM28XX_REG_OFF;
+			dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &arg);
+			mdelay(100);
+
+			/* upload firmware */
+			switch(dev->tuner_type) {
+			case TUNER_XCEIVE_XC3028:
+				{
+					struct xc3028_init_cmd cmd;
+
+					/* this is moreover to switch the decoder to FM */
+					cmd.new_tv_mode_ptr = dev->fmnorm->tv_mode;
+					cmd.new_channel_map_ptr = dev->fmnorm->channelmap;
+					if (dev->tuner)
+						dev->tuner->tuner_cmd(dev->tuner, XC3028_INIT_TUNER, &cmd);
+					break;
+				} 
+			case TUNER_XCEIVE_XC5000:
+				{
+					struct xc_std_conf cmd;
+					/* this is moreover to switch the decoder to FM */
+					if (dev->tuner) {
+						dev->tuner->tuner_cmd(dev->tuner, XC5000_INIT_TUNER, NULL);
+						cmd.index=dev->fmnorm->index;
+						dev->tuner->tuner_cmd(dev->tuner, XC5000_SET_MODE, &cmd);
+					}
+					break;
+				}
+			default:
+				break;
+			}
+
+			dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+
+			em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, 0);
+			
+			arouting.input = CX25843_RADIO;
+			em28xx_i2c_call_clients(dev, VIDIOC_INT_S_AUDIO_ROUTING, &arouting);
+
+			printk("retrieved mode from tuner: %d\n",mode);
+		}
+		break;
+
+	case V4L2_TUNER_ANALOG_TV:
+		printk("em28xx-audio: device is currently in analog TV mode\n");
+		/* unmute by default */
+		ret = dev->em28xx_acquire(dev, EM28XX_VIDEO, 1); 
+		if (ret!=0)
+			return -EBUSY;
+
+		dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+
+		break;
+	case V4L2_TUNER_RADIO:
+	{
+		struct v4l2_routing arouting;
+		/* check current mode and put a hard lock onto it */
+		printk("em28xx-audio: device is currently in analogue FM mode\n");
+		/* unmute by default here */
+		ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); 
+
+		dev->mode = V4L2_TUNER_RADIO;
+
+		if ( ret == 0 ) 
+			printk("device is locked in fmradio mode now\n");
+
+		dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+		arouting.input = CX25843_RADIO;
+		em28xx_i2c_call_clients(dev, VIDIOC_INT_S_AUDIO_ROUTING, &arouting);
+
+		break;
+	}
+	default:
+		printk("em28xx-audio: unhandled mode %d\n", dev->mode);
+		return -EINVAL;
+	}
+	runtime->hw = snd_em28xx_hw_capture;
+	if(dev->alt == 0 && dev->adev->users == 0 ) {
+		int errCode;
+		dev->alt = 7;
+		errCode = usb_set_interface(dev->udev, dev->usb_interface, 7);
+		printk("changing alternate number to 7\n");
+	}
+	dev->adev->users++;
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	dev->adev->capture_pcm_substream = substream;
+	runtime->private_data = dev;
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_pcm_close(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream)
+#endif
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	int amode = 0;
+	dev->adev->users--;
+
+	/* decrease audio reference */
+	switch(dev->mode) {
+		case V4L2_TUNER_ANALOG_TV:
+			amode = EM28XX_VIDEO;
+			break;
+		case V4L2_TUNER_RADIO:
+			amode = EM28XX_RADIO;
+			break;
+		default:
+			printk("invalid mode: %d\n",dev->mode);
+			return -EINVAL;
+	}
+
+	dev->em28xx_acquire(dev, amode, 0); 
+
+	if(dev->adev->users == 0 && dev->adev->shutdown == 1) {
+		dev->adev->shutdown = 0;
+		em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,0);
+	}
+	wake_up(&dev->adev->open);
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params)
+#else
+static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
+#endif
+{
+	unsigned int channels, rate, format;
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	int ret;
+	ret = snd_pcm_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params));
+	dev->adev->hwptr_done_capture = 0;
+
+	format = params_format(hw_params);
+	rate = params_rate(hw_params);
+	channels = params_channels(hw_params);
+	/* TODO: set up em28xx audio chip to deliver the correct audio format, 
+	   xc5000/cx25843 supports stereo
+	   xc3028/em202 supports mono only
+	   */
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_free(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream)
+#endif
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+
+	if(dev->adev->capture_stream==STREAM_ON)
+		em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,0);
+
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_prepare(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_prepare(struct snd_pcm_substream *substream)
+#endif
+{
+	return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_trigger(snd_pcm_substream_t *substream, int cmd)
+#else
+static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+#endif
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	switch(cmd){
+		case SNDRV_PCM_TRIGGER_START:
+			em28xx_cmd(dev,EM28XX_CAPTURE_STREAM_EN,1);
+			return 0;
+		case SNDRV_PCM_TRIGGER_STOP:
+			dev->adev->shutdown=1;
+			return 0;
+		default:
+			return -EINVAL;
+	}
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static void em28xx_audio_isocirq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_audio_isocirq(struct urb *urb)
+#endif
+{
+	struct em28xx *dev = urb->context;
+	struct em28xx_audio *adev = dev->adev;
+	int i;
+	unsigned int oldptr;
+	unsigned long flags;
+	int period_elapsed = 0;
+	int status;
+	unsigned char *cp;
+	unsigned int stride;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_substream_t *substream;
+	snd_pcm_runtime_t *runtime;
+#else
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+#endif
+        if (adev->capture_stream == STREAM_INTERRUPT) {
+                adev->capture_stream = STREAM_OFF;
+                wake_up_interruptible(&adev->audio_wait_stream);
+        }
+
+	if ((dev->state & DEV_DISCONNECTED) || 
+			(dev->state & DEV_MISCONFIGURED))                
+		return;
+
+
+	if(dev->adev->capture_pcm_substream){
+		substream=dev->adev->capture_pcm_substream;
+		runtime=substream->runtime;
+
+		stride = runtime->frame_bits >> 3;
+		for(i=0;i<urb->number_of_packets;i++){
+
+			int length=urb->iso_frame_desc[i].actual_length/stride;
+			cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+			if(!length)
+				continue;
+
+
+			if (urb->iso_frame_desc[i].status) 
+				continue;
+
+			spin_lock_irqsave(&dev->adev->slock, flags);
+			oldptr = dev->adev->hwptr_done_capture;
+			dev->adev->hwptr_done_capture +=length;
+			if(dev->adev->hwptr_done_capture >= runtime->buffer_size)
+				dev->adev->hwptr_done_capture -= runtime->buffer_size;
+
+			dev->adev->capture_transfer_done += length;
+			if(dev->adev->capture_transfer_done >= runtime->period_size){
+				dev->adev->capture_transfer_done -= runtime->period_size;
+				period_elapsed=1;
+			}
+			spin_unlock_irqrestore(&dev->adev->slock, flags);
+
+			if(oldptr + length > runtime->buffer_size){
+				unsigned int cnt = runtime->buffer_size-oldptr;
+				memcpy(runtime->dma_area+oldptr*stride, cp , cnt*stride);
+				memcpy(runtime->dma_area, cp + cnt*stride, length*stride - cnt*stride);
+			} else {
+				memcpy(runtime->dma_area+oldptr*stride, cp, length*stride);
+			}
+		}
+		if(period_elapsed){
+			snd_pcm_period_elapsed(substream);
+		}
+	}
+	urb->status = 0;
+	
+	if(dev->adev->shutdown)
+		return;
+
+	if((status = usb_submit_urb(urb, GFP_ATOMIC)))
+		em28xx_errdev("resubmit of audio urb failed (error=%i)\n", status);
+	return;
+}
+
+static int em28xx_isoc_audio_deinit(struct em28xx *dev)
+{
+	int i;
+	for(i=0;i<EM28XX_AUDIO_BUFS;i++){
+		usb_kill_urb(dev->adev->urb[i]);
+		usb_free_urb(dev->adev->urb[i]);
+		dev->adev->urb[i]=NULL;
+	}
+	return 0;
+}
+
+static int em28xx_init_audio_isoc(struct em28xx *dev)
+{
+	int i;
+	int errCode;
+	const int sb_size=EM28XX_NUM_AUDIO_PACKETS * EM28XX_AUDIO_MAX_PACKET_SIZE;
+
+	for(i=0;i<EM28XX_AUDIO_BUFS;i++){
+		struct urb *urb;
+		int j,k;
+		dev->adev->transfer_buffer[i]=kmalloc(sb_size,GFP_ATOMIC);
+
+		if(!dev->adev->transfer_buffer[i])
+			return -ENOMEM;
+
+		memset(dev->adev->transfer_buffer[i],0x80,sb_size);
+		urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS,GFP_ATOMIC);
+
+		if(urb){
+			urb->dev=dev->udev;
+			urb->context=dev;
+			urb->pipe=usb_rcvisocpipe(dev->udev,0x83);
+			urb->transfer_flags = URB_ISO_ASAP;
+			urb->transfer_buffer = dev->adev->transfer_buffer[i];
+			urb->interval=1;
+			urb->complete = em28xx_audio_isocirq;
+			urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS;
+			urb->transfer_buffer_length = sb_size;
+			for(j=k=0; j<EM28XX_NUM_AUDIO_PACKETS;j++,k+=EM28XX_AUDIO_MAX_PACKET_SIZE){
+				urb->iso_frame_desc[j].offset = k;
+				urb->iso_frame_desc[j].length=EM28XX_AUDIO_MAX_PACKET_SIZE;
+			}
+			dev->adev->urb[i]=urb;
+		} else 
+			return -ENOMEM;
+
+	}
+	for(i=0;i<EM28XX_AUDIO_BUFS;i++){
+		errCode = usb_submit_urb(dev->adev->urb[i], GFP_ATOMIC);
+		if (errCode){
+			em28xx_isoc_audio_deinit(dev);
+			return errCode;
+		}
+	}
+	return 0;
+}
+
+
+static int em28xx_cmd(struct em28xx *dev, int cmd,int arg)
+{
+	switch(cmd){
+		case EM28XX_CAPTURE_STREAM_EN:
+			if(dev->adev->capture_stream == STREAM_OFF && arg==1){
+				dev->adev->capture_stream=STREAM_ON;
+				em28xx_init_audio_isoc(dev);
+			} else if (dev->adev->capture_stream==STREAM_ON && arg==0){
+				dev->adev->capture_stream=STREAM_OFF;
+				em28xx_isoc_audio_deinit(dev);
+			} 
+			return 0;
+		default:
+			return -EINVAL;
+	}
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(snd_pcm_substream_t *substream)
+#else
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream *substream)
+#endif
+{
+	struct em28xx *dev;
+	snd_pcm_uframes_t hwptr_done;
+	dev = snd_pcm_substream_chip(substream);
+	hwptr_done = dev->adev->hwptr_done_capture;
+	return hwptr_done;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static struct page *snd_pcm_get_vmalloc_page(snd_pcm_substream_t *subs,
+					     unsigned long offset)
+#else
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+					     unsigned long offset)
+#endif
+{
+	void *pageptr = subs->runtime->dma_area + offset;
+	return vmalloc_to_page(pageptr);
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_ops_t snd_em28xx_pcm_capture = {
+#else
+static struct snd_pcm_ops snd_em28xx_pcm_capture = {
+#endif
+	.open = snd_em28xx_capture_open,
+	.close = snd_em28xx_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_em28xx_hw_capture_params,
+	.hw_free = snd_em28xx_hw_capture_free,
+	.prepare = snd_em28xx_prepare,
+	.trigger = snd_em28xx_capture_trigger,
+	.pointer = snd_em28xx_capture_pointer,
+	.page = snd_pcm_get_vmalloc_page,
+};
+
+
+static int em28xx_audio_init(struct em28xx *dev)
+{
+	struct em28xx_audio *adev;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+	snd_pcm_t *pcm;
+	snd_card_t *card;
+#else
+	struct snd_pcm *pcm;
+	struct snd_card *card;
+#endif
+	static int devnr;
+	int ret;
+	int err;
+	printk("em28xx-audio.c: probing for em28x1 non standard usbaudio\n");
+	printk("em28xx-audio.c: Copyright (C) 2006 Markus Rechberger\n");
+	adev=kzalloc(sizeof(*adev),GFP_KERNEL);
+	if(!adev){
+		printk("em28xx-audio.c: out of memory\n");
+		return -1;
+	}
+	card = snd_card_new(index[devnr], "Em28xx Audio", THIS_MODULE,0);
+	if(card==NULL){
+		kfree(adev);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&adev->slock);
+	init_waitqueue_head(&adev->audio_wait_stream);
+	init_waitqueue_head(&adev->open);
+	ret=snd_pcm_new(card, "Em28xx Audio", 0, 0, 1, &pcm);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture);
+	pcm->info_flags = 0;
+	pcm->private_data = dev;
+	strcpy(pcm->name,"Empia 28xx Capture");
+	strcpy(card->driver, "Empia Em28xx Audio");
+	strcpy(card->shortname, "Em28xx Audio");
+	strcpy(card->longname,"Empia Em28xx Audio");
+
+	if((err = snd_card_register(card))<0){
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+	adev->sndcard=card;
+	adev->udev=dev->udev;
+	dev->adev=adev;
+	return 0;
+}
+
+static int em28xx_audio_fini(struct em28xx *dev)
+{
+	struct em28xx_audio *adev = dev->adev;
+	int ret;
+	printk("audio: deinitializing audio device\n");
+
+	if(dev==NULL) {
+		printk("audio: dev is already null\n");
+		return 0;
+	}
+
+	if(adev){
+		printk("audio: before waitstream\n");
+		if (adev->capture_stream == STREAM_ON) {
+			adev->capture_stream = STREAM_INTERRUPT;
+			ret = wait_event_timeout(adev->audio_wait_stream,
+					(adev->capture_stream == STREAM_OFF) ||
+					(dev->state & DEV_DISCONNECTED),
+					EM28XX_URB_TIMEOUT);
+		}
+		printk("audio: after waitstream\n");
+		printk("audio: waiting for audioclients to release audio node\n");
+
+		if (adev->users>0) 
+			/* we have to strictly wait for this... */
+
+			wait_event(adev->open, (adev->users == 0));
+
+		snd_card_free(dev->adev->sndcard);
+		kfree(dev->adev);
+		dev->adev=NULL;
+	}
+	return 0;
+}
+
+static struct em28xx_ops audio_ops = {
+	.id	= EM28XX_AUDIO,
+	.name	= "Em28xx Audio Extension",
+	.init	= em28xx_audio_init,
+	.fini	= em28xx_audio_fini,
+};
+
+static int __init em28xx_alsa_register(void)
+{
+	return em28xx_register_extension(&audio_ops);
+}
+
+static void __exit em28xx_alsa_unregister(void)
+{
+	em28xx_unregister_extension(&audio_ops);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_DESCRIPTION("Em28xx Audio driver");
+
+module_init(em28xx_alsa_register);
+module_exit(em28xx_alsa_unregister);
diff --git a/drivers/media/video/empia/em28xx-audioep.c b/drivers/media/video/empia/em28xx-audioep.c
new file mode 100644
index 0000000..868e488
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-audioep.c
@@ -0,0 +1,522 @@
+/* Copyright (C) 2008 Empia Technology Inc.
+ * Copyright (C) 2008 Markus Rechberger <mrechberger@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/sound.h>
+#include <linux/soundcard.h>
+#include <linux/vmalloc.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+
+
+#include "em28xx.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_LICENSE("GPL"); // my appropriate code is dual licensed also BSD
+
+static struct usb_driver em28xx_audio_drv;
+static int em28xx_cmd(struct em28xx_audio *adev, int cmd,int arg);
+
+static struct usb_device_id em28xx_audio_id_table[]={
+        { USB_DEVICE(0x0ccd, 0x0042) }, /* just for testing it will not attach to the real device */
+	{ USB_DEVICE(0xeb1a, 0xe300) },
+	{ USB_DEVICE(0xeb1a, 0xe301) },
+	{ USB_DEVICE(0xeb1a, 0xe305) },
+	{ USB_DEVICE(0xeb1a, 0xe310) },
+	{ USB_DEVICE(0xeb1a, 0xe320) },
+	{ USB_DEVICE(0xeb1a, 0x2861) },
+        { },
+};
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_hardware_t snd_em28xx_hw_capture = {
+#else
+static struct snd_pcm_hardware snd_em28xx_hw_capture = {
+#endif
+	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 
+		SNDRV_PCM_INFO_MMAP | 
+		SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_INTERLEAVED  | 
+		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,
+	.buffer_bytes_max = 1024*1024, 
+	.period_bytes_min = 64, 
+	.period_bytes_max = 512*1024,
+	.periods_min = 2,
+	.periods_max = 1024,
+};
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_pcm_alloc_vmalloc_buffer(snd_pcm_substream_t *subs, size_t size)
+#else
+static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size)
+#endif
+{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_runtime_t *runtime = subs->runtime;
+#else
+        struct snd_pcm_runtime *runtime = subs->runtime;
+#endif
+        if(runtime->dma_area){
+                if(runtime->dma_bytes > size)
+                        return 0;
+                vfree(runtime->dma_area);
+        }
+        runtime->dma_area = vmalloc(size);
+        if(!runtime ->dma_area)
+                return -ENOMEM;
+        runtime->dma_bytes = size;
+        return 0;
+}
+
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params)
+#else
+static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
+#endif
+{
+        unsigned int channels, rate, format;
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+        int ret;
+        ret = snd_pcm_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params));
+        adev->hwptr_done_capture = 0;
+
+        format = params_format(hw_params);
+        rate = params_rate(hw_params);
+        channels = params_channels(hw_params);
+        /* TODO: set up em28xx audio chip to deliver the correct audio format, 
+           xc5000/cx25843 supports stereo
+           xc3028/em202 supports mono only
+           */
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_hw_capture_free(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+
+        if(adev->capture_stream == STREAM_ON)
+                em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 0);
+
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_prepare(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_prepare(struct snd_pcm_substream *substream)
+#endif
+{
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_trigger(snd_pcm_substream_t *substream, int cmd)
+#else
+static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+        switch(cmd){
+                case SNDRV_PCM_TRIGGER_START:
+                        em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 1);
+                        return 0;
+                case SNDRV_PCM_TRIGGER_STOP:
+                        adev->shutdown = 1;
+                        return 0;
+                default:
+                        return -EINVAL;
+        }
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static void em28xx_audio_isocirq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_audio_isocirq(struct urb *urb)
+#endif
+{
+        struct em28xx_audio *adev = urb->context;
+        int i;
+        unsigned int oldptr;
+        unsigned long flags;
+        int period_elapsed = 0;
+        unsigned char *cp;
+        unsigned int stride;
+
+	int status;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_substream_t *substream;
+        snd_pcm_runtime_t *runtime;
+#else
+        struct snd_pcm_substream *substream;
+        struct snd_pcm_runtime *runtime;
+#endif
+        if (adev->capture_stream == STREAM_INTERRUPT) {
+                adev->capture_stream = STREAM_OFF;
+                wake_up_interruptible(&adev->audio_wait_stream);
+        }
+        if ((adev->state & DEV_DISCONNECTED) ||
+                        (adev->state & DEV_MISCONFIGURED))
+                return;
+
+
+        if(adev->capture_pcm_substream){
+                substream=adev->capture_pcm_substream;
+                runtime=substream->runtime;
+
+                stride = runtime->frame_bits >> 3;
+                for(i=0;i<urb->number_of_packets;i++){
+
+                        int length=urb->iso_frame_desc[i].actual_length/stride;
+                        cp=(unsigned char *) urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+
+                        if(!length)
+                                continue;
+
+
+                        if (urb->iso_frame_desc[i].status)
+                                continue;
+
+                        spin_lock_irqsave(&adev->slock, flags);
+                        oldptr = adev->hwptr_done_capture;
+                        adev->hwptr_done_capture +=length;
+                        if(adev->hwptr_done_capture >= runtime->buffer_size)
+                                adev->hwptr_done_capture -= runtime->buffer_size;
+
+                        adev->capture_transfer_done += length;
+                        if(adev->capture_transfer_done >= runtime->period_size){
+                                adev->capture_transfer_done -= runtime->period_size;
+                                period_elapsed=1;
+                        }
+                        spin_unlock_irqrestore(&adev->slock, flags);
+
+                        if(oldptr + length > runtime->buffer_size){
+                                unsigned int cnt = runtime->buffer_size-oldptr;
+                                memcpy(runtime->dma_area+oldptr*stride, cp , cnt*stride);
+                                memcpy(runtime->dma_area, cp + cnt*stride, length*stride - cnt*stride);
+                        } else {
+                                memcpy(runtime->dma_area+oldptr*stride, cp, length*stride);
+                        }
+                }
+                if(period_elapsed){
+                        snd_pcm_period_elapsed(substream);
+                }
+        }
+        urb->status = 0;
+
+        if(adev->shutdown)
+                return;
+
+        if((status = usb_submit_urb(urb, GFP_ATOMIC)))
+                printk("resubmit of audio urb failed (error=%i)\n", status);
+        return;
+}
+
+
+static int em28xx_isoc_audio_deinit(struct em28xx_audio *adev)
+{
+        int i;
+        for(i=0; i<EM28XX_AUDIO_BUFS; i++){
+                usb_kill_urb(adev->urb[i]);
+                usb_free_urb(adev->urb[i]);
+                adev->urb[i] = NULL;
+        }
+        return 0;
+}
+
+static int em28xx_init_audio_isoc(struct em28xx_audio *adev)
+{
+        int i;
+        int errCode;
+        const int sb_size=EM28XX_NUM_AUDIO_PACKETS * EM28XX_AUDIO_MAX_PACKET_SIZE;
+
+        for(i=0; i<EM28XX_AUDIO_BUFS; i++){
+                struct urb *urb;
+                int j,k;
+                adev->transfer_buffer[i]=kzalloc(sb_size,GFP_ATOMIC);
+
+                if(!adev->transfer_buffer[i])
+                        return -ENOMEM;
+
+                urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS,GFP_ATOMIC);
+
+                if(urb){
+                        urb->dev = adev->udev;
+                        urb->context = adev;
+                        urb->pipe = usb_rcvisocpipe(adev->udev,0x83);
+                        urb->transfer_flags = URB_ISO_ASAP;
+                        urb->transfer_buffer = adev->transfer_buffer[i];
+                        urb->interval = 1;
+                        urb->complete = em28xx_audio_isocirq;
+                        urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS;
+                        urb->transfer_buffer_length = sb_size;
+                        for(j=k=0; j<EM28XX_NUM_AUDIO_PACKETS; j++,k+=EM28XX_AUDIO_MAX_PACKET_SIZE){
+                                urb->iso_frame_desc[j].offset = k;
+                                urb->iso_frame_desc[j].length = EM28XX_AUDIO_MAX_PACKET_SIZE;
+                        }
+                        adev->urb[i] = urb;
+                } else
+                        return -ENOMEM;
+
+        }
+        for(i=0; i<EM28XX_AUDIO_BUFS; i++){
+                errCode = usb_submit_urb(adev->urb[i], GFP_ATOMIC);
+                if (errCode){
+                        em28xx_isoc_audio_deinit(adev);
+                        return errCode;
+                }
+        }
+        return 0;
+}
+
+static int em28xx_cmd(struct em28xx_audio *adev, int cmd,int arg)
+{
+        switch(cmd){
+	case EM28XX_CAPTURE_STREAM_EN:
+		if(adev->capture_stream == STREAM_OFF && arg==1){
+			adev->capture_stream = STREAM_ON;
+			em28xx_init_audio_isoc(adev);
+		} else if (adev->capture_stream == STREAM_ON && arg==0){
+			adev->capture_stream = STREAM_OFF;
+			em28xx_isoc_audio_deinit(adev);
+		}
+		return 0;
+	default:
+		return -EINVAL;
+        }
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(snd_pcm_substream_t *substream)
+#else
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev;
+        snd_pcm_uframes_t hwptr_done;
+        adev = snd_pcm_substream_chip(substream);
+        hwptr_done = adev->hwptr_done_capture;
+        return hwptr_done;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static struct page *snd_pcm_get_vmalloc_page(snd_pcm_substream_t *subs,
+                                             unsigned long offset)
+#else
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+                                             unsigned long offset)
+#endif
+{
+        void *pageptr = subs->runtime->dma_area + offset;
+        return vmalloc_to_page(pageptr);
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_capture_open(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_capture_open(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+	int errCode;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_runtime_t *runtime = substream->runtime;
+#else
+        struct snd_pcm_runtime *runtime = substream->runtime;
+#endif
+
+	if(adev->alt == 0 && adev->users == 0 ) 
+                errCode = usb_set_interface(adev->udev, 1, adev->alt_max); /* Todo */
+
+	runtime->hw = snd_em28xx_hw_capture;
+
+        adev->users++;
+        runtime->private_data = adev;
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	adev->capture_pcm_substream = substream;
+	return 0;
+
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static int snd_em28xx_pcm_close(snd_pcm_substream_t *substream)
+#else
+static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream)
+#endif
+{
+        struct em28xx_audio *adev = snd_pcm_substream_chip(substream);
+        adev->users--;
+        if(adev->users == 0 && adev->shutdown == 1) {
+                adev->shutdown = 0;
+                em28xx_cmd(adev, EM28XX_CAPTURE_STREAM_EN, 0);
+        }
+        wake_up(&adev->open);
+        return 0;
+}
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+static snd_pcm_ops_t snd_em28xx_pcm_capture = {
+#else
+static struct snd_pcm_ops snd_em28xx_pcm_capture = {
+#endif
+        .open = snd_em28xx_capture_open,
+        .close = snd_em28xx_pcm_close,
+        .ioctl = snd_pcm_lib_ioctl,
+        .hw_params = snd_em28xx_hw_capture_params,
+        .hw_free = snd_em28xx_hw_capture_free,
+        .prepare = snd_em28xx_prepare,
+        .trigger = snd_em28xx_capture_trigger,
+        .pointer = snd_em28xx_capture_pointer,
+        .page = snd_pcm_get_vmalloc_page,
+};
+
+
+static int em28xx_audio_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+	int err;
+	int i;
+	u8 max_alt;
+	u8 if_num;
+	u16 max_pck = 0, cur_pck = 0;
+	struct usb_device *udev;
+	struct em28xx_audio *adev = NULL;
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
+        snd_pcm_t *pcm;
+        snd_card_t *card;
+#else
+        struct snd_pcm *pcm;
+        struct snd_card *card;
+#endif
+
+
+	int retval = -ENODEV;
+	u8 i_max = 0;
+        static int devnr;
+        int ret;
+
+
+	struct usb_endpoint_descriptor *endpoint;
+	udev = usb_get_dev(interface_to_usbdev(interface));
+	endpoint = &interface->cur_altsetting->endpoint[0].desc;
+
+	if_num = interface->altsetting[0].desc.bInterfaceNumber;
+	max_alt = interface->num_altsetting;
+
+	if (max_alt == 6 && if_num > 0) {
+		printk("registering em28xx-audioep\n");
+		for (i=0; i<max_alt; i++) {
+			cur_pck=le16_to_cpu(interface->altsetting[i].endpoint[0].desc.wMaxPacketSize);
+			if (cur_pck > max_pck) {
+				i_max = i;
+				max_pck = cur_pck;
+			}
+		}
+		adev = kzalloc(sizeof(struct em28xx_audio), GFP_KERNEL);
+		retval = 0;
+		adev->max_pck = max_pck;
+		adev->alt_max = i_max;
+		adev->udev = udev;
+		
+		printk("em28xx-audioep.c: detected nonstandard usbaudio\n");
+		printk("em28xx-audioep.c: Copyright (C) 2008 Empia Technology Inc.\n");
+		printk("em28xx-audioep.c: Copyright (C) 2008 Markus Rechberger\n");
+		
+
+		card = snd_card_new(index[devnr], "Em28xx Audio2", THIS_MODULE, 0);
+
+		// todo increase devnr .. or better make a bitmap out of it
+
+		if(card == NULL) {
+			kfree(adev);
+			return -ENOMEM;
+		}
+
+		spin_lock_init(&adev->slock);
+		init_waitqueue_head(&adev->audio_wait_stream);
+		init_waitqueue_head(&adev->open);
+		ret = snd_pcm_new(card, "Em28xx Audio2", 0, 0, 1, &pcm);
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture);
+		pcm->info_flags = 0;
+		pcm->private_data = adev;
+		strcpy(pcm->name, "Empia em28xx Capture");
+		strcpy(card->driver, "Empia Em28xx Audio 2");
+		strcpy(card->shortname, "Em28xx Audio 2");
+		strcpy(card->longname, "Empia Em28xx Audio 2");
+
+		if ((err = snd_card_register(card)) < 0) {
+			snd_card_free(card);
+			return -ENOMEM;
+		}
+		adev->sndcard = card;
+
+		usb_set_intfdata(interface, adev);
+	}
+	return retval;
+}
+
+static void em28xx_audio_disconnect(struct usb_interface *interface)
+{
+	struct em28xx_audio *adev = usb_get_intfdata(interface);
+	usb_set_intfdata(interface, NULL);
+	if (!adev)
+		return;
+
+	if (adev->users > 0)
+		wait_event(adev->open, (adev->users == 0));
+
+	snd_card_free(adev->sndcard);
+	kfree(adev);
+}
+
+static struct usb_driver em28xx_audio_drv = {
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 15)
+	.owner = THIS_MODULE,
+#endif
+	.name = "em28xx-audioep",
+	.probe = em28xx_audio_probe,
+	.disconnect = em28xx_audio_disconnect,
+	.id_table = em28xx_audio_id_table,
+};
+
+static int __init em28xx_audio_init(void)
+{
+	int result;
+	result = usb_register(&em28xx_audio_drv);
+	if (result)
+		printk("usb_register failed. Error number %d.\n", result);
+	return result;
+}
+
+static void __exit em28xx_audio_exit(void)
+{
+	usb_deregister(&em28xx_audio_drv);
+}
+
+module_init(em28xx_audio_init);
+module_exit(em28xx_audio_exit);
diff --git a/drivers/media/video/empia/em28xx-cards.c b/drivers/media/video/empia/em28xx-cards.c
new file mode 100644
index 0000000..00bea04
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-cards.c
@@ -0,0 +1,3387 @@
+/*
+   em28xx-cards.c - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices
+
+   Copyright (C) 2005 - 2008 Markus Rechberger <mrechberger@gmail.com>
+		 2005       Mauro Carvalho Chehab <mchehab@infradead.org>
+		 2005       Sascha Sommer <saschasommer@freenet.de>
+		 2005       Ludovico Cavedon <cavedon@sssup.it>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/firmware.h>
+#include <media/tuner.h>
+#ifdef EM28XX_TVEEPROM
+#include <media/tveeprom.h>
+#endif
+#include <media/v4l2-common.h>
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 26)
+#include <media/audiochip.h>
+#else
+#include <media/v4l2-chip-ident.h>
+#endif
+#include "msp3400-driver.h"
+#include <media/saa7115.h>
+#include <media/tvp5150.h>
+
+#include "xc3028/xc3028_firmwares.h"
+#include "xc3028/xc3028l_firmwares.h"
+#include "xc3028/xc3028_channelmaps.h"
+#define XCEIVE_EXTERNAL_FIRMWARE
+#ifdef XCEIVE_EXTERNAL_FIRMWARE
+#include "xc3028/xc3028fw_helper.h"
+#endif
+
+#include "em28xx.h"
+#include "em28xx-keymaps.h"
+
+extern unsigned char *XC5000_firmware_SEQUENCE;
+
+
+
+static const u8 em28xx_terratec_cinergy_200_usb_i2c_devs[]     = {0x4a, 0x60, 0x62, 0x64, 0x86, 0xc0, 0xc2, 0};
+static const u8 em28xx_vgear_pockettv_i2c_devs[]               = {0x4a, 0x60, 0xc6, 0};
+
+/* keep this map private */
+#define EETI_XC3028_DEFAULT_ANALOG { 					\
+	{								\
+		/* analogue TV */					\
+		.name = "PAL-BG", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_BG, 					\
+		.tv_mode = &XC3028_tv_mode_b_g_pal_nicam_b_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 18						\
+	}, {								\
+		.name = "PAL-DK", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_DK, 					\
+		.tv_mode = &XC3028_tv_mode_d_k_pal_nicam_mono, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-I", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_I, 					\
+		.tv_mode = &XC3028_tv_mode_i_pal_nicam_mono, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "NTSC-M", /* AV - tested OK */			\
+		.id = V4L2_STD_NTSC_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_mono,        \
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", /* AV tested ok */			\
+		.id = V4L2_STD_SECAM_L, 				\
+                .tv_mode = &XC3028_tv_mode_l_secam_nicam_am_only,       \
+		.channelmap = &XC3028_channel_map_france_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM LC", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM K1", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-M", /* AV - not tested */			\
+		.id = V4L2_STD_PAL_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_mono, 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}								\
+}
+
+/* keep this map private */
+#define EETI_XC3028_DEFAULT_ANALOG_STEREO {				\
+	{								\
+		/* analogue TV */					\
+		.name = "PAL-BG", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_BG, 					\
+		.tv_mode = &XC3028_tv_mode_b_g_pal_nicam_b,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 18						\
+	}, {								\
+		.name = "PAL-DK", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_DK, 					\
+		.tv_mode = &XC3028_tv_mode_d_k_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-I", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_I, 					\
+		.tv_mode = &XC3028_tv_mode_i_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "NTSC-M", /* AV - tested OK */			\
+		.id = V4L2_STD_NTSC_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2_if,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_L, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM LC", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM K1", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.tv_mode = &XC3028_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 5, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-M", /* AV - not tested */			\
+		.id = V4L2_STD_PAL_M, 					\
+		.tv_mode = &XC3028_tv_mode_m_n_ntsc_pal_a2, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_ANALOG {					\
+	{								\
+		/* analogue TV */					\
+		.name = "PAL-BG", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_BG, 					\
+		.tv_mode = &XC3028L_tv_mode_b_g_pal_nicam_b,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 18						\
+	}, {								\
+		.name = "PAL-DK", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_DK, 					\
+		.tv_mode = &XC3028L_tv_mode_d_k_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-I", /* AV - tested OK */			\
+		.id = V4L2_STD_PAL_I, 					\
+		.tv_mode = &XC3028L_tv_mode_i_pal_nicam, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "NTSC-M", /* AV - tested OK */			\
+		.id = V4L2_STD_NTSC_M, 					\
+		.tv_mode = &XC3028L_tv_mode_m_n_ntsc_pal_a2_if,		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 8, 					\
+		.vbi_start_1 = 318, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_L, 				\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM LC", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "SECAM K1", /* AV - not tested */		\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}, {								\
+		.name = "PAL-M", /* AV - not tested */			\
+		.id = V4L2_STD_PAL_M, 					\
+		.tv_mode = &XC3028L_tv_mode_d_k_secam_a2_dk1,	 	\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 18, 					\
+		.vbi_count_1 = 18, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x12						\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_DVBT { 					\
+	{								\
+		/* DVB-T */						\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_7_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_QAM { 					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv6_zarlink_qam_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_FM { 					\
+	{								\
+		.tv_mode = &XC3028L_tv_mode_fm_radio_input1, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+	}								\
+}
+
+#define EETI_XC3028L_DEFAULT_ATSC {					\
+	{								\
+		/* ATSC */						\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028L_tv_mode_dtv6_atsc_lg_6_0mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_QAM { 					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv6_zarlink_qam_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_DVBT { 					\
+	{								\
+		/* DVB-T */						\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_7_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv78_zarlink_4_56mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air, 	\
+	}								\
+}
+
+
+#define EETI_XC3028_DEFAULT_ATSC {					\
+	{								\
+		/* ATSC */						\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv6_atsc_lg_6_0mhz, 	\
+		.channelmap = &XC3028_channel_map_ccir_digital_air	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_QAM64 {					\
+	{	/* QAM64 */						\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.tv_mode = &XC3028_tv_mode_dtv6_qam_6_0mhz, 		\
+		.channelmap = &XC3028_channel_map_ccir_digital_air	\
+	}								\
+}
+
+#define EETI_XC3028_DEFAULT_FM { 					\
+	{								\
+		.tv_mode = &XC3028_tv_mode_fm_radio_input1, 		\
+		.channelmap = &XC3028_channel_map_ccir_analog_air, 	\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_ANALOG {					\
+	{								\
+		.name = "PAL-BG", 					\
+		.id = V4L2_STD_PAL_BG, 					\
+		.index = 4, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "PAL-DK", 					\
+		.id = V4L2_STD_PAL_DK, 					\
+		.index = 9, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "PAL-I", 					\
+		.id = V4L2_STD_PAL_I, 					\
+		.index = 7, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "NTSC M", 					\
+		.id = V4L2_STD_NTSC_M, 					\
+		.index = 0, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 12, 					\
+		.vbi_count_1 = 12, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x06, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x0c						\
+	}, {								\
+		.name = "SECAM L", 					\
+		.id = V4L2_STD_SECAM_L, 				\
+		.index = 15, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "SECAM LC", 					\
+		.id = V4L2_STD_SECAM_LC, 				\
+		.index = 16, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "SECAM K1", 					\
+		.id = V4L2_STD_SECAM_K1, 				\
+		.index = 15, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}, {								\
+		.name = "PAL-M", /* todo, this one needs testing */	\
+		.id = V4L2_STD_PAL_M, 					\
+		.index = 1, 						\
+		.vbi_sample_rate = 27000000, 				\
+		.vbi_samples_per_line = 1440, 				\
+		.vbi_start_0 = 6, 					\
+		.vbi_start_1 = 317, 					\
+		.vbi_count_0 = 17, 					\
+		.vbi_count_1 = 17, 					\
+		.vbi_h_start = 0x00, 					\
+		.vbi_v_start = 0x46, 					\
+		.vbi_w = 0xb4, 						\
+		.vbi_h = 0x11						\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_FM {					\
+	{								\
+		.name = "FM Radio", 					\
+		.index = 22, 						\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_DVBT {					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.index = 18						\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_7_MHZ, 			\
+		.index = 20						\
+	}, {								\
+		.bandwidth = EM28XX_BANDWIDTH_6_MHZ, 			\
+		.index = 17						\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_ATSC {					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.index = 0 /* TODO */					\
+	}								\
+}
+
+#define EETI_XC5000_DEFAULT_QAM {					\
+	{								\
+		.bandwidth = EM28XX_BANDWIDTH_8_MHZ, 			\
+		.index = 0 /* TODO */					\
+	}								\
+}
+
+#define EETI_DEFAULT_GPIO {						\
+	.ts1_on     = _BIT_VAL(EM28XX_GPIO0,  0, 0), 			\
+	.a_on       = _BIT_VAL(EM28XX_GPIO1,  0, 0), 			\
+	.xc3028_sec = _BIT_VAL(EM28XX_GPIO2,  1, 0), 			\
+	/* reserved */							\
+	.t1_reset   = _BIT_VAL(EM28XX_GPIO4,  0, 1), 			\
+	/* reserved */							\
+	.t1_on      = _BIT_VAL(EM28XX_GPIO6,  0, 0), 			\
+	.t2_on      = _BIT_VAL(EM28XX_GPIO7,  1, 0), 			\
+									\
+	.l1_on      = _BIT_VAL(EM28XX_GOP2,   1, 0), 			\
+	.d1_reset   = _BIT_VAL(EM28XX_GOP3,   0, 1), 			\
+}
+
+#define EETI_DRX3975D_GPIO {						\
+	.ts1_on     = _BIT_VAL(EM28XX_GPIO0,  0, 0), 			\
+	.a_on       = _BIT_VAL(EM28XX_GPIO1,  0, 0), 			\
+	.xc3028_sec = _BIT_VAL(EM28XX_GPIO2,  1, 0), 			\
+	/* reserved */							\
+	.t1_reset   = _BIT_VAL(EM28XX_GPIO4,  0, 1), 			\
+	/* reserved */							\
+	.t1_on      = _BIT_VAL(EM28XX_GPIO6,  0, 0), 			\
+	.t2_on      = _BIT_VAL(EM28XX_GPIO7,  1, 0), 			\
+									\
+	.l1_on      = _BIT_VAL(EM28XX_GOP2,   1, 0), 			\
+}
+
+struct em28xx_board em28xx_boards[] = {
+	/* FIXME: please verify the supported video standards */
+	[EM2800_BOARD_GENERIC] = {
+		.name         = "Generic EM2800 video grabber",
+	},
+	[EM2820_BOARD_GENERIC] = {
+		.name         = "Generic EM2820 video grabber",
+	},
+	[EM2821_BOARD_GENERIC] = {
+		.name         = "Generic EM2821 video grabber",
+	},
+	[EM2750_BOARD_GENERIC] = {
+		.name         = "Generic EM2750 video grabber",
+	},
+	[EM2860_BOARD_GENERIC] = {
+		.name         = "Generic EM2860 video grabber",
+	},
+	[EM2861_BOARD_GENERIC] = {
+		.name         = "Generic EM2861 video grabber",
+	},
+	[EM2870_BOARD_GENERIC] = {
+		.name         = "Generic EM2870 video grabber",
+	},
+	[EM2881_BOARD_GENERIC] = {
+		.name         = "Generic EM2881 video grabber",
+	},
+	[EM2883_BOARD_GENERIC] = {
+		.name         = "Generic EM2883 video grabber",
+	},
+	[EM2820_BOARD_KWORLD_PVRTV2800RF] = {
+		.name         = "KWorld PVR TV 2800 RF",
+		.vchannels    = 2,
+		.norm         = V4L2_STD_PAL_BG,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input           = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_TERRATEC_CINERGY_250] = {
+		.name         = "Terratec Cinergy 250 USB",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.ir_i2c       = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_LINE_IN,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_DLINK_USB_TV] = {
+		.name         = "D-Link DUB-T210 TV Tuner",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2821_BOARD_PROLINK_PLAYTV_USB2] = {
+		.name         = "SIIG AVTuner-PVR/Prolink PlayTV USB 2.0",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC, /* unknown? */
+		.tda9887_conf = TDA9887_PRESENT, /* unknown? */
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_NOTOUCH,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 2,
+			.amix     = EM28XX_MIX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+			.amix     = EM28XX_MIX_LINE_IN,
+		}},
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_HERCULES_SMART_TV_USB2] = {
+		.name         = "Hercules Smart TV USB 2.0",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_USB_2] = {
+		.name         = "Pinnacle PCTV USB 2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+
+				.id = V4L2_STD_PAL_M,
+			}, {
+				.name = "NTSC",
+
+				.id = V4L2_STD_NTSC,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_USB_2_FM1216ME] = {
+		.name         = "Pinnacle PCTV USB 2 (Philips FM1216ME)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms        = {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+			}, {
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC,
+			}, {
+				.name = "SECAM-DK",
+				.id = V4L2_STD_SECAM_DK,
+			} },
+	},
+	[EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2] = {
+		.name         = "Hauppauge WinTV USB 2 (R2)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_ABSENT,
+		.tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.has_msp34xx  = 1,
+		.dev_modes    = EM28XX_VIDEO,
+		/*FIXME: S-Video not tested */
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 6,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC,
+		} },
+	},
+	[EM2820_BOARD_HAUPPAUGE_WINTV_USB_2] = {
+		.name         = "Hauppauge WinTV USB 2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC,
+		.tuner_type   = TUNER_ABSENT,
+		.tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.has_msp34xx  = 1,
+		.dev_modes    = EM28XX_VIDEO,
+		/*FIXME: S-Video not tested */
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 6,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "NTSC",
+
+				.id = V4L2_STD_NTSC,
+		} },
+	},
+	[EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2] = {
+		.name         = "Hauppauge WinTV USB 2 R2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 6,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2821_BOARD_SUPERCOMP_USB_2] = {
+		.name         = "Supercomp USB 2.0 TV",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+		.tda9887_conf = TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900] = {
+		.name         = "Hauppauge WinTV HVR 900",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT
+	},
+	[EM2883_BOARD_PINNACLE_PCTV_HD_PRO] = {
+		.name         = "Pinnacle PCTV HD Pro",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_ATSC,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.atscnorms	= EETI_XC3028_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC3028_DEFAULT_QAM,
+	},
+	[EM2883_BOARD_KWORLD_HYBRID_F306] = {
+		.name         = "KWorld PlusTV F306",
+		.em_type      = EM2883,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2888_BOARD_KWORLD_HYBRID_E329] = {
+		.name	      = "KWorld 329u",
+		.em_type      = EM2888,
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.ir_keytab    = ir_codes_em_kworld,
+		.ir_getkey    = em2888_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028L_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC3028L_DEFAULT_FM,
+	},
+	[EM2888_BOARD_LINCOLN_TV_FM] = {
+		.name	      = "Lincoln TV FM",
+		.em_type      = EM2888,
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC3028L_DEFAULT_FM,
+	},
+	[EM2883_BOARD_KWORLD_HYBRID_E323] = {
+		.name	      = "KWorld E323",
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+		.input        = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC3028_DEFAULT_FM,
+	},
+	[EM2883_BOARD_KWORLD_HYBRID_A316] = {
+		.name         = "KWorld PlusTV HD Hybrid 330",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950] = {
+		.name         = "Hauppauge WinTV HVR 950",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_ATSC,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.atscnorms	= EETI_XC3028_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC3028_DEFAULT_QAM,
+	},
+	[EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2] = {
+		.name         = "Hauppauge WinTV HVR (B2C0)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_hauppauge_new_u,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DRX3975D_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2860_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Terratec Cinergy A Hybrid XS",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2880_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Terratec Hybrid XS",
+		.vchannels    = 3,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2861_BOARD_KWORLD_PVRTV_300U] = {
+		.name	      = "KWorld PVRTV 300U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO2,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2880_BOARD_KWORLD_DVB_310U] = {
+		.name	      = "KWorld DVB-T 310U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_KWORLD_DVB_305U] = {
+		.name	      = "KWorld DVB-T 305U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_TERRATEC_HYBRID_XS_FR] = {
+		.name         = "Terratec Hybrid XS Secam",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_SECAM_L,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.has_msp34xx  = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2882_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Empia Hybrid ATSC (em2882)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec, 
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2883_BOARD_TERRATEC_HYBRID_XS_FM] = {
+		.name         = "Terratec Hybrid XS FM (em2883)",
+		.em_type      = EM2883,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_radio    = 1,
+		.has_inttuner = 1,
+#if 0
+		.powersaving  = 1,
+#endif
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.decoder      = EM28XX_CX25843,
+		.ir_keytab    = ir_codes_em_terratec2,
+		.ir_getkey    = em2880_get_key_terratec,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC5000_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+	},
+	[EM2881_BOARD_DNT_DA2_HYBRID] = {
+		.name         = "DNT DA2 Hybrid",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2870_BOARD_TERRATEC_XS] = {
+		.name         = "Terratec Cinergy T XS",
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.dev_modes    = EM28XX_DVBT,
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2870_BOARD_TERRATEC_XS_MT2060] = {
+		.name         = "Terratec Cinergy T XS (MT2060)",
+		.tuner_type   = TUNER_MT2060,
+		.dev_modes    = EM28XX_DVBT,
+#if 0
+		.manual_gpio  = 1,
+		.gpio_regs    = EETI_DEFAULT_GPIO,
+#endif
+	},
+	[EM2870_BOARD_PINNACLE_PCTV_DVB] = {
+		.name         = "Pinnacle PCTV DVB-T",
+		.tuner_type   = TUNER_MT2060,
+		.dev_modes    = EM28XX_DVBT,
+		.ir_keytab    = ir_codes_pinnacle2,
+		.ir_getkey    = em2880_get_key_empia,
+#if 0
+		.manual_gpio  = 1,
+		.gpio_regs    = EETI_DEFAULT_GPIO,
+#endif
+	},
+	[EM2870_BOARD_KWORLD_350U] = {
+		.name         = "KWorld 350 U DVB-T",
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.dev_modes    = EM28XX_DVBT,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2870_BOARD_KWORLD_355U] = {
+		.name         = "KWorld 355 U DVB-T",
+		.tuner_type   = TUNER_QT1010,
+		.dev_modes    = EM28XX_DVBT,
+		.manual_gpio  = 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+	},
+	[EM2870_BOARD_COMPRO_VIDEOMATE] = {
+		.name         = "Compro, VideoMate U3",
+		.tuner_type   = TUNER_MT2060,
+		.dev_modes    = EM28XX_DVBT,
+	},
+	[EM2881_BOARD_PINNACLE_HYBRID_PRO] = {
+		.name         = "Pinnacle Hybrid Pro",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_pinnacle2,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2882_BOARD_PINNACLE_HYBRID_PRO] = {
+		.name         = "Pinnacle Hybrid Pro (em2882)",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.tuner_addr   = 0xc2,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_pinnacle2_usb,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DRX3975D_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	/* maybe there's a reason behind it why Terratec sells the Hybrid XS as Prodigy XS with a
+	 * different PID, let's keep it separated for now maybe we'll need it lateron */
+	[EM2880_BOARD_TERRATEC_PRODIGY_XS] = {
+		.name         = "Terratec Prodigy XS",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_MSI_DIGIVOX_AD] = {
+		.name         = "MSI DigiVox A/D",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2880_BOARD_MSI_DIGIVOX_AD_II] = {
+		.name         = "MSI DigiVox A/D II",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2883_BOARD_ATI_TVWONDER600] = {
+		.name         = "ATI TV Wonder HD 600",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_ATSC | EM28XX_AUDIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.atscnorms	= EETI_XC3028L_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC3028L_DEFAULT_QAM,
+	},
+	[EM2820_BOARD_MSI_VOX_USB_2] = {
+		.name		= "MSI VOX USB 2.0",
+		.vchannels	= 3,
+		.norm		= V4L2_STD_PAL_BG,
+		.tuner_type	= TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf	= TDA9887_PRESENT|TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE,
+		.has_tuner	= 1,
+		.decoder        = EM28XX_SAA7114,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE4,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+			}, {
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC_M,
+		} },
+	},
+	[EM2800_BOARD_TERRATEC_CINERGY_200] = {
+		.name         = "Terratec Cinergy 200 USB",
+		.em_type      = EM2800,
+		.i2c_devs     = em28xx_terratec_cinergy_200_usb_i2c_devs,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.ir_i2c       = 1,
+
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE] = {
+		.name         = "Leadtek Winfast USB II Deluxe",
+		.em_type      = EM2820,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7114,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = 2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = 9,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2800_BOARD_LEADTEK_WINFAST_USBII] = {
+		.name         = "Leadtek Winfast USB II",
+		.em_type      = EM2800,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2800_BOARD_KWORLD_USB2800] = {
+		.name         = "KWorld USB2800",
+		.em_type      = EM2800,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+#if defined TUNER_PHILIPS_FCV1236D
+		.tuner_type   = TUNER_PHILIPS_FCV1236D,
+#else
+		.tuner_type   = TUNER_PHILIPS_ATSC,
+#endif
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}
+		},
+	},
+	[EM2820_BOARD_PINNACLE_DVC_90] = {
+		.name         = "Pinnacle Dazzle DVC 90",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 0,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_DVC_100] = {
+		.name         = "Pinnacle Dazzle DVC 100",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 0,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "SECAM L",
+				.id = V4L2_STD_SECAM_L,
+			}, {
+				.name = "SECAM LC",
+				.id = V4L2_STD_SECAM_LC,
+			}, {
+				.name = "SECAM K1",
+				.id = V4L2_STD_SECAM_K1,
+		} },
+	},
+	[EM2820_BOARD_VIDEOLOGY_20K14XUSB] = {
+		.name          = "Videology 20K14XUSB USB2.0",
+		.vchannels     = 1,
+		.norm          = V4L2_STD_PAL_BG,
+		.has_tuner     = 0,
+		.ctrl          = em28xx_vy_cctrl,
+		.gctrl         = em28xx_vy_gctrl,
+		.qctrl         = em28xx_vy_qctrl,
+		.dev_modes      = EM28XX_VIDEO,
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2821_BOARD_USBGEAR_VD204] = {
+		.name          = "Usbgear VD204v9",
+		.vchannels     = 2,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type  = EM28XX_VMUX_COMPOSITE1,
+			.vmux  = SAA7115_COMPOSITE0,
+			.amux  = 1,
+		}, {
+			.type  = EM28XX_VMUX_SVIDEO,
+			.vmux  = SAA7115_SVIDEO3,
+			.amux  = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2860_BOARD_TYPHOON_DVD_MAKER] = {
+		.name          = "Typhoon DVD Maker",
+		.vchannels     = 2,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type  = EM28XX_VMUX_COMPOSITE1,
+			.vmux  = SAA7115_COMPOSITE0,
+			.amux  = 1,
+		}, {
+			.type  = EM28XX_VMUX_SVIDEO,
+			.vmux  = SAA7115_SVIDEO3,
+			.amux  = 1,
+		} },
+		.tvnorms	= {{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2860_BOARD_GADMEI_UTV330] = {
+		.name         = "Gadmei UTV330",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.ir_keytab    = ir_codes_em_gadmei_usb,
+		.ir_getkey    = em2880_get_key_empia,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC_M,
+		}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+		} },
+	},
+	[EM2820_BOARD_GADMEI_UTV310] = {
+		.name         = "Gadmei UTV310",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {{
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC_M,
+		} },
+	},
+	[EM2800_BOARD_VGEAR_POCKETTV] = {
+		.name         = "V-Gear PocketTV",
+		.em_type      = EM2800,
+		.i2c_devs     = em28xx_vgear_pockettv_i2c_devs,
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_SAA7113,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			}, {
+				.name = "PAL-DK",
+				.id = V4L2_STD_PAL_DK,
+			}, {
+				.name = "PAL-I",
+				.id = V4L2_STD_PAL_I,
+			}, {
+				.name = "PAL-M",
+				.id = V4L2_STD_PAL_M,
+		} },
+	},
+	[EM2861_BOARD_YAKUMO_MOVIE_MIXER] = {
+		.name          = "Yakumo MovieMixer",
+		.vchannels     = 1,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_TVP5150,
+		.has_tuner     = 0,
+		.dev_modes      = EM28XX_VIDEO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2860_BOARD_NETGMBH_CAM] = {
+		.name          = "NetGMBH Cam", /* Beijing Huaqi Information Digital Technology Co., Ltd */
+		.vchannels     = 1,
+		.norm	       = V4L2_STD_PAL_BG,
+		.has_tuner     = 0,
+		.dev_modes      = EM28XX_VIDEO,
+
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2750_BOARD_DLCW_130] = {
+		.name          = "Huaqi DLCW-130", /* Beijing Huaqi Information Digital Technology Co., Ltd */
+		.vchannels     = 1,
+		.norm	       = V4L2_STD_PAL_BG,
+		.dev_modes      = EM28XX_VIDEO,
+		.em_type	= EM2751,
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2751_BOARD_EMPIA_SAMPLE] = {
+		.name          = "EM2751 Webcam + Audio",
+		.vchannels     = 1,
+		.norm	       = V4L2_STD_PAL_BG,
+		.dev_modes      = EM28XX_VIDEO,
+		.em_type	= EM2751,
+		.input         = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 0,
+			.amux     = 0,
+		} },
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+		} },
+	},
+	[EM2861_BOARD_PLEXTOR_PX_TV100U] = {
+		.name         = "Plextor ConvertX PX-TV100U",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.has_tuner    = 1,
+		.has_msp34xx  = 1,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO,  /* EM28XX_AUDIO ? */
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.tvnorms        = {{
+			.name = "NTSC",
+
+			.id = V4L2_STD_NTSC_M,
+		} },
+	},
+	[EM2863_BOARD_EMPIA_GENERIC] = {
+		.name         = "Empia generic",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = 1,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = 3,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = 2,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+#if 0
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+#endif
+
+		.manual_gpio  = 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+#if 0
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+#endif
+	},
+	[EM2883_BOARD_KWORLD_A340] = {
+		.name         = "KWorld ATSC 340",
+		.tuner_type   = TUNER_PHILIPS_TDA18271,
+		.dev_modes    = EM28XX_ATSC,
+	},
+	[EM2875_BOARD_SAMPLE_ISDBT] = {
+		.name         = "Empia ISDB",
+		.em_type      = EM2875,
+		.dev_modes    = EM28XX_ISDB,
+	},
+	[EM2879_BOARD_SAMPLE_DMB] = {
+		.name         = "Empia DMB-T",
+		.tuner_type   = TUNER_ADIMTV102,
+		.dev_modes    = EM28XX_DMB,
+	},
+	[EM2883_BOARD_EMPIA_HYBRID_ATSC] = {
+		.name         = "Empia Hybrid XS ATSC",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+#if 0
+		.powersaving  = 1,
+#endif
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI/* | EM28XX_ATSC needs more work*/,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+		.atscnorms	= EETI_XC5000_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC5000_DEFAULT_QAM,
+	},
+	[EM2883_BOARD_EQUINUX_TUBESTICK_ATSC] = {
+		.name         = "Equinux TubeStick Hybrid ATSC",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+#if 0
+		.powersaving  = 1,
+#endif
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_AUDIO | EM28XX_RADIO | EM28XX_VBI/* | EM28XX_ATSC needs more work*/,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+		.atscnorms	= EETI_XC5000_DEFAULT_ATSC,
+		.qamnorms	= EETI_XC5000_DEFAULT_QAM,
+	},
+	[EM2888_BOARD_DVB_TC_HYBRID] = {
+		.name	      = "Pinnacle 510e",
+		.em_type      = EM2888,
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_PAL_BG,
+		.tuner_type   = TUNER_XCEIVE_XC5000,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC5000_DEFAULT_ANALOG,
+		.fmnorms	= EETI_XC5000_DEFAULT_FM,
+	},
+	[EM2888_BOARD_EMPIA_HYBRID] = {
+		.name	      = "Empia Hybrid PCTV",
+		.em_type      = EM2888, /* TODO read this from the chip */
+		.vchannels    = 3,
+		.norm	      = V4L2_STD_NTSC_M,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.has_inttuner = 1,
+		.has_tuner    = 1,
+		.decoder      = EM28XX_CX25843,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO | EM28XX_RADIO,
+		.powersaving  = 1,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = CX25843_TELEVISION,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = CX25843_COMPOSITE1,
+			.amux     = 4,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = CX25843_SVIDEO,
+			.amux     = 5,
+		} },
+		.tvnorms	= EETI_XC3028L_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028L_DEFAULT_DVBT,
+		.fmnorms	= EETI_XC3028L_DEFAULT_FM,
+	},
+	[EM2860_BOARD_KAIOMY_TVNPC_U2] = {
+		/* TODO radio support */
+		.name	      = "Kaiomy TVnPC U2",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_NTSC_M,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.ir_keytab    = ir_codes_em_kworld,
+		.ir_getkey    = em2860_get_key_kaiomy,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_AUDIO2,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+	},
+	[EM2861_BOARD_POLLIN_USB_R1] = {
+		.name          = "Pollin USB-R1",
+		.vchannels     = 1,
+		.norm          = V4L2_STD_PAL_BG,
+		.decoder       = EM28XX_SAA7113,
+		.has_tuner     = 0,
+		.dev_modes      = EM28XX_VIDEO,
+		.input           = {{
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = 1,
+		}},
+		.tvnorms	= {
+			{
+				.name = "PAL-BG",
+				.id = V4L2_STD_PAL_BG,
+			},{
+				.name = "NTSC",
+				.id = V4L2_STD_NTSC,
+		}},
+	},
+	[EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H] = {
+		/* TODO: FM radio support for this Leadtek device */
+		.name         = "Leadtek PalmTop DTV 200H",
+		.vchannels    = 3,
+		.norm         = V4L2_STD_PAL_BG,
+		.has_tuner    = 1,
+		.has_inttuner = 1,
+		.tuner_type   = TUNER_XCEIVE_XC3028,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO | EM28XX_VBI | EM28XX_DVBT | EM28XX_AUDIO,
+		.input          = {{
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 0,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE1,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+		.manual_gpio	= 1,
+		.gpio_regs 	= EETI_DEFAULT_GPIO,
+		.tvnorms	= EETI_XC3028_DEFAULT_ANALOG,
+		.dvbnorms	= EETI_XC3028_DEFAULT_DVBT,
+	},
+	[EM2820_BOARD_COMPRO_VIDEO_MATE] = {
+		.name         = "Compro VideoMate ForYou/Stereo",
+		.vchannels    = 2,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_TVP5150,
+		.dev_modes    = EM28XX_VIDEO,
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = 1,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = 1,
+		} },
+	},
+};
+
+const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards);
+
+/*
+ * seems like it's possible to flash the eeprom, somehow one of my HVR 900 devices suddenly
+ * had the content of a WinTV USB 2 eeprom and thus identified itself as a Wintv USB 2
+ * device which of course didn't work...
+ *
+ */
+
+/* table of devices that work with this driver */
+struct usb_device_id em28xx_id_table [] = {
+	{ USB_DEVICE(0xeb1a, 0x2800), .driver_info = EM2800_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2820), .driver_info = EM2820_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2821), .driver_info = EM2821_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2750), .driver_info = EM2750_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2860), .driver_info = EM2860_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2861), .driver_info = EM2861_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2881), .driver_info = EM2881_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0x2870), .driver_info = EM2870_BOARD_GENERIC },
+	{ USB_DEVICE(0xeb1a, 0xe310), .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD },
+	{ USB_DEVICE(0xeb1a, 0xe320), .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD_II },
+	{ USB_DEVICE(0xeb1a, 0xe300), .driver_info = EM2861_BOARD_KWORLD_PVRTV_300U },
+	{ USB_DEVICE(0xeb1a, 0xe350), .driver_info = EM2870_BOARD_KWORLD_350U },
+	{ USB_DEVICE(0xeb1a, 0xe355), .driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0xeb1a, 0xe357), .driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0x1ae7, 0x0380), .driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0x0ccd, 0x0036), .driver_info = EM2820_BOARD_TERRATEC_CINERGY_250 },
+	{ USB_DEVICE(0x2304, 0x0208), .driver_info = EM2820_BOARD_PINNACLE_USB_2 },
+	{ USB_DEVICE(0x2040, 0x4200), .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 },
+	{ USB_DEVICE(0x2040, 0x4201), .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2 },
+	{ USB_DEVICE(0x2040, 0x650a), .driver_info = EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2 },
+	{ USB_DEVICE(0x2304, 0x0207), .driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+	{ USB_DEVICE(0x2304, 0x021a), .driver_info = EM2820_BOARD_PINNACLE_DVC_100 },
+	{ USB_DEVICE(0x2040, 0x6500), .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900 },
+	{ USB_DEVICE(0x2040, 0x6502), .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2 },
+	{ USB_DEVICE(0x0ccd, 0x0042), .driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x004f), .driver_info = EM2860_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x004c), .driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS_FR },
+	{ USB_DEVICE(0x0ccd, 0x005e), .driver_info = EM2882_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x0043), .driver_info = EM2870_BOARD_TERRATEC_XS },
+	{ USB_DEVICE(0x0ccd, 0x0047), .driver_info = EM2880_BOARD_TERRATEC_PRODIGY_XS },
+	{ USB_DEVICE(0x185b, 0x2870), .driver_info = EM2870_BOARD_COMPRO_VIDEOMATE },
+	{ USB_DEVICE(0x0413, 0x6023), .driver_info = EM2800_BOARD_LEADTEK_WINFAST_USBII },
+	{ USB_DEVICE(0x2001, 0xf112), .driver_info = EM2820_BOARD_DLINK_USB_TV },
+	{ USB_DEVICE(0xeb1a, 0x2883), .driver_info = EM2883_BOARD_GENERIC },
+	{ USB_DEVICE(0x2040, 0x6513), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x6517), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x651b), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x651f), .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2304, 0x0227), .driver_info = EM2883_BOARD_PINNACLE_PCTV_HD_PRO },
+	{ USB_DEVICE(0x2304, 0x0226), .driver_info = EM2882_BOARD_PINNACLE_HYBRID_PRO },
+	{ USB_DEVICE(0xeb1a, 0x2751), .driver_info = EM2751_BOARD_EMPIA_SAMPLE },
+	{ USB_DEVICE(0xeb1a, 0xe305), .driver_info = EM2880_BOARD_KWORLD_DVB_305U },
+	{ USB_DEVICE(0xeb1a, 0xa316), .driver_info = EM2883_BOARD_KWORLD_HYBRID_A316 },
+	{ USB_DEVICE(0x093b, 0xa005), .driver_info = EM2861_BOARD_PLEXTOR_PX_TV100U },
+	{ USB_DEVICE(0xeb1a, 0x50a6), .driver_info = EM2860_BOARD_GADMEI_UTV330 },
+	{ USB_DEVICE(0x0ccd, 0x0072), .driver_info = EM2883_BOARD_TERRATEC_HYBRID_XS_FM },
+	{ USB_DEVICE(0x0ccd, 0x0092), .driver_info = EM2883_BOARD_TERRATEC_HYBRID_XS_FM },
+	{ USB_DEVICE(0xeb1a, 0x2863), .driver_info = EM2863_BOARD_EMPIA_GENERIC },
+
+	{ USB_DEVICE(0xeb1a, 0xf306), .driver_info = EM2883_BOARD_KWORLD_HYBRID_F306 },
+	{ USB_DEVICE(0xeb1a, 0xe306), .driver_info = EM2883_BOARD_KWORLD_HYBRID_F306 }, /* development only */
+	{ USB_DEVICE(0x1b80, 0xe329), .driver_info = EM2888_BOARD_KWORLD_HYBRID_E329 },
+	{ USB_DEVICE(0xeb1a, 0x2889), .driver_info = EM2888_BOARD_EMPIA_HYBRID },
+	{ USB_DEVICE(0xeb1a, 0xe323), .driver_info = EM2883_BOARD_KWORLD_HYBRID_E323 },
+	{ USB_DEVICE(0xeb1a, 0xa139), .driver_info = EM2888_BOARD_LINCOLN_TV_FM },
+#if 0
+	{ USB_DEVICE(0x2304, 0x0242), .driver_info = EM2888_BOARD_DVB_TC_HYBRID },
+#endif
+	{ USB_DEVICE(0x0438, 0xb002), .driver_info = EM2883_BOARD_ATI_TVWONDER600 },
+	{ USB_DEVICE(0xeb1a, 0x2875), .driver_info = EM2875_BOARD_SAMPLE_ISDBT },
+	{ USB_DEVICE(0xeb1a, 0x2879), .driver_info = EM2879_BOARD_SAMPLE_DMB   },
+	{ USB_DEVICE(0xeb1a, 0xe301), .driver_info = EM2860_BOARD_KAIOMY_TVNPC_U2 },
+	{ USB_DEVICE(0x0413, 0x6f02), .driver_info = EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H },
+	{ USB_DEVICE(0x185b, 0x2041), .driver_info = EM2820_BOARD_COMPRO_VIDEO_MATE },
+	
+	{ },
+};
+EXPORT_SYMBOL(em28xx_id_table);
+
+/* TODO clean this up */
+int em28xx_card_setup(struct em28xx *dev)
+{
+	int ret = 0;
+
+#ifdef XCEIVE_EXTERNAL_FIRMWARE
+	switch(dev->tuner_type) {
+	case TUNER_XCEIVE_XC3028:
+	{
+		const struct firmware *fw;
+		static unsigned char *xc3028_firmware;
+
+		if (xc3028_firmware==NULL) {
+			ret = request_firmware(&fw, "empia_xc3028.fw", &dev->udev->dev);
+
+			if (ret) {
+				printk(KERN_INFO"Couldn't find empia_xc3028.fw, please run:\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc3028.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			}
+
+			if (fw->size != 120273) {
+				printk(KERN_INFO"Firmware size doesn't match, please be sure\n");
+				printk(KERN_INFO"that you picked the correct firmware\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc3028.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			} else 
+				printk(KERN_INFO"xc3028 firmware successfully loaded to RAM\n");
+
+
+			xc3028_firmware = kzalloc(fw->size, GFP_KERNEL);
+			memcpy(xc3028_firmware, fw->data, fw->size);
+			release_firmware(fw);
+
+			xc3028_set_firmware(xc3028_firmware, 120273);
+		}
+		break;
+	}
+	case TUNER_XCEIVE_XC5000:
+	{
+		const struct firmware *fw;
+
+		if (XC5000_firmware_SEQUENCE == NULL) {
+			ret = request_firmware(&fw, "empia_xc5000.fw", &dev->udev->dev);
+
+			if (ret) {
+				printk(KERN_INFO"Couldn't find empia_xc5000.fw, please run:\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc5000.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			}
+
+			if (fw->size != 12376) {
+				printk(KERN_INFO"Firmware size doesn't match, please be sure\n");
+				printk(KERN_INFO"that you picked the correct firmware\n");
+				printk(KERN_INFO"cd /lib/firmware\n");
+				printk(KERN_INFO"sudo wget mcentral.de/empia/empia_xc5000.fw\n");
+				printk(KERN_INFO"and replug your device\n");
+				return -EINVAL;
+			}
+
+
+			XC5000_firmware_SEQUENCE = kzalloc(fw->size, GFP_KERNEL);
+			memcpy(XC5000_firmware_SEQUENCE, &fw->data[6], fw->size-6);
+			release_firmware(fw);
+
+		}
+		break;
+	}
+	default:
+		break;
+	}
+#endif
+
+	switch (dev->model) {
+	case EM2750_BOARD_DLCW_130:
+	case EM2751_BOARD_EMPIA_SAMPLE:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x0a", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+	case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+	case EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+#ifdef EM28XX_TVEEPROM
+		struct tveeprom tv;
+		request_module("tveeprom");
+#endif
+#ifdef CONFIG_MODULES
+		request_module("ir-kbd-i2c");
+		request_module("msp3400");
+#endif
+		/* Call first TVeeprom */
+
+#ifdef EM28XX_TVEEPROM
+		dev->i2c_client.addr = 0xa0 >> 1;
+		tveeprom_hauppauge_analog(&dev->i2c_client, &tv, dev->eedata);
+
+		dev->tuner_type = tv.tuner_type;
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 26)
+		if (tv.audio_processor == AUDIO_CHIP_MSP34XX) {
+#else
+		if (tv.audio_processor == V4L2_IDENT_MSPX4XX) {
+#endif
+			dev->i2s_speed = 2048000;
+			dev->has_msp34xx = 1;
+		} else
+			dev->has_msp34xx = 0;
+
+		if (dev->has_msp34xx) {
+			/* Send a reset to other chips via gpio */
+			ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xf7", 1);
+			if (ret < 0)
+				return ret;
+			udelay(2500);
+			ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xff", 1);
+			if (ret < 0)
+				return ret;
+			udelay(2500);
+		}
+#endif
+
+		break;
+	}
+	case EM2820_BOARD_KWORLD_PVRTV2800RF:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* GPIO enables sound on KWORLD PVR TV 2800RF */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xf9", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#ifdef CONFIG_MODULES
+		request_module("tveeprom");
+#endif
+		break;
+	}
+	case EM2820_BOARD_MSI_VOX_USB_2:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* enables audio for that device */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x04, "\x08", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		break;
+	}
+
+	case EM2880_BOARD_TERRATEC_HYBRID_XS:
+	case EM2860_BOARD_TERRATEC_HYBRID_XS:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x97", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2882_BOARD_TERRATEC_HYBRID_XS:
+	case EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x07", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+	case EM2861_BOARD_KWORLD_PVRTV_300U:
+	case EM2880_BOARD_KWORLD_DVB_310U:
+	case EM2880_BOARD_KWORLD_DVB_305U:
+	case EM2880_BOARD_MSI_DIGIVOX_AD:
+	case EM2880_BOARD_MSI_DIGIVOX_AD_II:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+/*		case EM2870_BOARD_COMPRO_VIDEOMATE: TODO */
+	case EM2870_BOARD_PINNACLE_PCTV_DVB:
+	{
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		/* this device needs some gpio writes to get the DVB-T demod work */
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xde", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x22", 1); /* switch em2880 rc protocol */
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2870_BOARD_TERRATEC_XS_MT2060:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* this device needs some gpio writes to get the DVB-T demod work */
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xde", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = dev->em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		break;
+	}
+	case EM2870_BOARD_KWORLD_350U:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xdf", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xde", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfe", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6e", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7e", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2870_BOARD_KWORLD_355U:
+	case EM2870_BOARD_COMPRO_VIDEOMATE:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* TODO: someone can do some cleanup here... not everything's needed */
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x04, "\x01", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xfc", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xdc", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		ret = em28xx_write_regs(dev, 0x08, "\xfc", 1);
+		if (ret < 0)
+			return ret;
+		mdelay(70);
+		break;
+	}
+
+	case EM2883_BOARD_PINNACLE_PCTV_HD_PRO:
+	case EM2882_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+	case EM2883_BOARD_KWORLD_HYBRID_E323:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x04, "\x08", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x2d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x3d", 1);
+		if (ret < 0)
+			return ret;
+
+		/* enable audio 12 mhz i2s*/
+		ret = em28xx_write_regs(dev, 0x0f, "\xa7", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+	case EM2883_BOARD_ATI_TVWONDER600:
+	case EM2883_BOARD_KWORLD_HYBRID_A316:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x04, "\x00", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x04, "\x08", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x2d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x08, "\x3d", 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, 0x0f, "\xa7", 1); /* enable audio 12 mhz i2s*/
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		break;
+	}
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	{
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x08, "\xff", 1);
+		if (ret < 0)
+			return ret;
+		msleep(5);
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1); /* switch em2880 rc protocol */
+		if (ret < 0)
+			return ret;
+#if 0
+
+		ret = dev->em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = dev->em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#endif
+#if 0
+		em2880_ir_attach(dev, ir_codes_pinnacle2, ARRAY_SIZE(ir_codes_pinnacle2), em2880_get_key_empia);
+#endif
+		break;
+	}
+	case EM2820_BOARD_GADMEI_UTV310:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* Turn on analog audio output */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2860_BOARD_GADMEI_UTV330:
+	{
+		/* Turn on IR */
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x07", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2861_BOARD_PLEXTOR_PX_TV100U:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		/* Turn on analog audio output */
+		ret = em28xx_write_regs_req(dev, 0x00, 0x08, "\xfd", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2888_BOARD_KWORLD_HYBRID_E329:
+	case EM2888_BOARD_EMPIA_HYBRID:
+	case EM2888_BOARD_LINCOLN_TV_FM:
+	{
+		ret = em28xx_write_regs(dev, 0x80, "\xf0", 1);
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x80, "\xf4", 1);
+		msleep(50);
+		ret = em28xx_write_regs(dev, 0x80, "\xf6", 1);
+		ret = em28xx_write_regs(dev, 0x80, "\xb6", 1);
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x80, "\xf6", 1);
+		msleep(100);
+		ret = em28xx_write_regs(dev, 0x80, "\xf5", 1);
+		if (ret < 0)
+			return ret;
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x45", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x02", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, 0x50, "\x01", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2883_BOARD_EMPIA_HYBRID_ATSC:
+	case EM2883_BOARD_EQUINUX_TUBESTICK_ATSC:
+	case EM2883_BOARD_TERRATEC_HYBRID_XS_FM:
+	case EM2863_BOARD_EMPIA_GENERIC:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x97", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x2d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#if 0
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+#endif
+		break;
+	}
+	case EM2860_BOARD_KAIOMY_TVNPC_U2:
+	{
+		ret = em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x4c", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x6d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x08, "\x7d", 1);
+		if (ret < 0)
+			return ret;
+		msleep(10);
+		ret = em28xx_write_regs(dev, 0x0f, "\x07", 1);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	case EM2875_BOARD_SAMPLE_ISDBT:
+	{
+		//            em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1); // EEPROM access
+		em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x44", 1);
+		em28xx_write_regs(dev, 0x0f, "\x02", 1);
+		em28xx_write_regs(dev, 0x50, "\x0e", 1);
+		em28xx_write_regs(dev, 0x80, "\xf0", 1);
+		break;
+	}
+
+	default:
+		ret = dev->em28xx_write_regs(dev, R0F_XCLK_REG, "\x27", 1);
+		if (ret < 0)
+			return ret;
+		if (dev->em_type != EM2800) {
+			ret = em28xx_write_regs(dev, R06_I2C_CLK_REG, "\x40", 1);
+			if (ret < 0)
+				return ret;
+		}
+	}
+	return 0;
+}
+
+void em28xx_card_disconnect(struct em28xx *dev)
+{
+	if (dev->ir_em2880)
+		em2880_ir_detach(dev);
+}
+
+EXPORT_SYMBOL(em28xx_boards);
+EXPORT_SYMBOL(em28xx_bcount);
+
+MODULE_DEVICE_TABLE (usb, em28xx_id_table);
diff --git a/drivers/media/video/empia/em28xx-core.c b/drivers/media/video/empia/em28xx-core.c
new file mode 100644
index 0000000..48b4b8c
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-core.c
@@ -0,0 +1,1366 @@
+/*
+   em28xx-core.c - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices
+
+   Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+		      Markus Rechberger <mrechberger@gmail.com>
+		      Mauro Carvalho Chehab <mchehab@infradead.org>
+		      Sascha Sommer <saschasommer@freenet.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; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+
+#include "em28xx.h"
+
+/* #define ENABLE_DEBUG_ISOC_FRAMES */
+
+static unsigned int core_debug;
+module_param(core_debug, int, 0644);
+MODULE_PARM_DESC(core_debug, "enable debug messages [core]");
+
+#define em28xx_coredbg(fmt, arg...) do {\
+	if (core_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __FUNCTION__ , ##arg); } while (0)
+
+static unsigned int reg_debug;
+module_param(reg_debug, int, 0644);
+MODULE_PARM_DESC(reg_debug, "enable debug messages [URB reg]");
+
+#define em28xx_regdbg(fmt, arg...) do {\
+	if (reg_debug) \
+		printk(KERN_INFO "%s: "fmt, \
+			 dev->name, ##arg); } while (0)
+
+static unsigned int isoc_debug;
+module_param(isoc_debug, int, 0644);
+MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
+
+#define em28xx_isocdbg(fmt, arg...) do {\
+	if (isoc_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __FUNCTION__ , ##arg); } while (0)
+
+static int alt = EM28XX_PINOUT;
+module_param(alt, int, 0644);
+MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint");
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+static void *rvmalloc(size_t size)
+{
+	void *mem;
+	unsigned long adr;
+
+	size = PAGE_ALIGN(size);
+
+	mem = vmalloc_32((unsigned long)size);
+	if (!mem)
+		return NULL;
+
+	adr = (unsigned long)mem;
+	while (size > 0) {
+		SetPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	return mem;
+}
+
+static void rvfree(void *mem, size_t size)
+{
+	unsigned long adr;
+
+	if (!mem)
+		return;
+
+	size = PAGE_ALIGN(size);
+
+	adr = (unsigned long)mem;
+	while (size > 0) {
+		ClearPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vfree(mem);
+}
+#endif
+
+/*
+ * em28xx_request_buffers()
+ * allocate a number of buffers
+ */
+u32 em28xx_request_buffers(struct em28xx *dev, u32 count, int type)
+{
+	size_t size;
+
+	void *buff = NULL;
+	u32 i;
+
+
+	size = PAGE_ALIGN((type == EM28XX_VBI) ?
+			dev->vbi_frame_size : dev->frame_size);
+
+	switch (type) {
+	case EM28XX_VIDEO:
+		if (count > EM28XX_NUM_FRAMES)
+			count = EM28XX_NUM_FRAMES;
+
+		if (dev->num_frames > 0)
+			return -EINVAL;
+
+		dev->num_frames = count;
+		size = PAGE_ALIGN(dev->frame_size);
+		break;
+	case EM28XX_VBI:
+		if (count > EM28XX_NUM_FRAMES) /* FIXME VBI_NUM_FRAMES */
+			count = EM28XX_NUM_FRAMES;
+
+		if (dev->vbi_num_frames > 0)
+			return -EINVAL;
+
+		dev->vbi_num_frames = count;
+		size = PAGE_ALIGN(dev->vbi_frame_size);
+		break;
+	default:
+		printk(KERN_INFO"em28xx-core.c: error wrong buffer request!\n");
+		return -EINVAL;
+	}
+
+	em28xx_coredbg("requested %i buffers with size %zi", count, size);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+	buff = rvmalloc(count * size);
+#else
+	buff = vmalloc_32(count * size);
+#endif
+	if (buff)
+		memset(buff, 0, count * size);
+	else
+		return -ENOMEM;
+
+	for (i = 0; i < count; i++) {
+		switch (type) {
+		case EM28XX_VBI:
+			dev->vbi_frame[i].bufmem = buff + i * size;
+			dev->vbi_frame[i].buf.index = i;
+			dev->vbi_frame[i].buf.m.offset = i * size;
+			dev->vbi_frame[i].buf.length = dev->vbi_frame_size;
+			dev->vbi_frame[i].buf.type = V4L2_BUF_TYPE_VBI_CAPTURE;
+			dev->vbi_frame[i].buf.sequence = 0;
+
+/* Indicates the field order of the image in the buffer, see Table 3-8>. This
+   field is not used when the buffer contains VBI data. Drivers must set it 
+   when type refers to an input stream, applications when an output stream. 
+ 
+   this probably should be changed since we can have an interlaced field here 
+*/
+			if (dev->vbi_interlaced)
+				dev->vbi_frame[i].buf.field = V4L2_VBI_INTERLACED;
+			else
+				dev->vbi_frame[i].buf.field = V4L2_FIELD_NONE;
+			dev->vbi_frame[i].buf.memory = V4L2_MEMORY_MMAP;
+			dev->vbi_frame[i].buf.flags = 0;
+			break;
+		case EM28XX_VIDEO:
+			dev->frame[i].bufmem = buff + i * size;
+			dev->frame[i].buf.index = i;
+			dev->frame[i].buf.m.offset = i * size;
+			dev->frame[i].buf.length = dev->frame_size;
+			dev->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+			dev->frame[i].buf.sequence = 0;
+			dev->frame[i].buf.field = V4L2_FIELD_INTERLACED; /*_TB;  V4L2_FIELD_NONE; */
+			dev->frame[i].buf.memory = V4L2_MEMORY_MMAP;
+			dev->frame[i].buf.flags = 0;
+			break;
+		default:
+			printk(KERN_INFO"error wrong buffer request!\n");
+			return -EINVAL;
+		}
+
+	}
+	return count;
+}
+
+/*
+ * em28xx_queue_unusedframes()
+ * add all frames that are not currently in use to the inbuffer queue
+ */
+void em28xx_queue_unusedframes(struct em28xx *dev, int type)
+{
+	unsigned long lock_flags;
+	u32 i;
+	switch (type) {
+	case EM28XX_VIDEO:
+		for (i = 0; i < dev->num_frames; i++) {
+			if (dev->frame[i].state == F_UNUSED) {
+				spin_lock_irqsave(&dev->queue_lock, lock_flags);
+				dev->frame[i].state = F_QUEUED;
+				list_add_tail(&dev->frame[i].frame,
+						&dev->inqueue);
+				spin_unlock_irqrestore(&dev->queue_lock,
+						lock_flags);
+			}
+		}
+		break;
+	case EM28XX_VBI:
+		for (i = 0; i < dev->vbi_num_frames; i++) {
+			if (dev->vbi_frame[i].state == F_UNUSED) {
+				spin_lock_irqsave(&dev->vbi_queue_lock,
+						lock_flags);
+				dev->vbi_frame[i].state = F_QUEUED;
+				list_add_tail(&dev->vbi_frame[i].frame,
+						&dev->vbi_inqueue);
+				spin_unlock_irqrestore(&dev->vbi_queue_lock,
+						lock_flags);
+			}
+		}
+		break;
+	}
+}
+
+/*
+ * em28xx_release_buffers()
+ * free frame buffers
+ */
+void em28xx_release_buffers(struct em28xx *dev, int type)
+{
+	if (type == EM28XX_VIDEO && dev->num_frames) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		rvfree(dev->frame[0].bufmem,
+				dev->num_frames *
+				PAGE_ALIGN(dev->frame[0].buf.length));
+#else
+		vfree(dev->frame[0].bufmem);
+		dev->num_frames = 0;
+#endif
+	} else if (type == EM28XX_VBI && dev->vbi_num_frames) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		rvfree(dev->vbi_frame[0].bufmem,
+				dev->vbi_num_frames *
+				PAGE_ALIGN(dev->vbi_frame[0].buf.length));
+#else
+		vfree(dev->vbi_frame[0].bufmem);
+		dev->vbi_num_frames = 0;
+#endif
+	}
+}
+
+/*
+ * em28xx_read_reg_req()
+ * reads data from the usb device specifying bRequest
+ */
+int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg,
+				   char *buf, int len)
+{
+	int ret, byte;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), req,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, buf, len, HZ);
+
+	if (reg_debug) {
+		em28xx_regdbg("%s c0 %02x 00 00 %02x 00 %02x 00 <<<",
+				(ret < 0) ? "[NOK]" : "[ OK]", req, reg, len);
+
+		for (byte = 0; byte < len; byte++)
+			printk(" %02x", (unsigned char)buf[byte]);
+		printk("\n");
+	}
+	return ret;
+}
+
+/*
+ * em28xx_read_reg_req()
+ * reads data from the usb device specifying bRequest
+ */
+int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg)
+{
+	u8 val;
+	int ret;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return(-ENODEV);
+
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), req,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, &val, 1, HZ);
+
+	if (reg_debug) {
+		if (ret < 0)
+			em28xx_regdbg("%s c0 %02x 00 00 %02x 00 01 00 <<<\n",
+					(ret < 0) ? "[NOK]" : "[ OK]", req,
+					reg);
+		else
+			em28xx_regdbg("%s c0 %02x 00 00 %02x 00 01 00 <<< "
+				      "%02x\n", (ret < 0) ? "[NOK]" : "[ OK]",
+				      req, reg, (unsigned char)val);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+int em28xx_read_reg(struct em28xx *dev, u16 reg)
+{
+	return em28xx_read_reg_req(dev, USB_REQ_GET_STATUS, reg);
+}
+
+/*
+ * em28xx_write_regs_req()
+ * sends data to the usb device, specifying bRequest
+ */
+int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf,
+				 int len)
+{
+	int ret;
+
+	/*usb_control_msg seems to expect a kmalloced buffer */
+	unsigned char *bufs;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return(-ENODEV);
+
+	bufs = kmalloc(len, GFP_KERNEL);
+
+	if (!bufs)
+		return -ENOMEM;
+	memcpy(bufs, buf, len);
+
+	ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), req,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, bufs, len, HZ);
+	if (reg_debug) {
+		int i;
+		em28xx_regdbg("%s 40 %02x 00 00 %02x 00 %02x 00 >>>",
+				(ret < 0) ? "[NOK]" : "[ OK]", req, reg, len);
+		for (i = 0; i < len; ++i)
+			printk(" %02x", (unsigned char)buf[i]);
+		printk("\n");
+	}
+	kfree(bufs);
+	return ret;
+}
+
+int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len)
+{
+	return em28xx_write_regs_req(dev, USB_REQ_GET_STATUS, reg, buf, len);
+}
+
+/*
+ * em28xx_write_reg_bits()
+ * sets only some bits (specified by bitmask) of a register, by first reading
+ * the actual value
+ */
+int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val,
+				 u8 bitmask)
+{
+	int oldval;
+	u8 newval;
+	oldval = em28xx_read_reg(dev, reg);
+	if (oldval < 0)
+		return oldval;
+	newval = (((u8) oldval) & ~bitmask) | (val & bitmask);
+	return em28xx_write_regs(dev, reg, &newval, 1);
+}
+
+/*
+ * em28xx_write_ac97()
+ * write a 16 bit value to the specified AC97 address (LSB first!)
+ */
+int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val)
+{
+	int ret;
+	u8 addr;
+	switch (dev->model) {
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+		addr = 0x1a;
+		break;
+	default:
+		addr = reg & 0x7f;
+	}
+	
+	ret = em28xx_read_reg(dev, R43_AC97BUSY_REG);
+	if (ret < 0)
+		return ret;
+	else if (((u8) ret) & 0x01) 
+		return 0;
+	
+	ret = em28xx_write_regs(dev, R40_AC97LSB_REG, val, 2);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R42_AC97ADDR_REG, &addr, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_audio_analog_set(struct em28xx *dev)
+{
+	char s[2] = { 0x00, 0x00 };
+
+	switch (dev->model) {
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2881_BOARD_DNT_DA2_HYBRID:
+		s[0] = 0x05;
+		s[1] = 0x05;
+		break;
+	case EM2861_BOARD_PLEXTOR_PX_TV100U:
+	case EM2860_BOARD_GADMEI_UTV330:
+		switch (dev->ctl_ainput) {
+		case 0:
+			s[0] = 0xfd;
+			break;
+		case 1:
+			s[0] = 0xfc;
+			break;
+		default :
+			s[0] = 0xfe;
+			break;
+		}
+		if (dev->mute)
+			s[0] = 0xfe;
+		return em28xx_write_regs(dev, 0x08, s, 1);
+	default:
+		s[0] |= 0x1f - dev->volume;
+		s[1] |= 0x1f - dev->volume;
+	}
+
+	if (dev->mute)
+		s[1] |= 0x80;
+	return em28xx_write_ac97(dev, R02_MASTER_AC97, s);
+}
+
+int em28xx_colorlevels_set_default(struct em28xx *dev)
+{
+	int ret;
+
+	/* contrast */
+	ret = em28xx_write_regs(dev, R20_YGAIN_REG, "\x10", 1);
+	if (ret < 0)
+		return ret;
+
+	/* brightness */
+	ret = em28xx_write_regs(dev, R21_YOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	/* saturation */
+	ret = em28xx_write_regs(dev, R22_UVGAIN_REG, "\x10", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R23_UOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R24_VOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R25_SHARPNESS_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R14_GAMMA_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R15_RGAIN_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R16_GGAIN_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R17_BGAIN_REG, "\x20", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R18_ROFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R19_GOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1A_BOFFSET_REG, "\x00", 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_capture_start(struct em28xx *dev, int start)
+{
+	int ret;
+	/* FIXME: which is the best order? */
+	/* video registers are sampled by VREF */
+	if ((ret = em28xx_write_reg_bits(dev, R0C_USBSUSP_REG, start ?
+					0x10 : 0x00,
+					  0x10)) < 0)
+		return ret;
+	/* enable video capture */
+	return em28xx_write_regs(dev, R12_VINENABLE_REG, start ?
+			"\x67" : "\x27", 1);
+}
+
+int em28xx_outfmt_set_yuv422(struct em28xx *dev)
+{
+	int ret;
+
+	/* maybe put this configuration into the card configstruct,
+	   it's not entirely clear what values are needed for em2750
+	   boards so this is a hacking place at the moment */
+
+	if (dev->em_type == EM2750 || dev->em_type == EM2751) {
+		ret = em28xx_write_regs(dev, R11_VINCTRL_REG, "\x00", 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, R10_VINMODE_REG, "\x0a", 1);
+		if (ret < 0)
+			return ret;
+	} else {
+		u8 fmt_val;
+
+		fmt_val = ((u8)em28xx_read_reg(dev, R27_OUTFMT_REG) & ~0x1f) | dev->outfmt->config;
+
+		ret = em28xx_write_regs(dev, R27_OUTFMT_REG, &fmt_val, 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, R10_VINMODE_REG, "\x10", 1);
+		if (ret < 0)
+			return ret;
+
+		ret = em28xx_write_regs(dev, R11_VINCTRL_REG, "\x11", 1);
+		if (ret < 0)
+			return ret;
+	}
+	if (dev->dev_modes & EM28XX_VBI)
+		em28xx_set_vbi(dev, 1);
+	return 0;
+}
+
+int em28xx_set_vbi(struct em28xx *dev, int enable)
+{
+	u8 vic;
+	int ret;
+	ret = em28xx_write_regs_req(dev, 0x00, 0x34,
+			&dev->tvnorm->vbi_h_start, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs_req(dev, 0x00, 0x35,
+			&dev->tvnorm->vbi_v_start, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs_req(dev, 0x00, 0x36,
+			&dev->tvnorm->vbi_w, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs_req(dev, 0x00, 0x37,
+			&dev->tvnorm->vbi_h, 1);
+	if (ret < 0)
+		return ret;
+
+	if (enable)
+		vic = em28xx_read_reg(dev, R11_VINCTRL_REG) | 0x48;
+	else
+		vic = em28xx_read_reg(dev, R11_VINCTRL_REG) & ~0x48;
+
+	ret = em28xx_write_regs(dev, R11_VINCTRL_REG, &vic , 1);
+
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, u8 ymin,
+				  u8 ymax)
+{
+	int ret;
+	em28xx_coredbg("em28xx Scale: (%d,%d)-(%d,%d)\n", xmin,
+			ymin, xmax, ymax);
+
+	ret = em28xx_write_regs(dev, R28_XMIN_REG, &xmin, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R29_XMAX_REG, &xmax, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R2A_YMIN_REG, &ymin, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R2B_YMAX_REG, &ymax, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart,
+				   u16 width, u16 height)
+{
+	int ret;
+	u8 cwidth = width;
+	u8 cheight = height;
+	u8 overflow = (height >> 7 & 0x02) | (width >> 8 & 0x01);
+
+	em28xx_coredbg("em28xx Area Set: (%d,%d)\n",
+			(width | (overflow & 2) << 7),
+			(height | (overflow & 1) << 8));
+
+	ret = em28xx_write_regs(dev, R1C_HSTART_REG, &hstart, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1D_VSTART_REG, &vstart, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1E_CWIDTH_REG, &cwidth, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1F_CHEIGHT_REG, &cheight, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, R1B_OFLOW_REG, &overflow, 1);
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v)
+{
+	int ret;
+	u8 mode;
+	u8 buf[2];
+	/* the em2800 scaler only supports scaling down to 50% */
+
+	switch (dev->em_type) {
+	case EM2750:
+#if 1 /* TODO: PLEASE VERIFY */
+		mode = (h || v)? 0x30: 0x00;
+		ret = em28xx_write_regs(dev, R30_HSCALELOW_REG, "\x00\x30", 2);
+		if (ret < 0)
+			return ret;
+		ret = em28xx_write_regs(dev, R32_VSCALELOW_REG, "\x00\x30", 2);
+		if (ret < 0)
+			return ret;
+#endif
+		break;
+	case EM2800:
+		mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00);
+		break;
+	default:
+		buf[0] = h;
+		buf[1] = h >> 8;
+		ret = em28xx_write_regs(dev, R30_HSCALELOW_REG, (char *)buf, 2);
+		if (ret < 0)
+			return ret;
+		buf[0] = v;
+		buf[1] = v >> 8;
+		ret = em28xx_write_regs(dev, R32_VSCALELOW_REG, (char *)buf, 2);
+		if (ret < 0)
+			return ret;
+		/* it seems that both H and V scalers must be active to work
+		   correctly */
+		mode = (h || v)? 0x30: 0x00;
+	}
+	ret = em28xx_write_reg_bits(dev, R26_COMPR_REG, mode, 0x30);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+/* FIXME: this only function read values from dev */
+int em28xx_resolution_set(struct em28xx *dev)
+{
+	int width, height;
+	width = norm_maxw(dev);
+	height = norm_maxh(dev) >> 1;
+
+	em28xx_outfmt_set_yuv422(dev);
+	em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2);
+	em28xx_capture_area_set(dev, 0, 0, width >> 2, height >> 2);
+	return em28xx_scaler_set(dev, dev->hscale, dev->vscale);
+}
+
+
+/******************* isoc transfer handling ****************************/
+
+#ifdef ENABLE_DEBUG_ISOC_FRAMES
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static void em28xx_isoc_dump(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_isoc_dump(struct urb *urb)
+#endif
+{
+	int len = 0;
+	int ntrans = 0;
+	int i;
+
+	printk(KERN_DEBUG "isocIrq: sf=%d np=%d ec=%x\n",
+	       urb->start_frame, urb->number_of_packets,
+	       urb->error_count);
+	for (i = 0; i < urb->number_of_packets; i++) {
+		unsigned char *buf =
+				urb->transfer_buffer +
+				urb->iso_frame_desc[i].offset;
+		int alen = urb->iso_frame_desc[i].actual_length;
+		if (alen > 0) {
+			if (buf[0] == 0x88) {
+				ntrans++;
+				len += alen;
+			} else if (buf[0] == 0x22) {
+				printk(KERN_DEBUG
+						"= l=%d nt=%d bpp=%d\n",
+				len - 4 * ntrans, ntrans,
+				ntrans == 0 ? 0 : len / ntrans);
+				ntrans = 1;
+				len = alen;
+			} else
+				printk(KERN_DEBUG "!\n");
+		}
+		printk(KERN_DEBUG "   n=%d s=%d al=%d %x\n", i,
+		       urb->iso_frame_desc[i].status,
+		       urb->iso_frame_desc[i].actual_length,
+		       (unsigned int)
+				       *((unsigned char *)(urb->transfer_buffer +
+				       urb->iso_frame_desc[i].
+				       offset)));
+	}
+}
+#endif
+
+static int em28xx_isoc_video(struct em28xx *dev, struct em28xx_frame_t **f,
+				struct em28xx_frame_t **vbif,
+				unsigned long *lock_flags, unsigned char buf)
+{
+
+	if (!(buf & 0x01)) {
+		if ((*vbif)) {
+			if ((*vbif)->state == F_GRABBING) {
+				/*previous frame is incomplete */
+				if ((*vbif)->fieldbytesused < dev->vbi_field_size) {
+					(*vbif)->state = F_ERROR;
+				} else {
+					(*vbif)->state = F_DONE;
+					(*vbif)->buf.bytesused = dev->vbi_frame_size;
+				}
+			}
+			if ((*vbif)->state == F_DONE ||
+					(*vbif)->state == F_ERROR) {
+				/* move current frame to outqueue and get next free buffer from inqueue */
+				spin_lock_irqsave(&dev->vbi_queue_lock,
+						*lock_flags);
+				list_move_tail(&(*vbif)->frame,
+						&dev->vbi_outqueue);
+				if (!list_empty(&dev->vbi_inqueue))
+					(*vbif) = list_entry(
+							dev->vbi_inqueue.next,
+							struct em28xx_frame_t,
+							frame);
+				else
+					(*vbif) = NULL;
+				spin_unlock_irqrestore(&dev->vbi_queue_lock,
+						*lock_flags);
+			}
+			if ((*vbif)) {
+				do_gettimeofday(&(*vbif)->buf.timestamp);
+				(*vbif)->buf.sequence = ++dev->vbi_frame_count;
+				(*vbif)->buf.field = V4L2_FIELD_INTERLACED;
+				(*vbif)->state = F_GRABBING;
+				(*vbif)->buf.bytesused = 0;
+				(*vbif)->top_field = 1;
+				(*vbif)->fieldbytesused = 0;
+			}
+		}
+		if ((*f)) {
+			if ((*f)->state == F_GRABBING) {
+				/*previous frame is incomplete */
+				if ((*f)->fieldbytesused < dev->field_size) {
+					(*f)->state = F_ERROR;
+				} else {
+					(*f)->state = F_DONE;
+					(*f)->buf.bytesused = dev->frame_size;
+				}
+			}
+			if ((*f)->state == F_DONE || (*f)->state == F_ERROR) {
+				/* move current frame to outqueue and get next
+				   free buffer from inqueue */
+				spin_lock_irqsave(&dev->queue_lock, *lock_flags);
+				list_move_tail(&(*f)->frame, &dev->outqueue);
+				if (!list_empty(&dev->inqueue)) {
+					(*f) = list_entry(dev->inqueue.next,
+							struct em28xx_frame_t,
+							frame);
+				} else {
+					(*f) = NULL;
+				}
+				spin_unlock_irqrestore(&dev->queue_lock,
+						*lock_flags);
+			}
+			if ((*f)) {
+				do_gettimeofday(&(*f)->buf.timestamp);
+				(*f)->buf.sequence = ++dev->frame_count;
+				(*f)->buf.field = V4L2_FIELD_INTERLACED;
+				(*f)->state = F_GRABBING;
+				(*f)->buf.bytesused = 0;
+				(*f)->top_field = 1;
+				(*f)->fieldbytesused = 0;
+			}
+		}
+	} else {
+		if ((*vbif)) {
+			if ((*vbif)->state == F_QUEUED)
+				(*vbif)->top_field = 0;
+			else if ((*vbif)->state == F_GRABBING) {
+				if (!(*vbif)->top_field)
+					(*vbif)->state = F_ERROR;
+				else if ((*vbif)->fieldbytesused <
+						dev->vbi_field_size - 172)
+					(*vbif)->state = F_ERROR;
+				else {
+					(*vbif)->top_field = 0;
+					(*vbif)->fieldbytesused = 0;
+				}
+			}
+		}
+
+		if ((*f)) {
+			if ((*f)->state == F_GRABBING) {
+				if (!(*f)->top_field)
+					(*f)->state = F_ERROR;
+				else if ((*f)->fieldbytesused <
+						dev->field_size - 172)
+					(*f)->state = F_ERROR;
+				else {
+					(*f)->top_field = 0;
+					(*f)->fieldbytesused = 0;
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+static void em28xx_isoc_video_copy(struct em28xx *dev,
+					  struct em28xx_frame_t **f, unsigned char *buf, int len, int vbioffset)
+{
+	void *fieldstart, *startwrite, *startread;
+	int linesdone, currlinedone, offset, lencopy, remain;
+
+	if (dev->frame_size != (*f)->buf.length)
+		return;
+
+	if (vbioffset > 0) {
+		startread = buf+vbioffset;
+		len -= vbioffset;
+	} else {
+		startread = buf+4;
+		len -= 4;
+	}
+
+	if ((*f)->fieldbytesused + len > dev->field_size)
+		len = dev->field_size - (*f)->fieldbytesused;
+
+	remain = len;
+
+
+	if ((*f)->top_field)
+		fieldstart = (*f)->bufmem;
+	else
+		fieldstart = (*f)->bufmem + dev->bytesperline;
+
+	linesdone = (*f)->fieldbytesused / dev->bytesperline;
+	currlinedone = (*f)->fieldbytesused % dev->bytesperline;
+	offset = linesdone * dev->bytesperline * 2 + currlinedone;
+	startwrite = fieldstart + offset;
+	lencopy = dev->bytesperline - currlinedone;
+	lencopy = lencopy > remain ? remain : lencopy;
+
+	memcpy(startwrite, startread, lencopy);
+	remain -= lencopy;
+
+	while (remain > 0) {
+		startwrite += lencopy + dev->bytesperline;
+		startread += lencopy;
+		if (dev->bytesperline > remain)
+			lencopy = remain;
+		else
+			lencopy = dev->bytesperline;
+
+		memcpy(startwrite, startread, lencopy);
+		remain -= lencopy;
+	}
+
+	(*f)->fieldbytesused += len;
+}
+
+static int em28xx_isoc_vbi_copy(struct em28xx *dev,
+		struct em28xx_frame_t **vbif, unsigned char *buf, int len)
+{
+	void *fieldstart, *startwrite, *startread;
+	int linesdone, currlinedone, offset, lencopy, remain;
+
+	if (((*vbif) && dev->vbi_dropbytes == 0) &&
+		!((*vbif)->top_field == 0 && (*vbif)->state == F_QUEUED)) {
+		if ((*vbif)->fieldbytesused < dev->vbi_field_size) {
+			if ((*vbif)->fieldbytesused + (len-4) >
+					dev->vbi_field_size) {
+				len = dev->vbi_field_size -
+					(*vbif)->fieldbytesused;
+
+				startread = buf+4;
+				remain = len-4;
+
+				if ((*vbif)->top_field)
+					fieldstart = (*vbif)->bufmem;
+				else {
+					if (dev->vbi_interlaced)
+						fieldstart = (*vbif)->bufmem +
+							dev->vbi_bytesperline;
+					else
+						fieldstart = (*vbif)->bufmem +
+							dev->vbi_field_size;
+				}
+
+				linesdone = (*vbif)->fieldbytesused /
+					dev->vbi_bytesperline;
+				currlinedone = (*vbif)->fieldbytesused %
+					dev->vbi_bytesperline;
+
+				if (dev->vbi_interlaced)
+					offset = linesdone * dev->vbi_bytesperline *
+						2 + currlinedone;
+				else
+					offset = linesdone * dev->vbi_bytesperline +
+						currlinedone;
+
+				startwrite = fieldstart + offset;
+				lencopy = dev->vbi_bytesperline - currlinedone;
+				lencopy = lencopy > remain ? remain : lencopy;
+
+				memcpy(startwrite, startread, lencopy);
+				remain -= lencopy;
+
+				while (remain > 0) {
+					startwrite += lencopy;
+					if (dev->vbi_interlaced)
+						startwrite += dev->vbi_bytesperline;
+
+					startread += lencopy;
+					if (dev->vbi_bytesperline > remain)
+						lencopy = remain;
+					else
+						lencopy = dev->vbi_bytesperline;
+
+					memcpy(startwrite, startread, lencopy);
+					remain -= lencopy;
+				}
+				(*vbif)->fieldbytesused = dev->vbi_field_size;
+			} else {
+				if ((*vbif)->top_field)
+					fieldstart = (*vbif)->bufmem;
+				else {
+					if (dev->vbi_interlaced)
+						fieldstart = (*vbif)->bufmem + 
+							dev->vbi_bytesperline;
+					else 
+						fieldstart = (*vbif)->bufmem +
+							dev->vbi_field_size;
+				}
+
+				startread = buf+4;
+				remain = len-4;
+
+				linesdone = (*vbif)->fieldbytesused /
+					dev->vbi_bytesperline;
+				currlinedone = (*vbif)->fieldbytesused %
+					dev->vbi_bytesperline;
+				if (dev->vbi_interlaced)
+					offset = linesdone * dev->vbi_bytesperline *
+						2 + currlinedone;
+				else
+					offset = linesdone * dev->vbi_bytesperline +
+						currlinedone;
+
+				startwrite = fieldstart + offset;
+				lencopy = dev->vbi_bytesperline - currlinedone;
+				lencopy = lencopy > remain ? remain : lencopy;
+
+				memcpy(startwrite, startread, lencopy);
+				remain -= lencopy;
+
+				while (remain > 0) {
+					startwrite += lencopy;
+
+					if (dev->vbi_interlaced)
+						startwrite += dev->vbi_bytesperline;
+
+					startread += lencopy;
+					if (dev->vbi_bytesperline > remain)
+						lencopy = remain;
+					else
+						lencopy = dev->vbi_bytesperline;
+					memcpy(startwrite, startread, lencopy);
+					remain -= lencopy;
+				}
+				(*vbif)->fieldbytesused += (len-4);
+				len -= 4;
+			}
+		} else {
+			len = -4;
+		}
+	} else {
+		if (dev->vbi_dropbytes < dev->vbi_field_size) {
+			if (dev->vbi_dropbytes + (len-4) >
+					dev->vbi_field_size) {
+				len = dev->vbi_field_size - dev->vbi_dropbytes;
+				dev->vbi_dropbytes = dev->vbi_field_size;
+			} else {
+				dev->vbi_dropbytes += (len-4);
+				len -= 4;
+			}
+		} else {
+			len = -4;
+		}
+	}
+	return len;
+}
+
+/*
+ * em28xx_isoIrq()
+ * handles the incoming isoc urbs and fills the frames from our inqueue
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+void em28xx_isocIrq(struct urb *urb, struct pt_regs *regs)
+#else
+static void em28xx_isocIrq(struct urb *urb)
+#endif
+{
+	struct em28xx *dev = urb->context;
+	int i, status;
+	struct em28xx_frame_t **f, **vbif;
+	unsigned long lock_flags;
+	int vbioffset = 0;
+
+	if (!dev)
+		return;
+#ifdef ENABLE_DEBUG_ISOC_FRAMES
+	if (isoc_debug > 1)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+		em28xx_isoc_dump(urb, regs);
+#else
+		em28xx_isoc_dump(urb);
+#endif
+#endif
+	if (urb->status == -ENOENT)
+		return;
+
+	f = &dev->frame_current;
+	vbif = &dev->vbi_frame_current;
+
+	if (dev->video_stream == STREAM_INTERRUPT) {
+		dev->video_stream = STREAM_OFF;
+		if ((*f))
+			(*f)->state = F_QUEUED;
+		(*f) = NULL;
+
+		em28xx_isocdbg("stream interrupted");
+		wake_up_interruptible(&dev->video_wait_stream);
+	}
+
+	if (dev->vbi_stream == STREAM_INTERRUPT) {
+		dev->vbi_stream = STREAM_OFF;
+		if ((*vbif)) {
+			if ((*vbif)->fieldbytesused)
+				dev->vbi_dropbytes = (*vbif)->fieldbytesused;
+
+			(*vbif)->state = F_QUEUED;
+		}
+		(*vbif) = NULL;
+		wake_up_interruptible(&dev->vbi_wait_stream);
+	}
+
+	if ((dev->state & DEV_DISCONNECTED) || (dev->state & DEV_MISCONFIGURED))
+		return;
+
+	if (dev->video_stream == STREAM_ON || dev->vbi_stream == STREAM_ON) {
+		if (dev->video_stream == STREAM_ON &&
+			!list_empty(&dev->inqueue) &&
+			!(*f))
+
+			(*f) = list_entry(dev->inqueue.next,
+					struct em28xx_frame_t, frame);
+
+		if ((dev->dev_modes & EM28XX_VBI) &&
+			dev->vbi_stream == STREAM_ON &&
+			!list_empty(&dev->vbi_inqueue) &&
+			!(*vbif))
+
+			(*vbif) = list_entry(dev->vbi_inqueue.next,
+					struct em28xx_frame_t, frame);
+
+		for (i = 0; i < urb->number_of_packets; i++) {
+			unsigned char *buf = urb->transfer_buffer +
+					urb->iso_frame_desc[i].offset;
+			int len = urb->iso_frame_desc[i].actual_length;
+
+			if (urb->iso_frame_desc[i].status) {
+				em28xx_isocdbg("data error: [%d] len=%d, "
+						"status=%d", i,
+					urb->iso_frame_desc[i].actual_length,
+					urb->iso_frame_desc[i].status);
+
+				if (urb->iso_frame_desc[i].status != -EPROTO) {
+					em28xx_isocdbg("continue, len=%d\n",
+						urb->iso_frame_desc[i].actual_length);
+					continue;
+				}
+			}
+			if (urb->iso_frame_desc[i].actual_length <= 0) {
+				em28xx_isocdbg("packet %d is empty\n", i);
+				continue;
+			}
+
+			if (urb->iso_frame_desc[i].actual_length >
+						 dev->max_pkt_size) {
+				em28xx_isocdbg("packet bigger than packet "
+					       "size\n");
+				continue;
+			}
+#if 0
+			if (buf[0] != 0x88 && buf[1] != 0x88)
+				printk(KERN_DEBUG"headings: %02x %02x\n",
+						buf[0], buf[1]);
+#endif
+			if (buf[0] == 0x22 && buf[1] == 0x5a) {
+				em28xx_isocdbg("Video frame, length=%i!\n",
+						len);
+				dev->vbi_dropbytes = 0;
+				em28xx_isoc_video(dev, f, vbif, &lock_flags,
+						buf[2]);
+			} else if (buf[0] == 0x33 && buf[1] == 0x95) {
+				dev->vbi_dropbytes = 0;
+				em28xx_isoc_video(dev, f, vbif, &lock_flags,
+						buf[2]);
+			}
+
+			/* actual copying */
+			if (dev->dev_modes & EM28XX_VBI) {
+				vbioffset = em28xx_isoc_vbi_copy(dev, vbif,
+						buf, len) + 4;
+			} else
+				vbioffset = 0;
+
+			if ((*f) && (len - vbioffset > 0) &&
+					(*f)->state == F_GRABBING)
+				em28xx_isoc_video_copy(dev, f, buf, len,
+						vbioffset);
+		}
+	}
+
+	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 = 0;
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status) {
+		em28xx_errdev("resubmit of urb failed (error=%i)\n", status);
+		dev->state |= DEV_MISCONFIGURED;
+	}
+	wake_up_interruptible(&dev->wait_frame);
+	if (dev->dev_modes & EM28XX_VBI)
+		wake_up_interruptible(&dev->wait_vbi_frame);
+}
+
+/*
+ * em28xx_uninit_isoc()
+ * deallocates the buffers and urbs allocated during em28xx_init_iosc()
+ */
+void em28xx_uninit_isoc(struct em28xx *dev)
+{
+	int i;
+	for (i = 0; i < EM28XX_NUM_BUFS; i++) {
+		if (dev->urb[i]) {
+			usb_kill_urb(dev->urb[i]);
+			if (dev->transfer_buffer[i]) {
+				usb_buffer_free(dev->udev,
+					dev->urb[i]->transfer_buffer_length,
+					dev->transfer_buffer[i],
+					dev->urb[i]->transfer_dma);
+			}
+			usb_free_urb(dev->urb[i]);
+		}
+		dev->urb[i] = NULL;
+		dev->transfer_buffer[i] = NULL;
+	}
+	em28xx_capture_start(dev, 0);
+}
+
+/*
+ * em28xx_init_isoc()
+ * allocates transfer buffers and submits the urbs for isoc transfer
+ */
+int em28xx_init_isoc(struct em28xx *dev)
+{
+	/* change interface to 3 which allowes the biggest packet sizes */
+	int i, errCode;
+	const int sb_size = EM28XX_NUM_PACKETS * dev->max_pkt_size;
+
+	/* reset streaming vars */
+	dev->frame_current = NULL;
+	dev->frame_count = 0;
+
+	dev->vbi_frame_current = NULL;
+	dev->vbi_frame_count = 0;
+
+	/* allocate urbs */
+	for (i = 0; i < EM28XX_NUM_BUFS; i++) {
+		struct urb *urb;
+		int j, k;
+		/* allocate transfer buffer */
+		urb = usb_alloc_urb(EM28XX_NUM_PACKETS, GFP_KERNEL);
+		if (!urb) {
+			em28xx_errdev("cannot alloc urb %i\n", i);
+			em28xx_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		dev->transfer_buffer[i] = usb_buffer_alloc(dev->udev,
+				sb_size, GFP_KERNEL, &urb->transfer_dma);
+		if (!dev->transfer_buffer[i]) {
+			em28xx_errdev("unable to allocate %i bytes for "
+				      "transfer buffer %i\n", sb_size, i);
+			em28xx_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		memset(dev->transfer_buffer[i], 0, sb_size);
+		urb->dev = dev->udev;
+		urb->context = dev;
+		urb->pipe = usb_rcvisocpipe(dev->udev, 0x82);
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->interval = 1;
+		urb->transfer_buffer = dev->transfer_buffer[i];
+		urb->complete = em28xx_isocIrq;
+		urb->number_of_packets = EM28XX_NUM_PACKETS;
+		urb->transfer_buffer_length = sb_size;
+		for (j = k = 0; j < EM28XX_NUM_PACKETS;
+				j++, k += dev->max_pkt_size) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+				dev->max_pkt_size;
+		}
+		dev->urb[i] = urb;
+	}
+
+	/* submit urbs */
+	for (i = 0; i < EM28XX_NUM_BUFS; i++) {
+		errCode = usb_submit_urb(dev->urb[i], GFP_KERNEL);
+		if (errCode) {
+			em28xx_errdev("submit of urb %i failed (error=%i)\n", i,
+				      errCode);
+			em28xx_uninit_isoc(dev);
+			return errCode;
+		}
+	}
+
+	return 0;
+}
+
+int em28xx_set_alternate(struct em28xx *dev)
+{
+	int errCode, prev_alt = dev->alt;
+	dev->alt = alt;
+	if (dev->alt == 0) {
+		int i;
+#if 1 /* Always try to get the maximum size value */
+		for (i = 0; i < dev->num_alt; i++)
+			if (dev->alt_max_pkt_size[i] >
+					dev->alt_max_pkt_size[dev->alt])
+				dev->alt = i;
+#endif
+#if 0 /* Should be dependent of horizontal size */
+		if (dev->em_type == EM2800) {
+			/* always use the max packet size for em2800
+			   based devices */
+			for (i = 0; i < dev->num_alt; i++)
+				if (dev->alt_max_pkt_size[i] >
+					dev->alt_max_pkt_size[dev->alt])
+					dev->alt = i;
+		} else {
+			/* FIXME: empiric magic number */
+			unsigned int min_pkt_size = dev->field_size / 137;
+			em28xx_coredbg("minimum isoc packet size: %u",
+					min_pkt_size);
+			dev->alt = 7;
+			for (i = 0; i < dev->num_alt; i ++)
+				if (dev->alt_max_pkt_size[i] >= min_pkt_size) {
+					dev->alt = i;
+					break;
+				}
+		}
+#endif
+	}
+
+	if (dev->alt != prev_alt) {
+		dev->max_pkt_size = dev->alt_max_pkt_size[dev->alt];
+		em28xx_coredbg("setting alternate %d with wMaxPacketSize=%u\n", dev->alt,
+		       dev->max_pkt_size);
+
+		errCode = usb_set_interface(dev->udev, dev->usb_interface, dev->alt);
+		if (errCode < 0) {
+			em28xx_errdev("cannot change alternate number to %d "
+					"(error=%i)\n",
+					dev->alt, errCode);
+			return errCode;
+		}
+	}
+	return 0;
+}
diff --git a/drivers/media/video/empia/em28xx-i2c.c b/drivers/media/video/empia/em28xx-i2c.c
new file mode 100644
index 0000000..1278e6e
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-i2c.c
@@ -0,0 +1,983 @@
+/*
+   em28xx-i2c.c - driver for Empia EM2800/EM2820/2840/2880 USB
+   video capture devices
+
+   Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+		      Markus Rechberger <mrechberger@gmail.com>
+		      Mauro Carvalho Chehab <mchehab@infradead.org>
+		      Sascha Sommer <saschasommer@freenet.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; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/version.h>
+#include <linux/video_decoder.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+#include <media/audiochip.h>
+#else
+#include <media/v4l2-chip-ident.h>
+#endif
+#ifdef EM28XX_TVEEPROM
+#include <media/tveeprom.h>
+#endif
+
+#include "em28xx.h"
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#include "dvb_frontend.h"
+
+#include "xc5000/xc5000_control.h"
+
+
+/* ----------------------------------------------------------- */
+
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, 0444);
+MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
+
+static unsigned int i2c_debug = 1;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#define dprintk1(lvl, fmt, args...) if (i2c_debug >= lvl) do {\
+			printk(fmt, ##args); } while (0)
+#define dprintk2(lvl, fmt, args...) if (i2c_debug >= lvl) do { \
+			printk(KERN_DEBUG "%s at %s: " fmt, \
+			dev->name, __func__, ##args); } while (0)
+
+/*
+ * em2800_i2c_send_max4()
+ * send up to 4 bytes to the i2c device
+ */
+static int em2800_i2c_send_max4(struct em28xx *dev, unsigned char addr,
+				char *buf, int len)
+{
+	int ret;
+	int write_timeout;
+	unsigned char b2[6];
+	BUG_ON(len < 1 || len > 4);
+	b2[5] = 0x80 + len - 1;
+	b2[4] = addr;
+	b2[3] = buf[0];
+	if (len > 1)
+		b2[2] = buf[1];
+	if (len > 2)
+		b2[1] = buf[2];
+	if (len > 3)
+		b2[0] = buf[3];
+
+	ret = dev->em28xx_write_regs(dev, 4 - len, &b2[4 - len], 2 + len);
+	if (ret < 0) {
+		dprintk1(2, "%s:%u:%s(): FIXME: em28xx_write_regs() failed,"
+			"ret = %i\n", __FILE__, __LINE__, __func__, ret);
+		return ret;
+	}
+	if (ret != 2 + len) {
+		em28xx_warn("writing to i2c device failed (error=%i)\n", ret);
+		return -EIO;
+	}
+	for (write_timeout = EM2800_I2C_WRITE_TIMEOUT; write_timeout > 0;
+	     write_timeout -= 5) {
+		ret = dev->em28xx_read_reg(dev, 0x05);
+		if (ret < 0) {
+			dprintk1(2, "%s:%u:%s(): FIXME: em28xx_read_reg()"
+				"failed, ret = %i\n", __FILE__, __LINE__,
+				__func__, ret);
+			return ret;
+		}
+		if (ret == 0x80 + len - 1)
+			return len;
+		msleep(5);
+	}
+	em28xx_warn("i2c write timed out\n");
+	return -EIO;
+}
+
+int em28xx_gpio_cmd(struct em28xx *dev, unsigned int command, u16 *value,
+		unsigned int *len)
+{
+	struct em28xx_gpio *gpio_map = &em28xx_boards[dev->model].gpio_regs;
+	switch (command) {
+	case EM28XX_ANALOG_ON:
+		*value = gpio_map->a_on;
+		break;
+	case EM28XX_LED1_ON:
+		*value = gpio_map->l1_on;
+		break;
+	case EM28XX_XC3028_SECAM:
+		*value = gpio_map->xc3028_sec;
+		break;
+	case EM28XX_TS1_ON:
+		*value = gpio_map->ts1_on;
+		break;
+	case EM28XX_MODESWITCH:
+		*value = gpio_map->m_switch;
+		break;
+	case EM28XX_DECODER_SLEEP:
+		*value = gpio_map->d_sleep;
+		break;
+	case EM28XX_LED2_ON:
+		*value = gpio_map->l2_on;
+		break;
+	case EM28XX_RF:
+		*value = gpio_map->rf;
+		break;
+	case EM28XX_DVB1_ON:
+		*value = gpio_map->dvbs_lnb;
+		break;
+	case EM28XX_DVB2_ON:
+		*value = gpio_map->dvbs_v;
+		break;
+	case EM28XX_TUNER1_ON:
+		*value = gpio_map->t1_on;
+		break;
+	case EM28XX_DEMOD1_RESET:
+		*value = gpio_map->d1_reset;
+		break;
+	case EM28XX_TUNER1_RESET:
+		*value = gpio_map->t1_reset;
+		break;
+	case EM28XX_DECODER_RESET:
+		*value = gpio_map->dc_reset;
+		break;
+	case EM28XX_DEMOD2_RESET:
+		*value = gpio_map->d2_reset;
+		break;
+	case EM28XX_TUNER2_RESET:
+		*value = gpio_map->t2_reset;
+		break;
+	case EM28XX_TUNER2_ON:
+		*value = gpio_map->t2_on;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+int em28xx_gpio_control_translate(void *priv, unsigned int command, void *ptr)
+{
+	int cmd;
+	switch (command) {
+	case XC3028_CHIP_RESET:
+		cmd = EM28XX_TUNER1_RESET;
+		break;
+	case XC5000_CHIP_RESET:
+		cmd = EM28XX_TUNER1_RESET;
+		break;
+	default:
+		printk("unknown gpio translate command\n");
+		break;
+	}
+
+	return em28xx_gpio_control(priv, cmd, ptr);
+}
+
+int em28xx_gpio_control(void *priv, unsigned int command, void *ptr)
+{
+	struct em28xx *dev = (struct em28xx *)priv;
+	unsigned int len;
+	u16 gpio_value;
+	u8 buf[2];
+	u8 *index;
+	u8 gpio;
+	int *arg = ptr;
+	u16 gpio_reg;
+	u8 eeprom_offset;
+
+	if (em28xx_boards[dev->model].manual_gpio)
+		em28xx_gpio_cmd(dev, command, &gpio_value, &len);
+	else {
+		switch (dev->em_type) {
+		case EM2888:
+		case EM2889:
+		case EM2875:
+			eeprom_offset = 0xa0;
+			buf[0] = 0;
+			index = &buf[1];
+			len = 2;
+			dev->em28xx_write_regs(dev, 0x06, "\x40", 1);
+			break;
+		case EM2883:
+		default:
+			eeprom_offset = 0x3c;
+			index = &buf[0];
+			len = 1;
+			break;
+		}
+
+		switch (command) {
+		case EM28XX_ANALOG_ON:
+			*index = eeprom_offset + 24;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_TS1_ON:
+			*index = eeprom_offset + 25;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_DECODER_SLEEP:
+			*index = eeprom_offset + 20;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_DEMOD1_RESET:
+			*index = eeprom_offset + 16;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_TUNER1_RESET:
+			*index = eeprom_offset + 17;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_TUNER1_ON:
+			*index = eeprom_offset + 23;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			break;
+		case EM28XX_LED1_ON:
+			printk("reading led\n");
+			*index = eeprom_offset + 22;
+			em28xx_write_regs_req(dev, 0x03, 0xa0, buf, len);
+			gpio_value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+			printk("led returned: %02x\n", gpio_value);
+			break;
+		default:
+			switch (dev->em_type) {
+			case EM2888:
+			case EM2889:
+			case EM2875:
+				dev->em28xx_write_regs(dev, 0x06, "\x45", 1);
+				break;
+			default:
+				break;
+			}
+			return -EINVAL;
+		}
+		switch (dev->em_type) {
+		case EM2888:
+		case EM2875:
+		case EM2889:
+			dev->em28xx_write_regs(dev, 0x06, "\x45", 1);
+			break;
+		case EM2883:
+		default:
+			eeprom_offset = 0x3b;
+			index = &buf[0];
+			len = 1;
+			break;
+		}
+	}
+
+	/* check if gpio register enabled */
+	if (gpio_value & 0x80) {
+		if (((gpio_value >> 6)&1) == 0) {
+			if (arg == NULL) {
+				printk(KERN_INFO"no argument given %02x\n",
+						gpio_value);
+				return -EINVAL;
+			}
+			/* on/off command */
+			switch (dev->em_type) {
+			case EM2888:
+			case EM2889:
+			case EM2875:
+				gpio_reg = 0x80;
+				break;
+			case EM2883:
+			default:
+				gpio_reg = gpio_value&0x10 ? 0x04 : 0x08;
+				break;
+			}
+			gpio = dev->em28xx_read_reg(dev, gpio_reg);
+			gpio &= ~((u8)(1 << (gpio_value&0x7)));
+
+			if (*arg == EM28XX_REG_ON)
+				gpio |= ((gpio_value >> 5)&1) <<
+					(gpio_value&7);
+			else
+				gpio |= (((gpio_value >> 5)&1)^1) <<
+					(gpio_value&7);
+
+			dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1);
+			dprintk1(2, "(1) writing to %02x -> %02x\n", gpio_reg, gpio);
+		} else {
+			switch (dev->em_type) {
+			case EM2889:
+			case EM2888:
+			case EM2875:
+				gpio_reg = 0x80;
+				break;
+			case EM2883:
+			default:
+				gpio_reg = gpio_value&0x10 ? 0x04 : 0x08;
+				break;
+			}
+			gpio = dev->em28xx_read_reg(dev, gpio_reg);
+			gpio &= ~((u8)(1 << (gpio_value&0xf)));
+
+			gpio |= ((gpio_value >> 5)&1) << (gpio_value&7);
+			dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1);
+			dprintk1(2, "(2) writing to %02x -> %02x\n", gpio_reg, gpio);
+			gpio &= ~((u8)(1 << (gpio_value&0xf)));
+			gpio |= (((gpio_value >> 5)&1)^1) << (gpio_value&7);
+			mdelay(100);
+			dev->em28xx_write_regs(dev, gpio_reg, &gpio, 1);
+			mdelay(100);
+			dprintk1(2, "(3) writing to %02x -> %02x\n", gpio_reg, gpio);
+		}
+	} else {
+		printk("register disabled\n");
+	}
+
+	return 0;
+}
+
+static int em2800_i2c_send_bytes(void *data, unsigned char addr, char *buf,
+				 short len)
+{
+	char *bufPtr = buf;
+	int ret;
+	int wrcount = 0;
+	int count;
+	int maxLen = 4;
+	struct em28xx *dev = (struct em28xx *)data;
+	while (len > 0) {
+		count = (len > maxLen) ? maxLen : len;
+		ret = em2800_i2c_send_max4(dev, addr, bufPtr, count);
+		if (ret > 0) {
+			len -= count;
+			bufPtr += count;
+			wrcount += count;
+		} else
+			return (ret < 0) ? ret : -EFAULT;
+	}
+	return wrcount;
+}
+
+/*
+ * em2800_i2c_check_for_device()
+ * check if there is a i2c_device at the supplied address
+ */
+int em2800_i2c_check_for_device(struct em28xx *dev, unsigned char addr)
+{
+	char msg;
+	int ret;
+	int write_timeout;
+	msg = addr;
+	ret = dev->em28xx_write_regs(dev, 0x04, &msg, 1);
+	if (ret < 0) {
+		em28xx_warn("setting i2c device address failed (error=%i)\n",
+			    ret);
+		return ret;
+	}
+	msg = 0x84;
+	ret = dev->em28xx_write_regs(dev, 0x05, &msg, 1);
+	if (ret < 0) {
+		em28xx_warn("preparing i2c read failed (error=%i)\n", ret);
+		return ret;
+	}
+	for (write_timeout = EM2800_I2C_WRITE_TIMEOUT; write_timeout > 0;
+	     write_timeout -= 5) {
+		unsigned int msg;
+		ret = dev->em28xx_read_reg(dev, 0x5);
+		if (ret < 0) {
+			dprintk1(2, "%s:%u:%s(): FIXME: em28xx_read_reg() "
+				"failed, gpval = %i\n", __FILE__, __LINE__,
+				__func__, ret);
+			return ret;
+		}
+		msg = ret;
+		if (msg == 0x94)
+			return -ENODEV;
+		else if (msg == 0x84)
+			return 0;
+		msleep(5);
+	}
+	return -ENODEV;
+}
+
+/*
+ * em2800_i2c_recv_bytes()
+ * read from the i2c device
+ */
+static int em2800_i2c_recv_bytes(struct em28xx *dev, unsigned char addr,
+				 char *buf, int len)
+{
+	int ret;
+	/* check for the device and set i2c read address */
+	ret = em2800_i2c_check_for_device(dev, addr);
+	if (ret) {
+		em28xx_warn
+		    ("preparing read at i2c address 0x%x failed (error=%i)\n",
+		     addr, ret);
+		return ret;
+	}
+	ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x3, buf, len);
+	if (ret < 0) {
+		em28xx_warn("reading from i2c device at 0x%x failed (error=%i)",
+			    addr, ret);
+		return ret;
+	}
+	return ret;
+}
+
+/*
+ * em28xx_i2c_send_bytes()
+ * untested for more than 4 bytes
+ */
+static int em28xx_i2c_send_bytes(void *data, unsigned char addr, char *buf,
+				 short len, int stop)
+{
+	int wrcount = 0;
+	struct em28xx *dev = (struct em28xx *)data;
+	int i;
+
+	wrcount = dev->em28xx_write_regs_req(dev, stop ? 2 : 3, addr, buf, len);
+	if (wrcount < 0) {
+		dprintk1(2, "%s:%u:%s(): FIXME: em28xx_write_regs_req()"
+			"failed, wrcount = %i\n", __FILE__, __LINE__,
+			__func__, wrcount);
+
+		return wrcount;
+	}
+
+	if (dev->em28xx_read_reg(dev, 0x5) != 0) {
+		printk(KERN_ERR"FIXME:em28xx_i2c_send_bytes(%02x): "
+				"write failed:\n", addr);
+		printk(KERN_ERR"===============================\n");
+		for (i = 0; i < len; i++)
+			printk("%02x ", (unsigned char)buf[i]);
+
+		printk("\n");
+		printk(KERN_ERR"================================\n");
+	}
+
+	return wrcount;
+}
+
+/*
+ * em28xx_i2c_recv_bytes()
+ * read a byte from the i2c device
+ */
+static int em28xx_i2c_recv_bytes(struct em28xx *dev, unsigned char addr,
+				 char *buf, int len)
+{
+	int ret;
+	ret = dev->em28xx_read_reg_req_len(dev, 2, addr, buf, len);
+	if (ret < 0) {
+		em28xx_warn("reading i2c device failed (error=%i)\n", ret);
+		return ret;
+	}
+	if (dev->em28xx_read_reg(dev, 0x5) != 0)
+		return -ENODEV;
+	return ret;
+}
+
+/*
+ * em28xx_i2c_check_for_device()
+ * check if there is a i2c_device at the supplied address
+ */
+static int em28xx_i2c_check_for_device(struct em28xx *dev, unsigned char addr)
+{
+	char msg;
+	int ret;
+	msg = addr;
+
+	ret = dev->em28xx_read_reg_req(dev, 2, addr);
+	if (ret < 0) {
+		em28xx_warn("reading from i2c device failed (error=%i)\n", ret);
+		return ret;
+	}
+	if (dev->em28xx_read_reg(dev, 0x5) != 0)
+		return -ENODEV;
+	return 0;
+}
+
+/*
+ * em28xx_i2c_xfer()
+ * the main i2c transfer function
+ */
+static int em28xx_i2c_xfer(struct i2c_adapter *i2c_adap,
+			   struct i2c_msg msgs[], int num)
+{
+	struct em28xx *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;
+		dprintk2(2, "%s %s addr=%x len=%d:",
+			 (msgs[i].flags & I2C_M_RD) ? "read" : "write",
+			 i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len);
+		/* no len: check only for device presence */
+		if (!msgs[i].len) {
+			if (dev->em_type == EM2800)
+				rc = em2800_i2c_check_for_device(dev, addr);
+			else
+				rc = em28xx_i2c_check_for_device(dev, addr);
+			if (rc < 0) {
+				dprintk2(2, " no device\n");
+				return rc;
+			}
+
+		} else if (msgs[i].flags & I2C_M_RD) {
+			/* read bytes */
+			if (dev->em_type == EM2800)
+				rc = em2800_i2c_recv_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len);
+			else
+				rc = em28xx_i2c_recv_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len);
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_INFO" %02x",
+							msgs[i].buf[byte]);
+			}
+		} else {
+			/* write bytes */
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(" %02x", msgs[i].buf[byte]);
+			}
+			if (dev->em_type == EM2800)
+				rc = em2800_i2c_send_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len);
+			else
+				rc = em28xx_i2c_send_bytes(dev, addr,
+							   msgs[i].buf,
+							   msgs[i].len,
+							   i == num - 1);
+		}
+		if (rc < 0)
+			goto err;
+		if (i2c_debug >= 2)
+			printk("\n");
+	}
+
+	return num;
+err:
+	dprintk2(2, " ERROR: %i\n", rc);
+	return rc;
+}
+
+static int em28xx_i2c_eeprom(struct em28xx *dev, struct i2c_client *client,
+		unsigned char *eedata, int len)
+{
+	unsigned char buf, *p = eedata;
+	struct em28xx_eeprom *em_eeprom = (void *)eedata;
+	int i, err, size = len, block;
+
+	dev->i2c_client.addr = 0xa0 >> 1;
+
+	/* Check if board has eeprom */
+	err = i2c_master_recv(client, &buf, 0);
+	if (err < 0)
+		return -1;
+
+	buf = 0;
+
+	err = i2c_master_send(client, &buf, 1);
+	if (1 != err) {
+		printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n",
+		       dev->name, err);
+		return -1;
+	}
+
+	while (size > 0) {
+		if (size > 16)
+			block = 16;
+		else
+			block = size;
+
+		if (block !=
+		    (err = i2c_master_recv(client, p, block))) {
+			printk(KERN_WARNING
+			       "%s: i2c eeprom read error (err=%d)\n",
+			       dev->name, err);
+			return -1;
+		}
+		size -= block;
+		p += block;
+	}
+	for (i = 0; i < len; i++) {
+		if (0 == (i % 16))
+			printk(KERN_INFO "%s: i2c eeprom %02x:",
+					dev->name, i);
+		printk(" %02x", eedata[i]);
+
+		if (15 == (i % 16))
+			printk("\n");
+	}
+
+	printk(KERN_INFO "EEPROM ID= 0x%08x\n", em_eeprom->id);
+	printk(KERN_INFO "Vendor/Product ID= %04x:%04x\n",
+			em_eeprom->vendor_ID, em_eeprom->product_ID);
+
+	switch (em_eeprom->chip_conf >> 4 & 0x3) {
+	case 0:
+		printk(KERN_INFO "No audio on board.\n");
+		break;
+	case 1:
+		printk(KERN_INFO "AC97 audio (5 sample rates)\n");
+		break;
+	case 2:
+		printk(KERN_INFO "I2S audio, sample rate=32k\n");
+		break;
+	case 3:
+		printk(KERN_INFO "I2S audio, 3 sample rates\n");
+		break;
+	}
+
+	if (em_eeprom->chip_conf & 1 << 3)
+		printk(KERN_INFO "USB Remote wakeup capable\n");
+
+	if (em_eeprom->chip_conf & 1 << 2)
+		printk(KERN_INFO "USB Self power capable\n");
+
+	switch (em_eeprom->chip_conf & 0x3) {
+	case 0:
+		printk(KERN_INFO "500mA max power\n");
+		break;
+	case 1:
+		printk(KERN_INFO "400mA max power\n");
+		break;
+	case 2:
+		printk(KERN_INFO "300mA max power\n");
+		break;
+	case 3:
+		printk(KERN_INFO "200mA max power\n");
+		break;
+	}
+	printk(KERN_INFO "Table at 0x%02x, strings = 0x%04x, 0x%04x, 0x%04x\n",
+				em_eeprom->string_idx_table,
+				em_eeprom->string1,
+				em_eeprom->string2,
+				em_eeprom->string3);
+
+	return 0;
+}
+
+/* ----------------------------------------------------------- */
+
+/*
+ * functionality()
+ */
+static u32 functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL;
+}
+
+#ifndef I2C_PEC
+static void inc_use(struct i2c_adapter *adap)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+	MOD_INC_USE_COUNT;
+#endif
+}
+
+static void dec_use(struct i2c_adapter *adap)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+	MOD_DEC_USE_COUNT;
+#endif
+}
+#endif
+
+static int em28xx_set_tuner(int check_eeprom, struct i2c_client *client)
+{
+	struct em28xx *dev = client->adapter->algo_data;
+	struct tuner_setup tun_setup;
+
+	if (dev->has_inttuner == 0 && dev->dev_modes != EM28XX_DVBT) {
+		/* do not set up a tuner if it's a dvb only device */
+		tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+		tun_setup.type = dev->tuner_type;
+		tun_setup.addr = dev->tuner_addr;
+		em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
+	} else {
+		tun_setup.mode_mask = T_UNINITIALIZED;
+		tun_setup.type = TUNER_ABSENT;
+		tun_setup.addr = dev->tuner_addr;
+		em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
+	}
+
+	return 0;
+}
+
+/*
+ * attach_inform()
+ * gets called when a device attaches to the i2c bus
+ * does some basic configuration
+ */
+static int attach_inform(struct i2c_client *client)
+{
+	struct em28xx *dev = client->adapter->algo_data;
+	int ret;
+
+	if (client->driver->id == I2C_DRIVERID_TUNER && dev->has_inttuner==1) {
+		printk(KERN_INFO"em28xx-i2c: using internal tuner, denying "
+			"request to i2c tuner module.\n");
+		em28xx_set_tuner(-1, client);
+		return 0;
+	}
+
+	switch (client->addr << 1) {
+	case 0x86:
+	case 0x84:
+	case 0x96:
+	case 0x94:
+	{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
+		struct v4l2_priv_tun_config tda9887_cfg;
+#endif
+
+		struct tuner_setup tun_setup;
+
+		tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+		tun_setup.type = TUNER_TDA9887;
+		tun_setup.addr = client->addr;
+
+		em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
+		tda9887_cfg.tuner = TUNER_TDA9887;
+		tda9887_cfg.priv = &dev->tda9887_conf;
+		em28xx_i2c_call_clients(dev, TUNER_SET_CONFIG, &tda9887_cfg);
+#endif
+#endif
+		break;
+	}
+	case 0x42:
+		dprintk1(1, "attach_inform: saa7114 detected.\n");
+		break;
+	case 0x4a:
+		dprintk1(1, "attach_inform: saa7113 detected.\n");
+		break;
+	case 0xa0:
+		dprintk1(1, "attach_inform: eeprom detected.\n");
+		em28xx_i2c_eeprom(dev, client, dev->eedata,
+				sizeof(dev->eedata));
+
+		switch (dev->model) {
+		case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+		case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+		case EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2:
+		case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+		case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+		case EM2883_BOARD_KWORLD_HYBRID_A316:
+		case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+		{
+#ifdef EM28XX_TVEEPROM
+			struct tveeprom tv;
+			tveeprom_hauppauge_analog(client, &tv, dev->eedata);
+
+			if (dev->tuner_type == TUNER_ABSENT) {
+				dev->tuner_type = tv.tuner_type;
+				printk(KERN_INFO"setting new tuner type now"
+						"%d!\n", tv.tuner_type);
+				em28xx_set_tuner(-1, client);
+			}
+#if defined V4L2_IDENT_MSPX4XX     /*  XXX  2.6.27  */
+			if (tv.audio_processor == V4L2_IDENT_MSPX4XX) {
+#else
+			if (tv.audio_processor == AUDIO_CHIP_MSP34XX) {
+#endif
+				dev->i2s_speed = 2048000;
+				dev->has_msp34xx = 1;
+			} else
+				dev->has_msp34xx = 0;
+#endif
+
+			if (dev->has_msp34xx) {
+				/* Send a reset to other chips via gpio */
+				ret = em28xx_write_regs_req(dev, 0x00, 0x08,
+						"\xf7", 1);
+				if (ret < 0) {
+					dprintk1(2, "%s:%u:%s(): FIXME: "
+						"em28xx_write_regs_req() "
+						"failed, ret = %i\n", __FILE__,
+						__LINE__, __func__, ret);
+					return ret;
+				}
+				udelay(2500);
+				ret = em28xx_write_regs_req(dev, 0x00, 0x08,
+						"\xff", 1);
+				if (ret < 0) {
+					dprintk1(2, "%s:%u:%s(): FIXME: "
+						"em28xx_write_regs_req() "
+						"failed, ret = %i\n", __FILE__,
+						__LINE__, __func__, ret);
+					return ret;
+				}
+				udelay(2500);
+			}
+		}
+		break;
+	}
+	case 0x60:
+	case 0x8e:
+	{
+		struct IR_i2c *ir = i2c_get_clientdata(client);
+		em28xx_set_ir(dev, ir);
+		break;
+	}
+	case 0x80:
+	case 0x88:
+		dprintk1(1, "attach_inform: msp34xx/cx25843 detected.\n");
+		break;
+	case 0xb8:
+	case 0xba:
+		dprintk1(1, "attach_inform: tvp5150 detected.\n");
+		break;
+	case 0x1e:
+		dprintk1(1, "zl10353 demodulator found!\n");
+		break;
+	default:
+		dprintk1(1, "attach inform (default): "
+			"detected I2C address %x\n", client->addr << 1);
+		dev->tuner_addr = client->addr;
+		em28xx_set_tuner(-1, client);
+	}
+	return 0;
+}
+
+static struct i2c_algorithm em28xx_algo = {
+	.master_xfer   = em28xx_i2c_xfer,
+	.functionality = functionality,
+};
+
+static struct i2c_adapter em28xx_adap_template = {
+#ifdef I2C_PEC
+	.owner = THIS_MODULE,
+#else
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
+	.inc_use = inc_use,
+	.dec_use = dec_use,
+#endif
+#endif
+#ifdef I2C_CLASS_TV_ANALOG
+	.class = I2C_CLASS_TV_ANALOG,
+#endif
+	.name = "em28xx",
+	.id = I2C_HW_B_EM28XX,
+	.algo = &em28xx_algo,
+	.client_register = attach_inform,
+};
+
+static struct i2c_client em28xx_client_template = {
+	.name = "em28xx internal",
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 15)
+	.flags = I2C_CLIENT_ALLOW_USE,
+#endif
+};
+
+/* ----------------------------------------------------------- */
+
+/*
+ * i2c_devs
+ * incomplete list of known devices
+ */
+static char *i2c_devs[128] = {
+	[0x4a >> 1] = "saa7113h",
+	[0x60 >> 1] = "remote IR sensor",
+	[0x8e >> 1] = "remote IR sensor",
+	[0x86 >> 1] = "tda9887",
+	[0x80 >> 1] = "msp34xx",
+	[0x88 >> 1] = "msp34xx/cx25843",
+	[0xa0 >> 1] = "eeprom",
+	[0xb8 >> 1] = "tvp5150a",
+	[0xba >> 1] = "tvp5150a",
+	[0xc0 >> 1] = "tuner (analog)",
+	[0xc2 >> 1] = "tuner (analog)",
+	[0xc4 >> 1] = "tuner (analog)",
+	[0xc6 >> 1] = "tuner (analog)",
+	[0x1e >> 1] = "zl10353/mt352 dvb-t demodulator",
+};
+
+/*
+ * do_i2c_scan()
+ * check i2c address range for devices
+ */
+static void do_i2c_scan(char *name, struct i2c_client *c)
+{
+	unsigned char buf;
+	int i, rc;
+
+	for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) {
+		c->addr = i;
+		rc = i2c_master_recv(c, &buf, 0);
+		if (rc < 0)
+			continue;
+		printk(KERN_INFO "%s: found i2c device @ 0x%x [%s]\n", name,
+		       i << 1, i2c_devs[i] ? i2c_devs[i] : "???");
+	}
+}
+
+/*
+ * em28xx_i2c_call_clients()
+ * send commands to all attached i2c devices
+ */
+void em28xx_i2c_call_clients(struct em28xx *dev, unsigned int cmd, void *arg)
+{
+	BUG_ON(NULL == dev->i2c_adap.algo_data);
+	i2c_clients_command(&dev->i2c_adap, cmd, arg);
+}
+EXPORT_SYMBOL(em28xx_i2c_call_clients);
+
+/*
+ * em28xx_i2c_register()
+ * register i2c bus
+ */
+int em28xx_i2c_register(struct em28xx *dev)
+{
+	BUG_ON(!dev->em28xx_write_regs || !dev->em28xx_read_reg);
+	BUG_ON(!dev->em28xx_write_regs_req || !dev->em28xx_read_reg_req);
+	dev->i2c_adap = em28xx_adap_template;
+	dev->i2c_adap.dev.parent = &dev->udev->dev;
+	strcpy(dev->i2c_adap.name, dev->name);
+	dev->i2c_adap.algo_data = dev;
+	i2c_add_adapter(&dev->i2c_adap);
+
+	dev->i2c_client = em28xx_client_template;
+	dev->i2c_client.adapter = &dev->i2c_adap;
+
+	if (i2c_scan || dev->dev_modes == 0)
+		do_i2c_scan(dev->name, &dev->i2c_client);
+	return 0;
+}
+
+/*
+ * em28xx_i2c_unregister()
+ * unregister i2c_bus
+ */
+int em28xx_i2c_unregister(struct em28xx *dev)
+{
+	i2c_del_adapter(&dev->i2c_adap);
+	return 0;
+}
+
diff --git a/drivers/media/video/empia/em28xx-input.c b/drivers/media/video/empia/em28xx-input.c
new file mode 100644
index 0000000..a0fcba4
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-input.c
@@ -0,0 +1,536 @@
+/*
+  handle em28xx IR remotes via linux kernel input layer.
+
+   Copyright (C) 2005-2007 Markus Rechberger <mrechberger@gmail.com>
+		 2005      Mauro Carvalho Chehab <mchehab@infradead.org>
+		 2005      Sascha Sommer <saschasommer@freenet.de>
+
+
+  There have been many issues with this input handling routines, starting
+  from crashing the box, printing random characters or blocking the
+  keyboard when loading it etc. (those issues were caused because it used
+  the global runqueue with the asynchronous module mechanism).
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+
+#include "em28xx.h"
+#include "em28xx-keymaps.h"
+
+static unsigned int disable_ir;
+module_param(disable_ir, int, 0444);
+MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+
+static unsigned int ir_debug;
+module_param(ir_debug, int, 0644);
+MODULE_PARM_DESC(ir_debug, "enable debug messages [IR]");
+
+#define dprintk(fmt, arg...)	if (ir_debug) \
+	printk(KERN_DEBUG"%s/ir: " fmt, ir->c.name , ## arg)
+
+/* ----------------------------------------------------------------------- */
+
+static unsigned int get_timestamp(void)
+{
+	struct timeval tm;
+	do_gettimeofday(&tm);
+	return (unsigned int)(tm.tv_sec*1000 + (tm.tv_usec /1000));
+}
+
+int em2888_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	struct em2880_ir *ir = dev->ir_em2880;
+	u8 buf[5];
+	static u8 dropkey;
+	static u8 repeatdelay;
+	int retval = 0;
+	unsigned int timeoutval;
+
+	/* this algorithm works best with 3-5 ms polling
+	   10ms in userspace */
+
+	msleep(3);
+
+
+	dev->em28xx_read_reg_req_len(dev, 0, 0x50, buf, 5);
+
+	timeoutval = get_timestamp();
+
+	if ((buf[0] != 0x11 || ir->btn != buf[1]) && dev->board->ir_keytab[buf[4]] != 0) {
+		if ((ir->btn != buf[1] && ir->released == 1) || ir->key != buf[4]) {
+
+#if 0
+			if (ir->key != buffer[4] && ir->released == 0) 
+				printk("old key got released\n");
+#endif
+
+			*ir_key = buf[4];
+			ir->released = 0;
+			ir->oldval = timeoutval;
+			ir->key = buf[4];
+			retval = 1;
+			
+			/* volume should be repeated faster than other keys */
+
+			if (ir->key<IR_KEYTAB_SIZE && (
+					dev->board->ir_keytab[ir->key] == KEY_VOLUMEUP ||
+					dev->board->ir_keytab[ir->key] == KEY_VOLUMEDOWN)) {
+				dropkey = 1; 
+				repeatdelay = 10;
+			} else {
+				dropkey = 3; 
+				repeatdelay = 100;
+			}
+		}
+
+		if (timeoutval - ir->oldval > repeatdelay && ir->released == 0) {
+			ir->oldval = timeoutval;
+			*ir_key = ir->key;
+			if (dropkey) {
+				retval = 0;
+				dropkey--;
+			} else
+				retval = 1;
+		}
+		ir->oldtimeoutval = 0;
+	} else {
+		if (timeoutval - ir->oldtimeoutval > 150 && ir->oldtimeoutval > 0 && ir->released == 0) {
+			ir->released = 1;
+			retval = 0;
+		}
+		if (ir->oldtimeoutval == 0)
+			ir->oldtimeoutval = timeoutval;
+		retval = 0;
+	}
+	ir->btn = buf[1];
+	return retval;
+}
+
+/* get_key for terratec's devices */
+
+int em2880_get_key_terratec(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	int rc;
+	int irc = 0;
+
+	msleep(50);
+
+	rc = em28xx_read_reg_req(dev, 0x0, 0x45);
+	if (rc < 0)
+		return -1;
+	else
+		irc = rc;
+
+	rc = em28xx_read_reg_req(dev, 0x0, 0x47);
+	if (rc < 0)
+		return -1;
+	else
+		*ir_key = rc;
+
+	dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1];
+	dev->ir_em2880->sequence[1] = dev->ir_em2880->sequence[2];
+	dev->ir_em2880->sequence[2] = irc;
+	if (dev->ir_em2880->sequence[0] != dev->ir_em2880->sequence[1] &&
+	    dev->ir_em2880->sequence[1] != dev->ir_em2880->sequence[2])
+		return 1;
+	else
+		return 0;
+}
+
+/* get_key for pinnacle's devices */
+/* TODO: this is just a fast implementation ... */
+
+int em2880_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	char buf[4];
+	int ret;
+
+	msleep(50);
+
+	ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x45, buf, 4);
+
+	if (ret < 0) {
+		switch (ret) {
+		case -ENODEV:
+			/* Device was disconnected */
+			em28xx_warn("reading key failed "
+				    "(error=%i=-ENODEV)\n", ret);
+			return -1;
+		default:
+			em28xx_warn("reading key failed (error=%i)\n", ret);
+			return -1;
+		}
+	}
+
+	dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1];
+	dev->ir_em2880->sequence[1] = dev->ir_em2880->sequence[2];
+	dev->ir_em2880->sequence[2] = buf[0]&1;
+	*ir_key = buf[2];
+	if ((dev->ir_em2880->sequence[0] == 1 &&
+	     dev->ir_em2880->sequence[1] == 0 &&
+	     dev->ir_em2880->sequence[2] == 1) ||
+	    (dev->ir_em2880->sequence[0] == 0 &&
+	     dev->ir_em2880->sequence[1] == 1 &&
+	     dev->ir_em2880->sequence[2] == 0))
+		return 1;
+	else
+		return 0;
+}
+
+static int em2880_ir_key_poll(struct em28xx *dev)
+{
+	struct em2880_ir *ir = dev->ir_em2880;
+	static u32 ir_key, keystatus;
+	u32 keycode;
+	static u32 old_keycode;
+	int rc;
+	rc = ir->get_key(dev, &ir_key, &keystatus);
+	switch (rc) {
+	case 0:
+		if (keystatus == 1) {
+			if (old_keycode == 0)
+				return -EINVAL;
+			input_report_key(ir->input, old_keycode, 0);
+			keystatus = 0;
+		}
+		break;
+	case -1:
+		keystatus = 0;
+		return rc;
+	default:
+		/* keydown */
+		keycode = IR_KEYCODE(ir->keymap, ir_key);
+		if (keycode == 0)
+			return -EINVAL;
+		input_report_key(ir->input, keycode, 1);
+		old_keycode = keycode;
+		keystatus = 1;
+	}
+	return 0;
+
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+static void em2880_ir_timer(unsigned long data)
+{
+      struct em2880_ir *ir = ((struct em28xx *)data)->ir_em2880;
+      schedule_work(&ir->work);
+}
+#endif
+
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+static void em28xx_ir_work(void *data)
+{
+	struct em28xx *dev = (struct em28xx *)data;
+	struct em2880_ir *ir = dev->ir_em2880;
+	int rc;
+#else
+static void em28xx_ir_work(struct work_struct *work)
+{
+	struct em2880_ir *ir = container_of(work, struct em2880_ir, work.work);
+	struct em28xx *dev = ir->dev;
+	int rc;
+#endif
+	if (ir->state == EM28XX_REMOTE_POLLING) {
+		rc = em2880_ir_key_poll(dev);
+		if (rc != 0) {
+			ir->state = EM28XX_REMOTE_IDLE;
+			return;
+		}
+		mutex_lock(&ir->state_lock);
+		if (ir->state == EM28XX_REMOTE_POLLING)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+			mod_timer(&dev->ir_em2880->timer, jiffies+msecs_to_jiffies(50));
+#else
+			schedule_delayed_work(&dev->ir_em2880->work, msecs_to_jiffies(50));
+#endif
+		mutex_unlock(&ir->state_lock);
+	}
+}
+
+int em2880_ir_detach(struct em28xx *dev)
+{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	struct em2880_ir *ir = dev->ir_em2880;
+
+	return 0;
+	mutex_lock(&dev->input_lock);
+	if (ir == NULL) {
+		printk(KERN_INFO"em28xx-input.c: ir==NULL, skipping"
+				"em2880_ir_detach()\n");
+		mutex_unlock(&dev->input_lock);
+		return 0;
+	}
+
+	mutex_lock(&ir->state_lock);
+	ir->state = EM28XX_REMOTE_INTERRUPT;
+	mutex_unlock(&ir->state_lock);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+	del_timer_sync(&ir->timer);
+#else
+	if (delayed_work_pending(&ir->work))
+		cancel_rearming_delayed_work(&ir->work);
+#endif
+
+	input_unregister_device(ir->input);
+	kfree(dev->ir_em2880);
+	dev->ir_em2880 = NULL;
+	printk(KERN_INFO"em28xx-input.c: remote control handler detached\n");
+	mutex_unlock(&dev->input_lock);
+#endif
+	return 0;
+}
+
+int em2880_ir_attach(struct em28xx *dev)
+{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	struct em2880_ir *ir;
+	struct input_dev *input_dev;
+	char buf[5];
+	int i;
+
+	/* DISABLED this remote control support is broken by design 
+           upcoming support will be moved to userspace plus interrupt
+           triggering support has to be used here, there are alot problems
+           with polling at a high interval, where I already think the timer
+           API is still not bugfree */
+	return 0;
+
+	mutex_lock(&dev->input_lock);
+	if (dev->ir_em2880) {
+		mutex_unlock(&dev->input_lock);
+		printk(KERN_INFO"RC Handler already registered\n");
+		return 0;
+	}
+	dev->ir_em2880 = kzalloc(sizeof(struct em2880_ir), GFP_KERNEL);
+	ir = dev->ir_em2880;
+	ir->keymap = dev->board->ir_keytab;
+	ir->get_key = dev->board->ir_getkey;
+	ir->dev = dev;
+	mutex_init(&ir->state_lock);
+	init_waitqueue_head(&ir->remote_loop);
+	input_dev = input_allocate_device();
+	ir->input = input_dev;
+	input_dev->id.bustype = BUS_USB;
+	sprintf(ir->name, "em2880/em2870 remote control");
+	sprintf(ir->phys, "USB");
+	input_dev->name = ir->name;
+	input_dev->phys = ir->phys; /* FIXME: this is the wrong entry here,
+				    some applications depend on it */
+	input_dev->keycode = ir->keymap;
+	for (i = 0; i < IR_KEYTAB_SIZE; i++)
+		set_bit(ir->keymap[i], input_dev->keybit);
+
+	input_dev->keycodesize = sizeof(IR_KEYTAB_TYPE);
+	input_dev->keycodemax = IR_KEYTAB_SIZE;
+	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+
+	input_register_device(ir->input);
+
+	ir->state = EM28XX_REMOTE_POLLING;
+
+	if (dev->board->ir_getkey == em2888_get_key_empia) {
+		dev->em28xx_read_reg_req_len(dev, 0, 0x50, buf, 5);
+		ir->key = buf[4];
+		ir->oldval = get_timestamp();
+		ir->btn = buf[1];
+		ir->released = 1;
+	}
+
+	INIT_DELAYED_WORK(&ir->work, em28xx_ir_work);
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(50));
+	printk(KERN_INFO"em28xx-input.c: remote control handler attached\n");
+	mutex_unlock(&dev->input_lock);
+#endif
+	return 0;
+}
+
+
+static int get_key_terratec(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
+{
+	unsigned char b;
+
+	/* poll IR chip */
+	if (1 != i2c_master_recv(&ir->c, &b, 1)) {
+		dprintk("read error\n");
+		return -EIO;
+	}
+
+	/* it seems that 0xFE indicates that a button is still hold
+	   down, while 0xff indicates that no button is hold
+	   down. 0xfe sequences are sometimes interrupted by 0xFF */
+
+	dprintk("key %02x\n", b);
+
+	if (b == 0xff)
+		return 0;
+
+	if (b == 0xfe)
+		/* keep old data */
+		return 1;
+
+	*ir_key = b;
+	*ir_raw = b;
+	return 1;
+}
+
+
+static int get_key_em_haup(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
+{
+	unsigned char buf[2];
+	unsigned char code;
+
+	/* poll IR chip */
+	if (2 != i2c_master_recv(&ir->c, buf, 2))
+		return -EIO;
+
+	/* Does eliminate repeated parity code */
+	if (buf[1] == 0xff)
+		return 0;
+
+	ir->old = buf[1];
+
+	/* Rearranges bits to the right order */
+	code =   ((buf[0]&0x01)<<5) | /* 0010 0000 */
+		 ((buf[0]&0x02)<<3) | /* 0001 0000 */
+		 ((buf[0]&0x04)<<1) | /* 0000 1000 */
+		 ((buf[0]&0x08)>>1) | /* 0000 0100 */
+		 ((buf[0]&0x10)>>3) | /* 0000 0010 */
+		 ((buf[0]&0x20)>>5);  /* 0000 0001 */
+
+	dprintk("ir hauppauge (em2840): code=0x%02x (rcv=0x%02x)\n",
+			code, buf[0]);
+
+	/* return key */
+	*ir_key = code;
+	*ir_raw = code;
+	return 1;
+}
+
+static int get_key_pinnacle_usb(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
+{
+	unsigned char buf[3];
+
+	/* poll IR chip */
+
+	if (3 != i2c_master_recv(&ir->c, buf, 3)) {
+		dprintk("read error\n");
+		return -EIO;
+	}
+
+	dprintk("key %02x\n", buf[2]&0x3f);
+	if (buf[0] != 0x00) {
+		return 0;
+	}
+
+	*ir_key = buf[2]&0x3f;
+	*ir_raw = buf[2]&0x3f;
+
+	return 1;
+}
+
+int em2860_get_key_kaiomy(struct em28xx *dev, u32 *ir_key, u32 *keystatus)
+{
+	char buf[4];
+	int ret;
+
+	msleep(50);
+
+	ret = dev->em28xx_read_reg_req_len(dev, 0x0, 0x45, buf, 4);
+
+	if (ret < 0) {
+		switch (ret) {
+		case -ENODEV:
+			/* Device was disconnected */
+			em28xx_warn("reading key failed "
+				    "(error=%i=-ENODEV)\n", ret);
+			return -1;
+		default:
+			em28xx_warn("reading key failed (error=%i)\n", ret);
+			return -1;
+		}
+	}
+
+	ret = em28xx_read_reg_req(dev, 0x0, 0x44);
+	if (ret < 0)
+		return -1;
+
+	dev->ir_em2880->sequence[0] = dev->ir_em2880->sequence[1]&0x80;
+	dev->ir_em2880->sequence[1] = buf[0];
+	*ir_key = buf[2];
+	return (buf[0]&0x7f) || (dev->ir_em2880->sequence[0] != dev->ir_em2880->sequence[1]);
+}
+
+
+
+/* ----------------------------------------------------------------------- */
+void em28xx_set_ir(struct em28xx *dev, struct IR_i2c *ir)
+{
+	if (disable_ir) {
+		ir->get_key = NULL;
+		return;
+	}
+
+	/* detect & configure */
+	switch (dev->model) {
+	case EM2800_BOARD_GENERIC:
+		break;
+	case EM2820_BOARD_GENERIC:
+		break;
+	case EM2800_BOARD_TERRATEC_CINERGY_200:
+	case EM2820_BOARD_TERRATEC_CINERGY_250:
+		ir->ir_codes = ir_codes_em_terratec_u;
+		ir->get_key = get_key_terratec;
+		snprintf(ir->c.name, sizeof(ir->c.name),
+				"i2c IR (EM28XX Terratec)");
+		break;
+	case EM2820_BOARD_PINNACLE_USB_2:
+		ir->ir_codes = ir_codes_em_pinnacle_usb;
+		ir->get_key = get_key_pinnacle_usb;
+		snprintf(ir->c.name, sizeof(ir->c.name),
+				"i2c IR (EM28XX Pinnacle PCTV)");
+		break;
+	case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+		ir->ir_codes = ir_codes_hauppauge_new_u;
+		ir->get_key = get_key_em_haup;
+		snprintf(ir->c.name, sizeof(ir->c.name),
+				"i2c IR (EM2840 Hauppauge)");
+		break;
+	case EM2820_BOARD_MSI_VOX_USB_2:
+		break;
+	case EM2800_BOARD_LEADTEK_WINFAST_USBII:
+		break;
+	case EM2800_BOARD_KWORLD_USB2800:
+		break;
+	}
+}
+
+/* ----------------------------------------------------------------------
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/drivers/media/video/empia/em28xx-keymaps.c b/drivers/media/video/empia/em28xx-keymaps.c
new file mode 100644
index 0000000..5f82f91
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-keymaps.c
@@ -0,0 +1,350 @@
+#include "em28xx-keymaps.h"
+
+IR_KEYTAB_TYPE ir_codes_em_terratec_u[IR_KEYTAB_SIZE] = {
+        [ 0x01 ] = KEY_CHANNEL,
+        [ 0x02 ] = KEY_SELECT,
+        [ 0x03 ] = KEY_MUTE,
+        [ 0x04 ] = KEY_POWER,
+        [ 0x05 ] = KEY_1,
+        [ 0x06 ] = KEY_2,
+        [ 0x07 ] = KEY_3,
+        [ 0x08 ] = KEY_CHANNELUP,
+        [ 0x09 ] = KEY_4,
+        [ 0x0a ] = KEY_5,
+        [ 0x0b ] = KEY_6,
+        [ 0x0c ] = KEY_CHANNELDOWN,
+        [ 0x0d ] = KEY_7,
+        [ 0x0e ] = KEY_8,
+        [ 0x0f ] = KEY_9,
+        [ 0x10 ] = KEY_VOLUMEUP,
+        [ 0x11 ] = KEY_0,
+        [ 0x12 ] = KEY_MENU,
+        [ 0x13 ] = KEY_PRINT,
+        [ 0x14 ] = KEY_VOLUMEDOWN,
+        [ 0x16 ] = KEY_PAUSE,
+        [ 0x18 ] = KEY_RECORD,
+        [ 0x19 ] = KEY_REWIND,
+        [ 0x1a ] = KEY_PLAY,
+        [ 0x1b ] = KEY_FORWARD,
+        [ 0x1c ] = KEY_BACKSPACE,
+        [ 0x1e ] = KEY_STOP,
+        [ 0x40 ] = KEY_ZOOM,
+};
+
+EXPORT_SYMBOL_GPL(ir_codes_em_terratec_u);
+
+IR_KEYTAB_TYPE ir_codes_hauppauge_new_u[IR_KEYTAB_SIZE] = {
+        /* Keys 0 to 9 */
+        [ 0x00 ] = KEY_0,
+        [ 0x01 ] = KEY_1,
+        [ 0x02 ] = KEY_2,
+        [ 0x03 ] = KEY_3,
+        [ 0x04 ] = KEY_4,
+        [ 0x05 ] = KEY_5,
+        [ 0x06 ] = KEY_6,
+        [ 0x07 ] = KEY_7,
+        [ 0x08 ] = KEY_8,
+        [ 0x09 ] = KEY_9,
+
+        [ 0x0a ] = KEY_TEXT,            /* keypad asterisk as well */
+        [ 0x0b ] = KEY_RED,             /* red button */
+        [ 0x0c ] = KEY_RADIO,
+        [ 0x0d ] = KEY_MENU,
+        [ 0x0e ] = KEY_SUBTITLE,        /* also the # key */
+        [ 0x0f ] = KEY_MUTE,
+        [ 0x10 ] = KEY_VOLUMEUP,
+        [ 0x11 ] = KEY_VOLUMEDOWN,
+        [ 0x12 ] = KEY_PREVIOUS,        /* previous channel */
+        [ 0x14 ] = KEY_UP,
+        [ 0x15 ] = KEY_DOWN,
+        [ 0x16 ] = KEY_LEFT,
+        [ 0x17 ] = KEY_RIGHT,
+        [ 0x18 ] = KEY_VIDEO,           /* Videos */
+        [ 0x19 ] = KEY_AUDIO,           /* Music */
+        /* 0x1a: Pictures - presume this means
+           "Multimedia Home Platform" -
+           no "PICTURES" key in input.h
+         */
+        [ 0x1a ] = KEY_MHP,
+
+        [ 0x1b ] = KEY_EPG,             /* Guide */
+        [ 0x1c ] = KEY_TV,
+        [ 0x1e ] = KEY_NEXTSONG,        /* skip >| */
+        [ 0x1f ] = KEY_EXIT,            /* back/exit */
+        [ 0x20 ] = KEY_CHANNELUP,       /* channel / program + */
+        [ 0x21 ] = KEY_CHANNELDOWN,     /* channel / program - */
+        [ 0x22 ] = KEY_CHANNEL,         /* source (old black remote) */
+        [ 0x24 ] = KEY_PREVIOUSSONG,    /* replay |< */
+        [ 0x25 ] = KEY_ENTER,           /* OK */
+        [ 0x26 ] = KEY_SLEEP,           /* minimize (old black remote) */
+        [ 0x29 ] = KEY_BLUE,            /* blue key */
+        [ 0x2e ] = KEY_GREEN,           /* green button */
+        [ 0x30 ] = KEY_PAUSE,           /* pause */
+        [ 0x32 ] = KEY_REWIND,          /* backward << */
+        [ 0x34 ] = KEY_FASTFORWARD,     /* forward >> */
+        [ 0x35 ] = KEY_PLAY,
+        [ 0x36 ] = KEY_STOP,
+        [ 0x37 ] = KEY_RECORD,          /* recording */
+        [ 0x38 ] = KEY_YELLOW,          /* yellow key */
+        [ 0x3b ] = KEY_SELECT,          /* top right button */
+        [ 0x3c ] = KEY_ZOOM,            /* full */
+        [ 0x3d ] = KEY_POWER,           /* system power (green button) */
+};
+
+EXPORT_SYMBOL_GPL(ir_codes_hauppauge_new_u);
+
+
+
+IR_KEYTAB_TYPE ir_codes_pinnacle2[IR_KEYTAB_SIZE] = {
+        /* Keys 0 to 9 */
+        [ 0x15 ] = KEY_0,
+        [ 0x08 ] = KEY_1,
+        [ 0x09 ] = KEY_2,
+        [ 0x0a ] = KEY_3,
+        [ 0x0c ] = KEY_4,
+        [ 0x0d ] = KEY_5,
+        [ 0x0e ] = KEY_6,
+        [ 0x10 ] = KEY_7,
+        [ 0x11 ] = KEY_8,
+        [ 0x12 ] = KEY_9,
+
+        [ 0x03 ] = KEY_POWER,
+
+        [ 0x0b ] = KEY_VOLUMEUP,
+        [ 0x0f ] = KEY_VOLUMEDOWN,
+        [ 0x13 ] = KEY_CHANNELUP,
+        [ 0x17 ] = KEY_CHANNELDOWN,
+        [ 0x14 ] = KEY_INFO,
+
+        [ 0x00 ] = KEY_MUTE,
+
+        [ 0x06 ] = KEY_PLAY,
+        [ 0x04 ] = KEY_REWIND,
+        [ 0x07 ] = KEY_FORWARD,
+        [ 0x06 ] = KEY_PAUSE,
+        [ 0x05 ] = KEY_STOP,
+        [ 0x01 ] = KEY_RECORD,
+        [ 0x02 ] = KEY_ZOOM, /* fullscreen */
+        [ 0x16 ] = KEY_M,
+
+};
+EXPORT_SYMBOL_GPL(ir_codes_pinnacle2);
+
+IR_KEYTAB_TYPE ir_codes_em_gadmei_usb[IR_KEYTAB_SIZE] = {
+        [ 0x00 ] = KEY_1,
+        [ 0x01 ] = KEY_2,
+        [ 0x02 ] = KEY_3,
+        [ 0x03 ] = KEY_4,
+        [ 0x04 ] = KEY_5,
+        [ 0x05 ] = KEY_6,
+        [ 0x06 ] = KEY_7,
+        [ 0x07 ] = KEY_8,
+        [ 0x08 ] = KEY_9,
+        [ 0x09 ] = KEY_0,
+        [ 0x0a ] = KEY_A,
+        [ 0x0b ] = KEY_VIDEO,
+        [ 0x0c ] = KEY_MUTE,
+        [ 0x0d ] = KEY_PLAYPAUSE,
+        [ 0x0e ] = KEY_DVD,
+        [ 0x0f ] = KEY_RADIO,
+        [ 0x10 ] = KEY_VOLUMEUP,
+        [ 0x11 ] = KEY_VOLUMEDOWN,
+        [ 0x12 ] = KEY_CHANNELUP,
+        [ 0x13 ] = KEY_CHANNELDOWN,
+        [ 0x14 ] = KEY_POWER,
+        [ 0x15 ] = KEY_MENU,
+        [ 0x17 ] = KEY_STOP,
+        [ 0x18 ] = KEY_TV,
+        [ 0x1a ] = KEY_RECORD,
+        [ 0x1c ] = KEY_PREVIOUS,
+        [ 0x1e ] = KEY_B,
+        [ 0x1f ] = KEY_C,
+        [ 0x44 ] = KEY_E,
+        [ 0x46 ] = KEY_D,
+        [ 0x4a ] = KEY_ZOOM,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_gadmei_usb);
+
+IR_KEYTAB_TYPE ir_codes_em_pinnacle2_usb[IR_KEYTAB_SIZE] = {
+        [ 0x00 ] = KEY_MUTE,
+        [ 0x01 ] = KEY_A,
+        [ 0x39 ] = KEY_POWER,
+        [ 0x03 ] = KEY_VOLUMEUP,
+        [ 0x06 ] = KEY_CHANNELUP,
+        [ 0x09 ] = KEY_VOLUMEDOWN,
+        [ 0x0c ] = KEY_CHANNELDOWN,
+        [ 0x0f ] = KEY_1,
+        [ 0x15 ] = KEY_2,
+        [ 0x10 ] = KEY_3,
+        [ 0x18 ] = KEY_4,
+        [ 0x1b ] = KEY_5,
+        [ 0x1e ] = KEY_6,
+        [ 0x11 ] = KEY_7,
+        [ 0x21 ] = KEY_8,
+        [ 0x12 ] = KEY_9,
+        [ 0x24 ] = KEY_ZOOM,
+        [ 0x27 ] = KEY_0,
+        [ 0x2a ] = KEY_T,
+        [ 0x2d ] = KEY_REWIND,
+        [ 0x30 ] = KEY_PLAY,
+        [ 0x33 ] = KEY_FORWARD,
+        [ 0x36 ] = KEY_RECORD,
+        [ 0x3c ] = KEY_STOP,
+        [ 0x3f ] = KEY_INFO,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_pinnacle2_usb);
+
+IR_KEYTAB_TYPE ir_codes_em_pinnacle_usb[IR_KEYTAB_SIZE] = {
+        [ 0x3a ] = KEY_0,
+        [ 0x31 ] = KEY_1,
+        [ 0x32 ] = KEY_2,
+        [ 0x33 ] = KEY_3,
+        [ 0x34 ] = KEY_4,
+        [ 0x35 ] = KEY_5,
+        [ 0x36 ] = KEY_6,
+        [ 0x37 ] = KEY_7,
+        [ 0x38 ] = KEY_8,
+        [ 0x39 ] = KEY_9,
+
+        [ 0x2f ] = KEY_POWER,
+
+        [ 0x2e ] = KEY_P,
+        [ 0x1f ] = KEY_L,
+        [ 0x2b ] = KEY_I,
+
+        [ 0x2d ] = KEY_ZOOM,
+        [ 0x1e ] = KEY_ZOOM,
+        [ 0x1b ] = KEY_VOLUMEUP,
+        [ 0x0f ] = KEY_VOLUMEDOWN,
+        [ 0x17 ] = KEY_CHANNELUP,
+        [ 0x1c ] = KEY_CHANNELDOWN,
+        [ 0x25 ] = KEY_INFO,
+
+        [ 0x3c ] = KEY_MUTE,
+
+        [ 0x3d ] = KEY_LEFT,
+        [ 0x3b ] = KEY_RIGHT,
+
+        [ 0x3f ] = KEY_UP,
+        [ 0x3e ] = KEY_DOWN,
+        [ 0x1a ] = KEY_PAUSE,
+
+        [ 0x1d ] = KEY_MENU,
+        [ 0x19 ] = KEY_PLAY,
+        [ 0x16 ] = KEY_REWIND,
+        [ 0x13 ] = KEY_FORWARD,
+        [ 0x15 ] = KEY_PAUSE,
+        [ 0x0e ] = KEY_REWIND,
+        [ 0x0d ] = KEY_PLAY,
+        [ 0x0b ] = KEY_STOP,
+        [ 0x07 ] = KEY_FORWARD,
+        [ 0x27 ] = KEY_RECORD,
+        [ 0x26 ] = KEY_TUNER,
+        [ 0x29 ] = KEY_TEXT,
+        [ 0x2a ] = KEY_MEDIA,
+        [ 0x18 ] = KEY_EPG,
+        [ 0x27 ] = KEY_RECORD,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_pinnacle_usb);
+
+IR_KEYTAB_TYPE ir_codes_em_terratec2[IR_KEYTAB_SIZE] = {
+        [ 0x01 ] = KEY_POWER,
+        [ 0x02 ] = KEY_1,
+        [ 0x03 ] = KEY_2,
+        [ 0x04 ] = KEY_3,
+        [ 0x05 ] = KEY_4,
+        [ 0x06 ] = KEY_5,
+        [ 0x07 ] = KEY_6,
+        [ 0x08 ] = KEY_7,
+        [ 0x09 ] = KEY_8,
+        [ 0x0a ] = KEY_9,
+        [ 0x0b ] = KEY_TUNER,
+        [ 0x0c ] = KEY_0,
+        [ 0x0d ] = KEY_PREVIOUSSONG,
+
+        [ 0x41 ] = KEY_HOME,
+        [ 0x42 ] = KEY_MENU,
+        [ 0x43 ] = KEY_SUBTITLE,
+        [ 0x44 ] = KEY_TEXT,
+        [ 0x45 ] = KEY_DELETE,
+        [ 0x46 ] = KEY_TV,
+        [ 0x47 ] = KEY_DVD,
+        [ 0x49 ] = KEY_VIDEO,
+        [ 0x4a ] = KEY_AUDIO,
+        [ 0x4b ] = KEY_SHUFFLE,
+
+        [ 0x10 ] = KEY_UP,
+        [ 0x11 ] = KEY_LEFT,
+        [ 0x12 ] = KEY_OK,
+        [ 0x13 ] = KEY_RIGHT,
+        [ 0x14 ] = KEY_DOWN,
+
+        [ 0x0f ] = KEY_EPG,
+        [ 0x16 ] = KEY_INFO,
+        [ 0x4d ] = KEY_BACK,
+
+        [ 0x1c ] = KEY_VOLUMEUP,
+        [ 0x4c ] = KEY_PLAY,
+        [ 0x1b ] = KEY_CHANNELUP,
+        [ 0x1e ] = KEY_VOLUMEDOWN,
+        [ 0x1d ] = KEY_MUTE,
+        [ 0x1f ] = KEY_CHANNELDOWN,
+
+        [ 0x17 ] = KEY_RED,
+        [ 0x18 ] = KEY_GREEN,
+        [ 0x19 ] = KEY_YELLOW,
+        [ 0x1a ] = KEY_BLUE,
+
+        [ 0x58 ] = KEY_RECORD,
+        [ 0x48 ] = KEY_STOP,
+        [ 0x40 ] = KEY_PAUSE,
+        [ 0x54 ] = KEY_LAST,
+        [ 0x4e ] = KEY_REWIND,
+        [ 0x4f ] = KEY_FORWARD,
+        [ 0x5c ] = KEY_NEXT,
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_terratec2);
+
+
+IR_KEYTAB_TYPE ir_codes_em_kworld[IR_KEYTAB_SIZE] = {
+        [ 0x43 ] = KEY_POWER,
+	[ 0x03 ] = KEY_POWER2,
+
+        [ 0x04 ] = KEY_1,
+        [ 0x08 ] = KEY_2,
+        [ 0x02 ] = KEY_3,
+        [ 0x0f ] = KEY_4,
+        [ 0x05 ] = KEY_5,
+        [ 0x06 ] = KEY_6,
+        [ 0x0c ] = KEY_7,
+        [ 0x0d ] = KEY_8,
+        [ 0x0a ] = KEY_9,
+        [ 0x11 ] = KEY_0,
+
+        [ 0x09 ] = KEY_CHANNELUP,
+        [ 0x07 ] = KEY_CHANNELDOWN,
+        [ 0x0e ] = KEY_VOLUMEUP,
+        [ 0x13 ] = KEY_VOLUMEDOWN,
+
+        [ 0x01 ] = KEY_TUNER,
+	[ 0x0b ] = KEY_ZOOM,
+
+        [ 0x16 ] = KEY_PLAY,
+        [ 0x17 ] = KEY_MUTE,
+        [ 0x14 ] = KEY_RECORD,
+        [ 0x15 ] = KEY_STOP,
+        
+	[ 0x18 ] = KEY_UP,
+        [ 0x19 ] = KEY_DOWN,
+        [ 0x1a ] = KEY_LEFT,
+        [ 0x1b ] = KEY_RIGHT,
+        
+	[ 0x1c ] = KEY_RED,
+        [ 0x1d ] = KEY_GREEN,
+        [ 0x1e ] = KEY_YELLOW,
+        [ 0x1f ] = KEY_BLUE,
+
+	[ 0x12 ] = KEY_OK,
+	[ 0x10 ] = KEY_HOME
+};
+EXPORT_SYMBOL_GPL(ir_codes_em_kworld);
diff --git a/drivers/media/video/empia/em28xx-keymaps.h b/drivers/media/video/empia/em28xx-keymaps.h
new file mode 100644
index 0000000..1205419
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-keymaps.h
@@ -0,0 +1,21 @@
+#ifndef _EM28XX_KEYMAPS
+#define _EM28XX_KEYMAPS
+#include <linux/input.h>
+#include <linux/workqueue.h>
+
+/* TODO move all that stuff to userspace, fixed keytab definitions in the kernel
+ * are broken by design, there are multiple remotes available with custom return keys
+ * -> raw lirc module is required here */
+
+#define IR_KEYTAB_TYPE  u32 
+#define IR_KEYTAB_SIZE  128  // enougth for rc5, probably need more some day ...
+
+extern IR_KEYTAB_TYPE ir_codes_pinnacle2[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_pinnacle_usb[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_terratec2[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_pinnacle2_usb[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_gadmei_usb[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_terratec_u[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_hauppauge_new_u[IR_KEYTAB_SIZE];
+extern IR_KEYTAB_TYPE ir_codes_em_kworld[IR_KEYTAB_SIZE];
+#endif
diff --git a/drivers/media/video/empia/em28xx-video.c b/drivers/media/video/empia/em28xx-video.c
new file mode 100644
index 0000000..49db364
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-video.c
@@ -0,0 +1,4244 @@
+/*
+   em28xx-video.c - driver for Empia
+		    EM2800/2820/2840/2870/2880
+		    USB video capture devices
+
+   Copyright (C) 2005 Sascha Sommer <saschasommer@freenet.de>
+		 2005-2007 Markus Rechberger <mrechberger@gmail.com>
+		 2005-2006 Mauro Carvalho Chehab <mchehab@infradead.org>
+		 2005 Ludovico Cavedon <cavedon@sssup.it>
+
+   Some parts based on SN9C10x PC Camera Controllers GPL driver made
+   by Luca Risolia <luca.risolia@studio.unibo.it>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/version.h>
+#include <linux/video_decoder.h>
+#include "dvb_frontend.h"
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+#include <linux/mutex.h>
+#endif
+
+#include "em28xx.h"
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 26)
+#include <media/v4l2-ioctl.h>
+#endif
+#include "include/tunerchip.h"
+#include "xc3028/xc3028_control.h"
+#include "xc3028/xc3028_module.h"
+
+#include "xc5000/xc5000_control.h"
+#include "xc5000/xc5000_module.h"
+#include "cx25843/em28xx-cx25843.h"
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+#include "i2c-compat.h"
+#include <linux/moduleparam.h>
+#endif
+
+#define DRIVER_AUTHOR "Ludovico Cavedon <cavedon@sssup.it>, " \
+		      "Markus Rechberger <mrechberger@gmail.com>, " \
+		      "Mauro Carvalho Chehab <mchehab@infradead.org>, " \
+		      "Sascha Sommer <saschasommer@freenet.de>"
+
+#define DRIVER_NAME         "em28xx"
+#define DRIVER_DESC         "Empia em28xx based USB video device driver"
+#define EM28XX_VERSION_CODE  KERNEL_VERSION(0, 0, 1)
+
+#define em28xx_videodbg(fmt, arg...) do {\
+	if (video_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __FUNCTION__ , ##arg); } while (0)
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+unsigned char *XC5000_firmware_SEQUENCE;
+
+LIST_HEAD(em28xx_devlist);
+static LIST_HEAD(em28xx_extension_devlist);
+static DEFINE_MUTEX(em28xx_extension_devlist_lock);
+
+static unsigned int card[]     = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int radio_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int video_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int vbi_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+MODULE_PARM(card, "1-" __stringify(EM28XX_MAXBOARDS) "i");
+MODULE_PARM(video_nr, "1-" __stringify(EM28XX_MAXBOARDS) "i");
+MODULE_PARM(vbi_nr, "1-" __stringify(EM28XX_MAXBOARDS) "i");
+#else
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
+static int dummy;
+module_param_array(card,  int, dummy, 0444);
+module_param_array(video_nr, int, dummy, 0444);
+module_param_array(vbi_nr, int, dummy, 0444);
+#else
+module_param_array(card,  int, NULL, 0444);
+module_param_array(video_nr, int, NULL, 0444);
+module_param_array(vbi_nr, int, NULL, 0444);
+#endif
+#endif
+MODULE_PARM_DESC(card, "card type");
+MODULE_PARM_DESC(video_nr, "video device numbers");
+MODULE_PARM_DESC(vbi_nr, "vbi device numbers");
+
+static int tuner = -1;
+module_param(tuner, int, 0444);
+MODULE_PARM_DESC(tuner, "tuner type");
+
+static unsigned int video_debug;
+module_param(video_debug, int, 0644);
+MODULE_PARM_DESC(video_debug, "enable debug messages [video]");
+
+static unsigned int device_mode;
+module_param(device_mode, int, 0644);
+MODULE_PARM_DESC(device_mode, "device mode (DVB-T/Analogue TV)");
+
+static unsigned int vbi_mode = 1;
+module_param(vbi_mode, int, 0644);
+MODULE_PARM_DESC(vbi_mode, "VBI mode (0 disabled/1 enabled(default, "
+				"if appropriate))");
+
+static unsigned int vbi_interlaced;
+module_param(vbi_interlaced, int, 0644);
+MODULE_PARM_DESC(vbi_interlaced, "VBI Interlaced (default 0 - off)");
+
+/* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS */
+static unsigned long em28xx_devused;
+
+static int em28xx_v4l2_vbi_mmap(struct file *filp, struct vm_area_struct *vma);
+static int em28xx_stream_interrupt(struct em28xx *dev, int type);
+
+struct workqueue_struct *em28xx_wq; /* global workqueue for polling the
+					      remote control and requesting
+					      submodules */
+/* supported controls */
+/* Common to all boards */
+static struct v4l2_queryctrl em28xx_qctrl[] = {
+	{
+		.id = V4L2_CID_AUDIO_VOLUME,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Volume",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 0x1,
+		.default_value = 0x1f,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_AUDIO_MUTE,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "Mute",
+		.minimum = 0,
+		.maximum = 1,
+		.step = 1,
+		.default_value = 1,
+		.flags = 0,
+	}
+};
+
+/* FIXME: These are specific to saa711x - should be moved to its code */
+static struct v4l2_queryctrl saa711x_qctrl[] = {
+	{
+		.id = V4L2_CID_BRIGHTNESS,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Brightness1",
+		.minimum = -128,
+		.maximum = 127,
+		.step = 1,
+		.default_value = 0,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_CONTRAST,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Contrast",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 0x1,
+		.default_value = 0x10,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_SATURATION,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Saturation",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 0x1,
+		.default_value = 0x10,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_RED_BALANCE,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Red chroma balance",
+		.minimum = -128,
+		.maximum = 127,
+		.step = 1,
+		.default_value = 0,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_BLUE_BALANCE,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Blue chroma balance",
+		.minimum = -128,
+		.maximum = 127,
+		.step = 1,
+		.default_value = 0,
+		.flags = 0,
+	}, {
+		.id = V4L2_CID_GAMMA,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Gamma",
+		.minimum = 0x0,
+		.maximum = 0x3f,
+		.step = 0x1,
+		.default_value = 0x20,
+		.flags = 0,
+	}
+#ifdef V4L2_CID_SHARPNESS
+	 , {
+		.id = V4L2_CID_SHARPNESS,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Sharpness",
+		.minimum = 0x0,
+		.maximum = 0xf,
+		.step = 0x1,
+		.default_value = 0x8,
+		.flags = 0,
+	}
+#endif
+};
+
+struct em28xx_output_fmt em28xx_out_fmt[]={
+	{
+		.fmt = {
+			.index = 0,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "Y0-U-Y1-V, 16 bpp",
+			.pixelformat = V4L2_PIX_FMT_YUYV,
+		},
+		.config = 0x14
+	}, {
+		.fmt = {
+			.index = 1,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "Y1-U-Y0-V, 16 bpp",
+			.pixelformat = V4L2_PIX_FMT_YUY1,
+		},
+		.config = 0x15
+	}, {
+		.fmt = {
+			.index = 2,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "YUV411, 12 bpp",
+			.pixelformat = V4L2_PIX_FMT_Y41P
+		},
+		.config = 0x18
+	}, {
+		.fmt = {
+			.index = 3,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "YUV211, 8 bpp",
+			.pixelformat = V4L2_PIX_FMT_YUV211
+		},
+		.config = 0x10
+	}, {
+		.fmt = {
+			.index = 4,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 16 bpp, 6-5-6", /* 16 != 656->17 */
+			.pixelformat = V4L2_PIX_FMT_RGB565   /* 565->16 */
+		},
+		.config = 0x04,
+	}, {
+		.fmt = {
+			.index = 5,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit RGRG",
+			.pixelformat = 1 /* TODO */
+		},
+		.config = 0x00
+	}, {
+		.fmt = {
+			.index = 6,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit GRGR",
+			.pixelformat = 2 /* TODO */
+		},
+		.config = 0x01
+	}, {
+		.fmt = {
+			.index = 7,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit GBGB",
+			.pixelformat = 3 /* TODO */
+		},
+		.config = 0x02
+	}, {
+		.fmt = {
+			.index = 8,
+			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			.description = "RGB, 8bit BGBG",
+			.pixelformat = 4 /* TODO */
+		},
+		.config = 0x03
+	}
+};
+
+static struct usb_driver em28xx_usb_driver;
+
+static DEFINE_MUTEX(em28xx_sysfs_lock);
+static DECLARE_RWSEM(em28xx_disconnect);
+
+/* ----------------------------------------------------------- */
+/* delayed request_module                                      */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+#if defined(CONFIG_MODULES) && defined(MODULE)
+static void request_module_async(struct work_struct *work)
+{
+	struct em28xx *dev = container_of(work,
+			struct em28xx,
+			request_module_wk.work);
+
+	if ((dev->state & DEV_DISCONNECTED) ||
+		(dev->state & DEV_MISCONFIGURED))
+		goto out;
+
+	if (dev->dev_modes & EM28XX_AUDIO)
+		request_module("em28xx-audio");
+
+	if ((dev->dev_modes & EM28XX_DVBT || dev->dev_modes & EM28XX_ATSC || dev->dev_modes & EM28XX_ISDB))
+		request_module("em28xx-dvb");
+
+	/* unlock all the device nodes here */
+out:
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+}
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
+static void request_modules(struct em28xx *dev) {
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+}
+#else
+/* this is a bit fishy here, if we'd directly try to load the module here
+   it would end up in a deadlock since modprobe locks the em28xx module.
+   When loading the em2880-dvb module it also tries to open the em28xx module
+   and it gets stuck and won't return, so a small workaround is to set up
+   a workqueue and run modprobe from another process
+
+   the previous approach used the global workqueue, although this could have
+   locked up the keyboard input system, currently when running
+
+   while :; do modprobe em28xx; rmmod em28xx-dvb; rmmod em28xx-audio; rmmod
+   em28xx; done
+
+   the unloading process might deadlock with the request thread, the requesting-   insmod needs to get killed manually ...(the "bug/feature" is in the
+   linux module code...)
+
+   This whole issue goes back for a very long time already and quite a few
+   people went on this before...
+ */
+
+static void request_modules(struct em28xx *dev)
+{
+       schedule_delayed_work(&dev->request_module_wk, msecs_to_jiffies(50));
+}
+#endif
+#else
+static void request_modules(struct em28xx *dev) {
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+}
+#endif
+#endif
+
+/*********************  v4l2 interface  ******************************************/
+
+/*
+ * em28xx_config()
+ * inits registers with sane defaults
+ */
+int em28xx_config(struct em28xx *dev)
+{
+	em28xx_audio_usb_mute(dev, EM28XX_MUTED);
+	dev->mute = 1;		/* maybe not the right place... */
+	dev->volume = 0x1f;
+	em28xx_audio_analog_set(dev);
+	em28xx_audio_analog_setup(dev);
+	em28xx_outfmt_set_yuv422(dev);
+	em28xx_colorlevels_set_default(dev);
+	em28xx_compression_disable(dev);
+
+	return 0;
+}
+
+/*
+ * em28xx_config_i2c()
+ * configure i2c attached devices
+ * TODO: add MSI_VOX_USB_2 support again!
+ *
+ */
+void em28xx_config_i2c(struct em28xx *dev)
+{
+	struct v4l2_routing route;
+
+	route.input = INPUT(dev->ctl_input)->vmux;
+	route.output = 0;
+
+	/* configure decoder */
+	em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, 0);
+	em28xx_i2c_call_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route);
+	em28xx_i2c_call_clients(dev, VIDIOC_STREAMON, NULL);
+
+}
+
+/*
+ * em28xx_empty_framequeues()
+ * prepare queues for incoming and outgoing frames
+ */
+static void em28xx_empty_framequeues(struct em28xx *dev, int type)
+{
+	u32 i;
+	/* FIXME please verify EM28XX_NUM_FRAMES */
+
+	if (type == EM28XX_VBI) {
+		INIT_LIST_HEAD(&dev->vbi_inqueue);
+		INIT_LIST_HEAD(&dev->vbi_outqueue);
+		for (i = 0; i < EM28XX_NUM_FRAMES; i++) {
+			dev->vbi_frame[i].state = F_UNUSED;
+			dev->vbi_frame[i].buf.bytesused = 0;
+		}
+	} else {
+		INIT_LIST_HEAD(&dev->inqueue);
+		INIT_LIST_HEAD(&dev->outqueue);
+		for (i = 0; i < EM28XX_NUM_FRAMES; i++) {
+			dev->frame[i].state = F_UNUSED;
+			dev->frame[i].buf.bytesused = 0;
+		}
+	}
+
+}
+
+static void video_mux(struct em28xx *dev, int index)
+{
+	int input = INPUT(index)->vmux;
+	struct v4l2_routing route;
+	int ainput;
+
+	route.input = INPUT(index)->vmux;
+	route.output = 0;
+
+	dev->ctl_input = index;
+	dev->ctl_ainput = INPUT(index)->amux;
+	dev->ctl_amix = INPUT(index)->amix;
+
+	em28xx_i2c_call_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route);
+
+	em28xx_videodbg("Setting input index = %d, vmux = %d, amux = %d\n",
+			index, input, dev->ctl_ainput);
+
+	if (dev->has_msp34xx) {
+		if (dev->i2s_speed)
+			em28xx_i2c_call_clients(dev, VIDIOC_INT_I2S_CLOCK_FREQ,
+					&dev->i2s_speed);
+		em28xx_i2c_call_clients(dev, VIDIOC_S_AUDIO, &dev->ctl_ainput);
+		ainput = EM28XX_AUDIO_SRC_TUNER;
+		em28xx_audio_source(dev, ainput);
+	} else {
+		switch (dev->model) {
+		case EM2860_BOARD_GADMEI_UTV330:
+		{
+			u8 val;
+			switch (dev->ctl_ainput) {
+				case 0:
+					val = 0xfd;
+					break;
+				case 1:
+					val = 0xfc;
+					break;
+				default :
+					val = 0xfe;
+					break;
+			}
+			if (dev->mute)
+				val = 0xfe;
+
+			em28xx_write_regs(dev, 0x08, &val, 1);
+			break;
+		}
+		default :
+			switch (dev->ctl_ainput) {
+			case 0:
+				ainput = EM28XX_AUDIO_SRC_TUNER;
+				break;
+			default:
+				ainput = EM28XX_AUDIO_SRC_LINE;
+			}
+			em28xx_audio_source(dev, ainput);
+			break;
+		}
+	}
+	em28xx_audio_set_mixer(dev, dev->ctl_amix);
+}
+
+
+static int em28xx_acquire(struct em28xx *dev, int mode, int lock)
+{
+	int ret = 0;
+
+	if (dev->mode_lock && mode != EM28XX_LOCK)
+		return -EBUSY;
+
+	switch (mode) {
+	case EM28XX_LOCK:
+		if (lock)
+			dev->mode_lock = 1;
+		else
+			dev->mode_lock = 0;
+		break;
+	/* radio is allowed if TV and DVB is not in use */
+	case EM28XX_RADIO:
+		if (lock) {
+			if (dev->fe_user || dev->video_user || dev->vbi_user)
+				ret = -EBUSY;
+			else
+				dev->radio_user++;
+		} else
+			dev->radio_user--;
+		break;
+	/* video, vbi and analogue audio are allowed at the same time */
+	case EM28XX_VIDEO:
+		if (lock) {
+			if (dev->fe_user || dev->radio_user)
+				ret = -EBUSY;
+			else
+				dev->video_user++;
+		} else
+			dev->video_user--;
+		break;
+	case EM28XX_VBI:
+		if (lock) {
+			if (dev->fe_user || dev->radio_user)
+				ret = -EBUSY;
+			else
+				dev->vbi_user++;
+		} else
+			dev->vbi_user--;
+		break;
+	case EM28XX_DVBT:
+		if (lock)  {
+			if (dev->radio_user || dev->video_user || dev->vbi_user)
+				ret = -EBUSY;
+			else
+				dev->fe_user++;
+		} else {
+			dev->fe_user--;
+		}
+		break;
+	case EM28XX_ATSC:
+		if (lock) {
+			if (dev->radio_user || dev->video_user || dev->vbi_user)
+				ret = -EBUSY;
+			else
+				dev->fe_user++;
+		} else
+			dev->fe_user--;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+/*
+ * em28xx_v4l2_open()
+ * inits the device and starts isoc transfer
+ */
+
+static int em28xx_v4l2_open(struct inode *inode, struct file *filp)
+{
+	int minor = iminor(inode);
+	int errCode = 0;
+	int mode = V4L2_TUNER_ANALOG_TV;
+	int type = 0;
+	int ret;
+	struct em28xx *h, *dev = NULL;
+	struct em28xx_fh *fh;
+	struct list_head *list;
+
+	list_for_each(list, &em28xx_devlist) {
+		h = list_entry(list, struct em28xx, devlist);
+		if (h == NULL) {
+			printk("em28xx-devlist empty\n");
+			return -ENODEV;
+		}
+
+		if (h->vdev &&
+				h->vdev->minor == minor) {
+			dev  = h;
+			type = EM28XX_VIDEO;
+			mode = V4L2_TUNER_ANALOG_TV;
+			break;
+		}
+
+		if ((h->dev_modes & EM28XX_RADIO) &&
+				h->rdev &&
+				h->rdev->minor == minor) {
+			dev = h;
+			type = EM28XX_RADIO;
+			mode = V4L2_TUNER_RADIO;
+			break;
+		}
+
+		if ((h->dev_modes & EM28XX_VBI) &&
+				h->vbi_dev &&
+				h->vbi_dev->minor == minor) {
+			dev  = h;
+			type = EM28XX_VBI;
+			mode = V4L2_TUNER_ANALOG_TV;
+			break;
+		}
+	}
+
+	if (NULL == dev) {
+		printk(KERN_INFO"device struct is not set\n");
+		return -ENODEV;
+	}
+
+	ret = dev->em28xx_acquire(dev, type, 1);
+
+	if (ret != 0)
+		return ret;
+
+	if (dev->has_inttuner) {
+		if (dev->mode != mode) {
+			int arg;
+
+			dev->mode = mode;
+			arg = EM28XX_REG_ON;
+
+			dev->em28xx_gpio_control(dev, EM28XX_ANALOG_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_LED1_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_TUNER1_ON, &arg);
+			arg = EM28XX_REG_OFF;
+			dev->em28xx_gpio_control(dev, EM28XX_TS1_ON, &arg);
+			dev->em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &arg);
+			mdelay(100);
+
+			em28xx_config(dev);
+			em28xx_config_i2c(dev);
+			if (dev->mode == V4L2_TUNER_ANALOG_TV)
+				em28xx_i2c_call_clients(dev, VIDIOC_S_STD,
+						&dev->tvnorm->id);
+
+			switch (dev->tuner_type) {
+			case TUNER_XCEIVE_XC3028:
+			{
+				struct xc3028_init_cmd cmd;
+
+				if (dev->tuner == NULL) {
+					printk(KERN_INFO"tuner is not "
+							"attached\n");
+					dev->em28xx_acquire(dev, type, 0);
+					return -EINVAL;
+				}
+				switch (dev->mode) {
+				case V4L2_TUNER_RADIO:
+					/* this is moreover to switch the
+					   decoder to FM */
+
+					cmd.new_tv_mode_ptr =
+						dev->fmnorm->tv_mode;
+					cmd.new_channel_map_ptr =
+						dev->fmnorm->channelmap;
+					if (dev->tuner)
+						dev->tuner->tuner_cmd(
+							dev->tuner,
+							XC3028_INIT_TUNER,
+							&cmd);
+					break;
+				case V4L2_TUNER_ANALOG_TV:
+					cmd.new_tv_mode_ptr =
+						dev->tvnorm->tv_mode;
+					cmd.new_channel_map_ptr =
+						dev->tvnorm->channelmap;
+					if (dev->tuner && dev->tuner->tuner_cmd)
+						dev->tuner->tuner_cmd(
+							dev->tuner,
+							XC3028_INIT_TUNER,
+							&cmd);
+					break;
+				}
+				break;
+			}
+			case TUNER_XCEIVE_XC5000:
+			{
+				if (dev->tuner == NULL) {
+					printk("tuner is not attached\n");
+					dev->em28xx_acquire(dev, type, 0);
+					return -EINVAL;
+				}
+
+				if (dev->tuner && dev->tuner->tuner_cmd)
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC5000_INIT_TUNER,
+							NULL);
+				switch (dev->mode) {
+				case V4L2_TUNER_RADIO:
+				{
+					struct xc_std_conf cmd;
+					cmd.index = dev->fmnorm->index;
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC5000_SET_MODE, &cmd);
+					break;
+				}
+				case V4L2_TUNER_ANALOG_TV:
+				{
+					struct xc_std_conf cmd;
+					cmd.index = dev->tvnorm->index;
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC5000_SET_MODE, &cmd);
+					break;
+				}
+				}
+				break;
+			}
+			}
+			if (dev->mode == V4L2_TUNER_RADIO) {
+				struct v4l2_routing arouting;
+				arouting.input = CX25843_RADIO;
+				em28xx_i2c_call_clients(dev,
+						VIDIOC_INT_S_AUDIO_ROUTING,
+						&arouting);
+			}
+
+		}
+	}
+
+	fh = kzalloc(sizeof(struct em28xx_fh), GFP_KERNEL);
+
+	if (!fh) {
+		printk(KERN_INFO"em28xx-video.c: Out of memory\n");
+		dev->em28xx_acquire(dev, type, 0);
+		return -ENOMEM;
+	}
+	fh->type = type;
+	fh->dev = dev;
+	filp->private_data = fh;
+
+	em28xx_videodbg("open minor = %d type = %s users = %d\n",
+				minor, v4l2_type_names[dev->type], dev->users);
+
+	if (!down_read_trylock(&em28xx_disconnect)) {
+		dev->em28xx_acquire(dev, type, 0);
+		return -ERESTARTSYS;
+	}
+
+	dev->users++;
+	if (dev->vbi_user == 1 && fh->type == EM28XX_VBI)
+		dev->vbi_bytesread = 0;
+
+	if ((fh->type == EM28XX_VIDEO || fh->type == EM28XX_VBI) &&
+			dev->users == 1) {
+
+		em28xx_set_alternate(dev);
+
+		dev->width = norm_maxw(dev);
+		dev->height = norm_maxh(dev);
+		dev->frame_size = dev->width * dev->height * 2;
+		dev->vbi_frame_size = 720 * 2 *
+			(dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1);
+		dev->field_size = dev->frame_size >> 1;
+		dev->vbi_field_size = dev->vbi_frame_size >> 1;
+		dev->bytesperline = dev->width * 2;
+		dev->vbi_bytesperline = dev->width * 2;
+		dev->hscale = 0;
+		dev->vscale = 0;
+		dev->video_bytesread = 0;
+		dev->vbi_bytesread = 0;
+		dev->vbi_dropbytes = 0;
+
+		dev->vbi_frame_current = NULL;
+		dev->frame_current = NULL;
+
+		em28xx_capture_start(dev, 1);
+		em28xx_resolution_set(dev);
+
+		/* start the transfer */
+		errCode = em28xx_init_isoc(dev);
+
+		em28xx_empty_framequeues(dev, EM28XX_VBI);
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+		if (errCode) {
+			dev->users--;
+			goto err;
+		}
+		dev->video_io = IO_NONE;
+		dev->vbi_io = IO_NONE;
+		dev->stream = STREAM_OFF;
+		dev->num_frames = 0;
+		dev->vbi_num_frames = 0;
+		dev->state |= DEV_INITIALIZED;
+		dev->state &= ~DEV_MISCONFIGURED;
+	}
+	up_read(&em28xx_disconnect);
+	return 0;
+err:
+	up_read(&em28xx_disconnect);
+	dev->em28xx_acquire(dev, type, 0);
+	return errCode;
+}
+
+/*
+ * em28xx_realease_resources()
+ * unregisters the v4l2, i2c and usb devices
+ * called when the device gets disconected or at module unload
+*/
+static void em28xx_release_resources(struct em28xx *dev)
+{
+	mutex_lock(&em28xx_sysfs_lock);
+
+	list_del(&dev->devlist);
+	if (dev->dev_modes&EM28XX_VIDEO) {
+		em28xx_info("disconnecting %s\n", dev->vdev->name);
+		em28xx_info("V4L2 VIDEO devices /dev/video%d deregistered\n",
+				dev->vdev->num);
+		video_unregister_device(dev->vdev);
+		if (dev->dev_modes & EM28XX_VBI) {
+			em28xx_info("V4L2 VBI devices /dev/vbi%d "
+				"deregistered\n",
+					dev->vbi_dev->num);
+			video_unregister_device(dev->vbi_dev);
+		}
+	}
+	if (dev->dev_modes & EM28XX_RADIO)
+		video_unregister_device(dev->rdev);
+
+	em28xx_i2c_unregister(dev);
+	usb_put_dev(dev->udev);
+	mutex_unlock(&em28xx_sysfs_lock);
+
+		
+	/* Mark device as unused */
+	em28xx_devused &= ~(1<<dev->devno);
+
+}
+
+/*
+ * em28xx_v4l2_close()
+ * stops streaming and deallocates all resources allocated by the
+ * v4l2 calls and ioctls
+ */
+static int em28xx_v4l2_close(struct inode *inode, struct file *filp)
+{
+	int errCode;
+	struct em28xx_fh *fh;
+	struct em28xx *dev;
+	int ret = 0;
+
+	if (filp->private_data == NULL)
+		return 0;
+
+
+	fh = filp->private_data;
+	dev = fh->dev;
+	em28xx_videodbg("users = %d\n", dev->users);
+
+	if ((dev->vbi_user == 1 || fh->reader == 1) &&
+			fh->type == EM28XX_VBI) {
+		if (dev->vbi_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_STREAMOFF: interrupting "
+					"stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+			dev->vbi_stream = STREAM_OFF;
+		}
+		dev->vbi_io = IO_NONE;
+		fh->reader = 0;
+		dev->vbi_reader = 0;
+	}
+	if ((dev->video_user == 1 || fh->reader == 1) &&
+			fh->type == EM28XX_VIDEO) {
+
+		if (dev->video_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_STREAMOFF: interrupting "
+					"stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+			em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+			dev->video_stream = STREAM_OFF;
+		}
+		fh->reader = 0;
+		dev->video_reader = 0;
+		dev->video_io = IO_NONE;
+	}
+
+	dev->em28xx_acquire(dev, fh->type, 0);
+
+	/* assume that all handles are closed */
+	if (dev->users == 1) {
+		int gpio_arg;
+		dev->video_reader = 0;
+		dev->vbi_reader = 0;
+
+		em28xx_uninit_isoc(dev);
+		em28xx_release_buffers(dev, EM28XX_VIDEO);
+		em28xx_release_buffers(dev, EM28XX_VBI);
+
+		/* turn off led */
+		gpio_arg = EM28XX_REG_OFF;
+		em28xx_gpio_control(dev, EM28XX_LED1_ON,      &gpio_arg);
+		/* the device is already disconnect, free the remaining
+		   resources */
+		if (dev->state & DEV_DISCONNECTED) {
+			em28xx_release_resources(dev);
+			kfree(dev->alt_max_pkt_size);
+			tuner_chip_detach(dev->tuner);
+			kfree(dev);
+			kfree(fh);
+			return 0;
+		}
+
+		/* set alternate 0 */
+		dev->alt = 0;
+		em28xx_videodbg("setting alternate 0\n");
+		if (dev->adev && dev->adev->users == 0) {
+			dev->alt = 0;
+			em28xx_videodbg("setting alternate 0\n");
+			errCode = usb_set_interface(dev->udev, dev->usb_interface, 0);
+			if (errCode < 0) {
+				em28xx_errdev("cannot change alternate "
+					"number to 0 (error = %i)\n",
+						errCode);
+			}
+		}
+	}
+	kfree(fh);
+	dev->users--;
+	wake_up_interruptible_nr(&dev->open, 1);
+	return ret;
+}
+
+/*
+ * em28xx_v4l2_read()
+ * will allocate buffers when called for the first time
+ */
+static ssize_t
+em28xx_v4l2_read(struct file *filp, char __user *buf, size_t count,
+		 loff_t *f_pos)
+{
+	struct em28xx_frame_t *f, *i;
+	unsigned long lock_flags;
+	int ret = 0;
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	/* TODO: either add support for sliced VBI */
+	if (fh->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
+		em28xx_videodbg("V4L2_BUF_TYPE_SLICED_VBI_CAPTURE is set\n");
+		if (copy_to_user(buf, "", 1))
+			return -EFAULT;
+		return 1;
+	}
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_videodbg("device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_videodbg("device misconfigured; close and open it "
+				"again\n");
+		return -EIO;
+	}
+
+	if (fh->type == EM28XX_VIDEO && dev->video_reader > 0 &&
+			fh->reader == 0)
+		return -EBUSY;
+
+	if (fh->type == EM28XX_VBI && dev->vbi_reader > 0 && fh->reader == 0)
+		return -EBUSY;
+
+	if (fh->type == EM28XX_VIDEO && dev->video_io == IO_MMAP) {
+		em28xx_videodbg("Video IO method is set to mmap"
+				" the device again to choose the"
+				"read method\n");
+		return -EINVAL;
+	} else {
+		if (fh->type == EM28XX_VIDEO) {
+			dev->video_reader = 1;
+			fh->reader = 1;
+			dev->video_io = IO_READ;
+		}
+	}
+
+	if (fh->type == EM28XX_VBI && dev->vbi_io == IO_MMAP) {
+		em28xx_videodbg("VBI IO method is set to mmap\n");
+		return -EINVAL;
+	} else {
+		if (fh->type == EM28XX_VBI) {
+			dev->vbi_reader = 1;
+			fh->reader = 1;
+			dev->vbi_io = IO_READ;
+		}
+	}
+
+	if (fh->type == EM28XX_VIDEO && dev->video_stream == STREAM_OFF) {
+		if (!em28xx_request_buffers(dev,
+					EM28XX_NUM_READ_FRAMES,
+					EM28XX_VIDEO)) {
+			em28xx_errdev("read failed, not enough memory\n");
+			return -ENOMEM;
+		}
+
+		dev->video_stream = STREAM_ON;
+		em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+	}
+
+	if (fh->type == EM28XX_VBI && dev->vbi_stream == STREAM_OFF) {
+		if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES,
+					EM28XX_VBI)) {
+			em28xx_errdev("read failed not enough memory\n");
+			return -ENOMEM;
+		}
+
+		dev->vbi_stream = STREAM_ON;
+		em28xx_queue_unusedframes(dev, EM28XX_VBI);
+	}
+
+
+
+	if (!count)
+		return 0;
+
+	if (fh->type == EM28XX_VIDEO) {
+		if (list_empty(&dev->outqueue)) {
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			ret = wait_event_interruptible
+				(dev->wait_frame,
+				 (!list_empty(&dev->outqueue)) ||
+				 (dev->state & DEV_DISCONNECTED));
+			if (ret)
+				return ret;
+
+			if (dev->state & DEV_DISCONNECTED)
+				return -ENODEV;
+
+			dev->video_bytesread = 0;
+		}
+
+		f = list_entry(dev->outqueue.prev, struct em28xx_frame_t,
+				frame);
+
+
+		em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+
+		if (count > f->buf.length)
+			count = f->buf.length;
+
+		if ((dev->video_bytesread+count) > dev->frame_size)
+			count = dev->frame_size - dev->video_bytesread;
+
+		if (copy_to_user(buf, f->bufmem + dev->video_bytesread, count))
+			return -EFAULT;
+
+		dev->video_bytesread += count;
+
+		if (dev->video_bytesread == dev->frame_size) {
+			spin_lock_irqsave(&dev->queue_lock, lock_flags);
+			list_for_each_entry(i, &dev->outqueue, frame)
+				i->state = F_UNUSED;
+			INIT_LIST_HEAD(&dev->outqueue);
+			spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+			em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+			dev->video_bytesread = 0;
+		}
+
+		*f_pos += count;
+	} else if (fh->type == EM28XX_VBI) {
+
+		if (list_empty(&dev->vbi_outqueue)) {
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			if (dev->vbi_num_frames == 0) {
+				em28xx_request_buffers(dev,
+						EM28XX_NUM_READ_FRAMES,
+						EM28XX_VBI);
+				em28xx_queue_unusedframes(dev, EM28XX_VBI);
+			}
+
+			ret = wait_event_interruptible
+				(dev->wait_vbi_frame,
+				 (!list_empty(&dev->vbi_outqueue)) ||
+				 (dev->state & DEV_DISCONNECTED));
+
+			if (ret)
+				return ret;
+
+			if (dev->state & DEV_DISCONNECTED)
+				return -ENODEV;
+
+			dev->vbi_bytesread = 0;
+		}
+
+		f = list_entry(dev->vbi_outqueue.prev, struct em28xx_frame_t,
+				frame);
+		em28xx_queue_unusedframes(dev, EM28XX_VBI);
+
+		if (count > f->buf.length)
+			count = f->buf.length;
+
+		if ((dev->vbi_bytesread + count) > dev->vbi_frame_size)
+			count = dev->vbi_frame_size-dev->vbi_bytesread;
+
+		if (copy_to_user(buf, f->bufmem+dev->vbi_bytesread, count))
+			return -EFAULT;
+
+		dev->vbi_bytesread += count;
+		if (dev->vbi_bytesread == dev->vbi_frame_size) {
+			spin_lock_irqsave(&dev->vbi_queue_lock, lock_flags);
+			list_for_each_entry(i, &dev->vbi_outqueue, frame)
+				i->state = F_UNUSED;
+			INIT_LIST_HEAD(&dev->vbi_outqueue);
+			spin_unlock_irqrestore(&dev->vbi_queue_lock,
+					lock_flags);
+			em28xx_queue_unusedframes(dev, EM28XX_VBI);
+			dev->vbi_bytesread = 0;
+		}
+
+		*f_pos += count;
+	}
+
+	return count;
+}
+
+/*
+ * em28xx_v4l2_poll()
+ * will allocate buffers when called for the first time
+ */
+static unsigned int em28xx_v4l2_poll(struct file *filp, poll_table * wait)
+{
+	unsigned int mask = 0;
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (dev->state & DEV_DISCONNECTED)
+		em28xx_videodbg("device not present\n");
+	else if (dev->state & DEV_MISCONFIGURED)
+		em28xx_videodbg("device is misconfigured; close and open it "
+				"again\n");
+	else if (fh->type == EM28XX_VBI) {
+		if (fh->reader == 0 && dev->vbi_io != IO_NONE)
+			return -EINVAL;
+
+		if (dev->vbi_num_frames == 0 && dev->vbi_reader == 0) {
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+			if (!em28xx_request_buffers(dev,
+						EM28XX_NUM_READ_FRAMES,
+						EM28XX_VBI))
+				return -ENOMEM;
+		}
+
+		if (dev->vbi_io == IO_NONE && dev->vbi_reader == 0) {
+			em28xx_queue_unusedframes(dev, EM28XX_VBI);
+			dev->vbi_io = IO_READ;
+			dev->vbi_stream = STREAM_ON;
+			dev->vbi_reader = 1;
+			fh->reader = 1;
+		}
+
+		poll_wait(filp, &dev->wait_vbi_frame, wait);
+		if (!list_empty(&dev->vbi_outqueue))
+			mask |= POLLIN | POLLRDNORM;
+		return mask;
+	} else if (fh->type == EM28XX_VIDEO) {
+
+		if (dev->num_frames == 0 && dev->reader == 0) {
+			em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+			if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES,
+						EM28XX_VIDEO))
+				return -ENOMEM;
+		}
+
+		if (dev->video_io == IO_NONE && dev->reader == 0) {
+			em28xx_queue_unusedframes(dev, EM28XX_VIDEO);
+			dev->video_io = IO_READ;
+			dev->video_stream = STREAM_ON;
+			dev->reader = 1;
+			fh->reader = 1;
+		}
+
+		poll_wait(filp, &dev->wait_frame, wait);
+		if (!list_empty(&dev->outqueue))
+			mask |= POLLIN | POLLRDNORM;
+		return mask;
+	}
+
+	return POLLERR;
+}
+
+/*
+ * em28xx_vm_open()
+ */
+static void em28xx_vm_open(struct vm_area_struct *vma)
+{
+	struct em28xx_frame_t *f = vma->vm_private_data;
+	f->vma_use_count++;
+}
+
+/*
+ * em28xx_vm_close()
+ */
+static void em28xx_vm_close(struct vm_area_struct *vma)
+{
+	/* NOTE: buffers are not freed here */
+	struct em28xx_frame_t *f = vma->vm_private_data;
+	f->vma_use_count--;
+}
+
+static struct vm_operations_struct em28xx_vm_ops = {
+	.open = em28xx_vm_open,
+	.close = em28xx_vm_close,
+};
+
+/*
+ * em28xx_v4l2_mmap()
+ */
+static int em28xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	unsigned long size = vma->vm_end - vma->vm_start,
+	    start = vma->vm_start;
+	void *pos;
+	u32 i;
+
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (fh->type == EM28XX_VBI)
+		return em28xx_v4l2_vbi_mmap(filp, vma);
+
+	if (fh->type != EM28XX_VIDEO)
+		return -EINVAL;
+
+
+	if (dev->video_reader > 0 && fh->reader == 0)
+		return -EBUSY;
+	else {
+		dev->video_reader = 1;
+		fh->reader = 1;
+	}
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_videodbg("mmap: device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_videodbg("mmap: Device is misconfigured; close and "
+						"open it again\n");
+		return -EIO;
+	}
+
+	if (dev->video_io != IO_MMAP || !(vma->vm_flags & VM_WRITE) /*||
+	    size != PAGE_ALIGN(dev->frame[0].buf.length) */) {
+		return -EINVAL;
+	}
+
+	for (i = 0; i < dev->num_frames; i++)
+		if ((dev->frame[i].buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
+			break;
+
+	if (i == dev->num_frames) {
+		em28xx_videodbg("mmap: user supplied mapping address is out "
+				"of range\n");
+		return -EINVAL;
+	}
+
+	/* VM_IO is eventually going to replace PageReserved altogether */
+	vma->vm_flags |= VM_IO;
+	vma->vm_flags |= VM_RESERVED;	/* avoid to swap out this VMA */
+
+	pos = dev->frame[i].bufmem;
+	if (pos == 0) {
+		em28xx_videodbg("em28xx-video.c: exception pos is 0\n");
+		return -EINVAL;
+	}
+	if (dev->frame[0].buf.length == 0)
+
+		return -EINVAL;
+
+	if (dev->frame[i].buf.length == 0)
+		return -EINVAL;
+
+	while (size > 0) {	/* size is page-aligned */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		unsigned long page = vmalloc_to_pfn(pos);
+		if (remap_pfn_range(vma, start, page, PAGE_SIZE,
+				    vma->vm_page_prot)) {
+			em28xx_videodbg("mmap: rename page map failed\n");
+#else
+		if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
+			em28xx_videodbg("mmap: vm_insert_page failed\n");
+#endif
+			return -EAGAIN;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vma->vm_ops = &em28xx_vm_ops;
+	vma->vm_private_data = &dev->frame[i];
+
+	em28xx_vm_open(vma);
+	return 0;
+}
+
+static int em28xx_v4l2_vbi_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	unsigned long size = vma->vm_end - vma->vm_start,
+	    start = vma->vm_start;
+	void *pos;
+	u32 i;
+
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (fh->type != EM28XX_VBI)
+		return -EINVAL;
+
+	if (dev->vbi_reader > 0 && fh->reader == 0) {
+		return -EBUSY;
+	} else {
+		dev->vbi_reader = 1;
+		fh->reader = 1;
+	}
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_videodbg("mmap: device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_videodbg("mmap: Device is misconfigured; close and "
+						"open it again\n");
+		return -EIO;
+	}
+
+	if (dev->vbi_io != IO_MMAP || !(vma->vm_flags & VM_WRITE) /*||
+	    size != PAGE_ALIGN(dev->frame[0].buf.length) */) {
+		return -EINVAL;
+	}
+
+	for (i = 0; i < dev->vbi_num_frames; i++) {
+		if ((dev->vbi_frame[i].buf.m.offset >> PAGE_SHIFT) ==
+				vma->vm_pgoff)
+			break;
+	}
+	if (i == dev->vbi_num_frames) {
+		em28xx_videodbg("mmap: user supplied mapping address is out "
+				"of range\n");
+		return -EINVAL;
+	}
+
+	/* VM_IO is eventually going to replace PageReserved altogether */
+	vma->vm_flags |= VM_IO;
+	vma->vm_flags |= VM_RESERVED;	/* avoid to swap out this VMA */
+
+	pos = dev->vbi_frame[i].bufmem;
+	if (pos == 0)
+		return -EINVAL;
+
+	if (dev->vbi_frame[0].buf.length == 0)
+		return -EINVAL;
+
+	if (dev->vbi_frame[i].buf.length == 0)
+		return -EINVAL;
+
+	while (size > 0) {	/* size is page-aligned */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15)
+		unsigned long page = vmalloc_to_pfn(pos);
+		if (remap_pfn_range(vma, start, page, PAGE_SIZE,
+				    vma->vm_page_prot)) {
+			em28xx_videodbg("mmap: rename page map failed\n");
+#else
+		if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
+			em28xx_videodbg("mmap: vm_insert_page failed\n");
+#endif
+			return -EAGAIN;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vma->vm_ops = &em28xx_vm_ops;
+	vma->vm_private_data = &dev->vbi_frame[i];
+
+	em28xx_vm_open(vma);
+	return 0;
+}
+
+/*
+ * em28xx_get_ctrl()
+ * return the current saturation, brightness or contrast, mute state
+ */
+static int em28xx_get_ctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		ctrl->value = dev->mute;
+		return 0;
+	case V4L2_CID_AUDIO_VOLUME:
+		ctrl->value = dev->volume;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/*FIXME: should be moved to saa711x */
+static int saa711x_get_ctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	s32 tmp;
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		tmp = em28xx_brightness_get(dev);
+		if (tmp < 0)
+			return -EIO;
+		/* FIXME: cleaner way to extend sign? */
+		ctrl->value = (s32) ((s8) tmp);
+		return 0;
+	case V4L2_CID_CONTRAST:
+		ctrl->value = em28xx_contrast_get(dev);
+		if (ctrl->value < 0)
+			return -EIO;
+		return 0;
+	case V4L2_CID_SATURATION:
+		ctrl->value = em28xx_saturation_get(dev);
+		if (ctrl->value < 0)
+			return -EIO;
+		return 0;
+	case V4L2_CID_RED_BALANCE:
+		tmp = em28xx_v_balance_get(dev);
+		if (tmp < 0)
+			return -EIO;
+		/* FIXME: clenaer way to extend sign? */
+		ctrl->value = (s32) ((s8) tmp);
+		return 0;
+	case V4L2_CID_BLUE_BALANCE:
+		tmp = em28xx_u_balance_get(dev);
+		if (tmp < 0)
+			return -EIO;
+		/* FIXME: clenaer way to extend sign? */
+		ctrl->value = (s32) ((s8) tmp);
+		return 0;
+	case V4L2_CID_GAMMA:
+		ctrl->value = em28xx_gamma_get(dev);
+		if (ctrl->value < 0)
+			return -EIO;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * em28xx_set_ctrl()
+ * mute or set new saturation, brightness or contrast
+ */
+static int em28xx_set_ctrl(struct em28xx *dev, const struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		if (ctrl->value != dev->mute) {
+			dev->mute = ctrl->value;
+			em28xx_audio_usb_mute(dev, ctrl->value);
+			return em28xx_audio_analog_set(dev);
+		}
+		return 0;
+	case V4L2_CID_AUDIO_VOLUME:
+		dev->volume = ctrl->value;
+		return em28xx_audio_analog_set(dev);
+	default:
+		return -EINVAL;
+	}
+}
+
+/*FIXME: should be moved to saa711x */
+static int saa711x_set_ctrl(struct em28xx *dev, const struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		return em28xx_brightness_set(dev, ctrl->value);
+	case V4L2_CID_CONTRAST:
+		return em28xx_contrast_set(dev, ctrl->value);
+	case V4L2_CID_SATURATION:
+		return em28xx_saturation_set(dev, ctrl->value);
+	case V4L2_CID_RED_BALANCE:
+		return em28xx_v_balance_set(dev, ctrl->value);
+	case V4L2_CID_BLUE_BALANCE:
+		return em28xx_u_balance_set(dev, ctrl->value);
+	case V4L2_CID_GAMMA:
+		return em28xx_gamma_set(dev, ctrl->value);
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * em28xx_stream_interrupt()
+ * stops streaming
+ */
+static int em28xx_stream_interrupt(struct em28xx *dev, int type)
+{
+	int ret = 0;
+
+	/* stop reading from the device */
+	switch (type) {
+	case EM28XX_VBI:
+		dev->vbi_stream = STREAM_INTERRUPT;
+		ret = wait_event_timeout(dev->vbi_wait_stream,
+				(dev->vbi_stream == STREAM_OFF) ||
+				(dev->state & DEV_DISCONNECTED),
+				EM28XX_URB_TIMEOUT);
+
+		if (dev->state & DEV_DISCONNECTED)
+			return -ENODEV;
+		else if (ret) {
+			dev->state |= DEV_MISCONFIGURED;
+			em28xx_videodbg("device is misconfigured; close and "
+					"open /dev/video%d again\n",
+					dev->vdev->num);
+		}
+		break;
+	case EM28XX_VIDEO:
+		dev->video_stream = STREAM_INTERRUPT;
+		ret = wait_event_timeout(dev->video_wait_stream,
+					 (dev->video_stream == STREAM_OFF) ||
+					 (dev->state & DEV_DISCONNECTED),
+					 EM28XX_URB_TIMEOUT);
+		if (dev->state & DEV_DISCONNECTED)
+			return -ENODEV;
+		else if (ret) {
+			dev->state |= DEV_MISCONFIGURED;
+			em28xx_videodbg("device is misconfigured; close and "
+				"open /dev/video%d again\n",
+					dev->vdev->num);
+			return ret;
+		}
+		break;
+	}
+	return 0;
+}
+
+static int em28xx_set_norm(struct em28xx *dev, int width, int height)
+{
+	unsigned int hscale, vscale;
+	unsigned int maxh, maxw;
+
+	maxw = norm_maxw(dev);
+	maxh = norm_maxh(dev);
+
+	/* width must even because of the YUYV format */
+	/* height must be even because of interlacing */
+	height &= 0xfffe;
+	width &= 0xfffe;
+
+	if (height < 32)
+		height = 32;
+	if (height > maxh)
+		height = maxh;
+	if (width < 48)
+		width = 48;
+	if (width > maxw)
+		width = maxw;
+
+	hscale = (((unsigned long)maxw) << 12) / width - 4096L;
+
+	if (hscale >= 0x4000)
+		hscale = 0x3fff;
+	width = (((unsigned long)maxw) << 12) / (hscale + 4096L);
+
+	vscale = (((unsigned long)maxh) << 12) / height - 4096L;
+
+	if (vscale >= 0x4000)
+		vscale = 0x3fff;
+
+	height = (((unsigned long)maxh) << 12) / (vscale + 4096L);
+
+	/* set new image size */
+	dev->width = width;
+	dev->height = height;
+	dev->frame_size = dev->width * dev->height * 2;
+	dev->field_size = dev->frame_size >> 1;
+	dev->bytesperline = dev->width * 2;
+	dev->hscale = hscale;
+	dev->vscale = vscale;
+
+	em28xx_resolution_set(dev);
+
+	return 0;
+}
+
+static int em28xx_get_fmt(struct em28xx *dev, struct v4l2_format *format)
+{
+	em28xx_videodbg("VIDIOC_G_FMT: type = %s\n",
+		(format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ?
+		"V4L2_BUF_TYPE_VIDEO_CAPTURE" :
+		(format->type == V4L2_BUF_TYPE_VBI_CAPTURE) ?
+		"V4L2_BUF_TYPE_VBI_CAPTURE" :
+		(format->type == V4L2_CAP_SLICED_VBI_CAPTURE) ?
+		"V4L2_BUF_TYPE_SLICED_VBI_CAPTURE " :
+		"not supported");
+
+	switch (format->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+	{
+		format->fmt.pix.width = dev->width;
+		format->fmt.pix.height = dev->height;
+		format->fmt.pix.pixelformat = dev->outfmt->fmt.pixelformat;
+	//	V4L2_PIX_FMT_YUYV;
+		format->fmt.pix.bytesperline = dev->bytesperline;
+		format->fmt.pix.sizeimage = dev->frame_size;
+		format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+		format->fmt.pix.field = V4L2_FIELD_INTERLACED;
+#if 0
+		format->fmt.pix.field = V4L2_FIELD_INTERLACED_TB;
+#endif
+
+		em28xx_videodbg("VIDIOC_G_FMT: %dx%d\n", dev->width,
+			dev->height);
+		break;
+	}
+
+	case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+	{
+		format->fmt.sliced.service_set = 0;
+		em28xx_i2c_call_clients(dev, VIDIOC_G_FMT, format);
+		if (format->fmt.sliced.service_set == 0)
+			return -EINVAL;
+		break;
+	}
+	case V4L2_BUF_TYPE_VBI_CAPTURE:
+	{
+		format->type = V4L2_BUF_TYPE_VBI_CAPTURE;
+
+		switch (dev->tvnorm->id) {
+		case V4L2_STD_NTSC_M:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+			break;
+		case V4L2_STD_PAL:
+		default:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+		}
+
+		format->fmt.vbi.samples_per_line =
+			dev->tvnorm->vbi_samples_per_line;
+		format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+		format->fmt.vbi.offset = dev->tvnorm->vbi_offset;
+		format->fmt.vbi.start[0] = dev->tvnorm->vbi_start_0;
+		format->fmt.vbi.count[0] = dev->tvnorm->vbi_count_0;
+		format->fmt.vbi.start[1] = dev->tvnorm->vbi_start_1;
+		format->fmt.vbi.count[1] = dev->tvnorm->vbi_count_1;
+		if (dev->vbi_interlaced)
+			format->fmt.vbi.flags = V4L2_VBI_INTERLACED;
+		else 
+			format->fmt.vbi.flags = 0;
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int em28xx_set_fmt(struct em28xx *dev, unsigned int cmd, struct v4l2_format *format)
+{
+	u32 i;
+	int ret = 0;
+	int width = format->fmt.pix.width;
+	int height = format->fmt.pix.height;
+	unsigned int hscale, vscale;
+	unsigned int maxh, maxw;
+
+	maxw = norm_maxw(dev);
+	maxh = norm_maxh(dev);
+
+	em28xx_videodbg("%s: type = %s\n",
+			cmd == VIDIOC_TRY_FMT ?
+			"VIDIOC_TRY_FMT" : "VIDIOC_S_FMT",
+			format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
+			"V4L2_BUF_TYPE_VIDEO_CAPTURE" :
+			format->type == V4L2_BUF_TYPE_VBI_CAPTURE ?
+			"V4L2_BUF_TYPE_VBI_CAPTURE " :
+			"not supported");
+
+	if (format->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
+		em28xx_i2c_call_clients(dev, VIDIOC_G_FMT, format);
+
+		if (format->fmt.sliced.service_set == 0)
+			return -EINVAL;
+
+		return 0;
+	}
+	if (format->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+			dev->dev_modes&EM28XX_VBI) {
+		em28xx_i2c_call_clients(dev, VIDIOC_S_FMT, format);
+		format->type = V4L2_BUF_TYPE_VBI_CAPTURE;
+		switch (dev->tvnorm->id) {
+		case V4L2_STD_NTSC_M:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+			break;
+		case V4L2_STD_PAL:
+		default:
+			format->fmt.vbi.sampling_rate =
+				dev->tvnorm->vbi_sample_rate;
+		}
+		format->fmt.vbi.samples_per_line =
+			dev->tvnorm->vbi_samples_per_line;
+		format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+		format->fmt.vbi.offset = dev->tvnorm->vbi_offset;
+
+		if (dev->vbi_interlaced)
+			format->fmt.vbi.flags = V4L2_VBI_INTERLACED;
+		else 
+			format->fmt.vbi.flags = 0;
+
+		format->fmt.vbi.start[0] = dev->tvnorm->vbi_start_0;
+		format->fmt.vbi.start[1] = dev->tvnorm->vbi_start_1;
+		format->fmt.vbi.count[0] = dev->tvnorm->vbi_count_0;
+		format->fmt.vbi.count[1] = dev->tvnorm->vbi_count_1;
+		dev->vbi_frame_size = 720 * 2 *
+			(dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1);
+		dev->vbi_field_size = dev->vbi_frame_size >> 1;
+
+		return 0;
+	}
+
+	if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	em28xx_videodbg("%s: requested %dx%d\n",
+		cmd == VIDIOC_TRY_FMT ?
+		"VIDIOC_TRY_FMT" : "VIDIOC_S_FMT",
+		format->fmt.pix.width, format->fmt.pix.height);
+
+	/* FIXME: Move some code away from here */
+	/* width must even because of the YUYV format */
+	/* height must be even because of interlacing */
+	height &= 0xfffe;
+	width &= 0xfffe;
+
+	if (height < 32)
+		height = 32;
+	if (height > maxh)
+		height = maxh;
+	if (width < 48)
+		width = 48;
+	if (width > maxw)
+		width = maxw;
+
+	if (dev->em_type == EM2800) {
+		/* the em2800 can only scale down to 50% */
+		if (height % (maxh / 2))
+			height = maxh;
+		if (width % (maxw / 2))
+			width = maxw;
+		/* according to empiatech support */
+		/* the MaxPacketSize is to small to support */
+		/* framesizes larger than 640x480 @ 30 fps */
+		/* or 640x576 @ 25 fps. As this would cut */
+		/* of a part of the image we prefer */
+		/* 360x576 or 360x480 for now */
+		if (width == maxw && height == maxh)
+			width /= 2;
+	}
+
+	hscale = (((unsigned long)maxw) << 12) / width - 4096L;
+	if (hscale >= 0x4000)
+		hscale = 0x3fff;
+
+	width = (((unsigned long)maxw) << 12) / (hscale + 4096L);
+
+	vscale = (((unsigned long)maxh) << 12) / height - 4096L;
+	if (vscale >= 0x4000)
+		vscale = 0x3fff;
+
+	height = (((unsigned long)maxh) << 12) / (vscale + 4096L);
+
+	format->fmt.pix.width = width;
+	format->fmt.pix.height = height;
+
+	dev->outfmt = &em28xx_out_fmt[0];
+
+	for (i = 0; i < ARRAY_SIZE(em28xx_out_fmt); i++) {
+		if (em28xx_out_fmt[i].fmt.pixelformat == format->fmt.pix.pixelformat) {
+			dev->outfmt = &em28xx_out_fmt[i];
+			break;
+		}
+	}
+		
+	format->fmt.pix.pixelformat = dev->outfmt->fmt.pixelformat;
+	format->fmt.pix.bytesperline = width * 2;
+	format->fmt.pix.sizeimage = width * 2 * height;
+	format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	format->fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+#if 0
+	/* available since what kernel version ? */
+	format->fmt.pix.field = V4L2_FIELD_INTERLACED_TB;
+#endif
+
+	em28xx_videodbg("%s: returned %dx%d (%d, %d)\n",
+		cmd == VIDIOC_TRY_FMT ?
+		"VIDIOC_TRY_FMT" :"VIDIOC_S_FMT",
+		format->fmt.pix.width, format->fmt.pix.height, hscale, vscale);
+
+	if (cmd == VIDIOC_TRY_FMT) {
+		return 0;
+	}
+
+	for (i = 0; i < dev->num_frames; i++)
+		if (dev->frame[i].vma_use_count) {
+			em28xx_videodbg("VIDIOC_S_FMT failed. "
+				"Unmap the buffers first.\n");
+			return -EINVAL;
+		}
+
+	/* stop io in case it is already in progress */
+
+	/* set new image size */
+	dev->width = width;
+	dev->height = height;
+	dev->frame_size = dev->width * dev->height * 2;
+	dev->field_size = dev->frame_size >> 1;
+	dev->bytesperline = dev->width * 2;
+	dev->hscale = hscale;
+	dev->vscale = vscale;
+	dev->vbi_frame_size = 720 * 2 *
+		(dev->tvnorm->vbi_count_0 + dev->tvnorm->vbi_count_1);
+	dev->vbi_field_size = dev->vbi_frame_size >> 1;
+
+	if (dev->video_stream == STREAM_ON) {
+		em28xx_videodbg("VIDIOC_SET_FMT: interupting video stream\n");
+		ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+		if (ret)
+			return ret;
+	}
+	if (dev->vbi_stream == STREAM_ON) {
+		em28xx_videodbg("VIDIOC_SET_FMT: interupting vbi stream\n");
+		ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+		if (ret)
+			return ret;
+	}
+
+	em28xx_release_buffers(dev, EM28XX_VIDEO);
+	em28xx_release_buffers(dev, EM28XX_VBI);
+	dev->video_io = IO_NONE;
+	dev->vbi_io = IO_NONE;
+	em28xx_uninit_isoc(dev);
+	em28xx_set_alternate(dev);
+	em28xx_capture_start(dev, 1);
+	em28xx_resolution_set(dev);
+	em28xx_init_isoc(dev);
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_do_ioctl()
+ * This function is _not_ called directly, but from
+ * em28xx_v4l2_ioctl. Userspace
+ * copying is done already, arg is a kernel pointer.
+ */
+static int em28xx_do_ioctl(struct inode *inode, struct file *filp,
+			   struct em28xx *dev, unsigned int cmd, void *arg,
+			   v4l2_kioctl driver_ioctl)
+{
+	int ret;
+	struct em28xx_fh *fh = filp->private_data;
+
+	switch (cmd) {
+		/* ---------- tv norms ---------- */
+	case VIDIOC_ENUMSTD:
+	{
+		struct v4l2_standard *e = arg;
+		unsigned int i;
+
+		i = e->index;
+		if (i >= MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0)
+			return -EINVAL;
+
+		ret = v4l2_video_std_construct(e,
+				dev->board->tvnorms[e->index].id,
+				dev->board->tvnorms[e->index].name);
+
+		e->index = i;
+		if (ret < 0)
+			return ret;
+		return 0;
+	}
+	case VIDIOC_G_STD:
+	{
+		v4l2_std_id *id = arg;
+		*id = dev->tvnorm->id;
+		return 0;
+	}
+	case VIDIOC_S_STD:
+	{
+		v4l2_std_id *id = arg;
+		unsigned int i;
+		int switch_std = 0;
+
+		for (i = 0; i < MAX_EM28XX_TVNORMS &&
+				dev->board->tvnorms[i].id; i++)
+			if (dev->board->tvnorms[i].id == *id)
+				break;
+
+		if (i == MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0)
+			for (i = 0; i < MAX_EM28XX_TVNORMS &&
+					dev->board->tvnorms[i].id; i++)
+				if ((*id & dev->board->tvnorms[i].id) &&
+						dev->board->tvnorms[i].id != 0)
+					break;
+
+		if (i == MAX_EM28XX_TVNORMS || dev->board->tvnorms[i].id == 0)
+			return -EINVAL;
+
+		if (((dev->tvnorm->id & V4L2_STD_SECAM) &&
+			(dev->board->tvnorms[i].id & V4L2_STD_SECAM) == 0) ||
+		   (((dev->tvnorm->id & V4L2_STD_SECAM) == 0) &&
+		    (dev->board->tvnorms[i].id & V4L2_STD_SECAM))) {
+			switch_std = 1;
+		}
+
+		if (((dev->tvnorm->id & V4L2_STD_625_50) &&
+		    ((dev->board->tvnorms[i].id & V4L2_STD_625_50) == 0)) ||
+		      (((dev->tvnorm->id & V4L2_STD_625_50) == 0) &&
+		      (dev->board->tvnorms[i].id & V4L2_STD_625_50))) {
+			if (dev->vbi_stream == STREAM_ON ||
+			    dev->video_stream == STREAM_ON) {
+				printk(KERN_INFO"em28xx: don't switch the standard "
+						"while the device is capturing");
+				return -EBUSY;
+			}
+			dev->vbi_frame_size = 720 * 2 *
+				(dev->board->tvnorms[i].vbi_count_0 + dev->board->tvnorms[i].vbi_count_1);
+			dev->vbi_field_size = dev->vbi_frame_size >> 1;
+		}
+
+		dev->tvnorm = &dev->board->tvnorms[i];
+
+		if (dev->dev_modes & EM28XX_VBI)
+			em28xx_set_vbi(dev, 1);
+
+		em28xx_set_norm(dev, dev->width, dev->height);
+
+		if (dev->has_inttuner) {
+			switch (dev->tuner_type) {
+			case TUNER_XCEIVE_XC3028:
+			{
+				struct xc3028_init_cmd cmd;
+				int gpio_arg;
+				cmd.new_tv_mode_ptr = dev->tvnorm->tv_mode;
+				cmd.new_channel_map_ptr =
+					dev->tvnorm->channelmap;
+
+				if (switch_std) {
+					if (*id & V4L2_STD_SECAM)
+						gpio_arg = EM28XX_REG_ON;
+					else
+						gpio_arg = EM28XX_REG_OFF;
+
+					em28xx_gpio_control(dev,
+							EM28XX_XC3028_SECAM,
+							&gpio_arg);
+				}
+
+				if (dev->tuner && dev->tuner->tuner_cmd)
+					dev->tuner->tuner_cmd(dev->tuner,
+							XC3028_INIT_TUNER,
+							&cmd);
+				break;
+			}
+			case TUNER_XCEIVE_XC5000:
+			{
+				struct xc_std_conf cmd;
+				cmd.index = dev->tvnorm->index;
+				dev->tuner->tuner_cmd(dev->tuner,
+						XC5000_SET_MODE,
+						&cmd);
+				break;
+			}
+			default:
+				break;
+			}
+		}
+
+		em28xx_i2c_call_clients(dev, VIDIOC_S_STD,
+				&dev->tvnorm->id);
+
+		return 0;
+	}
+
+	/* ------ input switching ---------- */
+	case VIDIOC_ENUMINPUT:
+	{
+		struct v4l2_input *i = arg;
+		unsigned int n;
+		static const char *iname[] = {
+			[EM28XX_VMUX_COMPOSITE1] = "Composite1",
+			[EM28XX_VMUX_COMPOSITE2] = "Composite2",
+			[EM28XX_VMUX_COMPOSITE3] = "Composite3",
+			[EM28XX_VMUX_COMPOSITE4] = "Composite4",
+			[EM28XX_VMUX_SVIDEO] = "S-Video",
+			[EM28XX_VMUX_TELEVISION] = "Television",
+			[EM28XX_VMUX_CABLE] = "Cable TV",
+			[EM28XX_VMUX_DVB] = "DVB",
+			[EM28XX_VMUX_DEBUG] = "for debug only",
+		};
+
+		n = i->index;
+		if (n >= MAX_EM28XX_INPUT)
+			return -EINVAL;
+		if (0 == INPUT(n)->type)
+			return -EINVAL;
+		memset(i, 0, sizeof(*i));
+		i->index = n;
+		i->type = V4L2_INPUT_TYPE_CAMERA;
+		strcpy(i->name, iname[INPUT(n)->type]);
+		if ((EM28XX_VMUX_TELEVISION == INPUT(n)->type) ||
+			(EM28XX_VMUX_CABLE == INPUT(n)->type))
+			i->type = V4L2_INPUT_TYPE_TUNER;
+		for (n = 0; dev->board->tvnorms[n].id; n++)
+			i->std |= dev->board->tvnorms[n].id;
+		return 0;
+	}
+	case VIDIOC_G_INPUT:
+	{
+		int *i = arg;
+		*i = dev->ctl_input;
+
+		return 0;
+	}
+	case VIDIOC_S_INPUT:
+	{
+		int *index = arg;
+
+		if (*index >= MAX_EM28XX_INPUT)
+			return -EINVAL;
+
+		if (0 == INPUT(*index)->type)
+			return -EINVAL;
+
+		video_mux(dev, *index);
+
+		return 0;
+	}
+	case VIDIOC_G_AUDIO:
+	{
+		struct v4l2_audio *a = arg;
+		unsigned int index = a->index;
+
+		if (a->index > 1)
+			return -EINVAL;
+		memset(a, 0, sizeof(*a));
+		index = dev->ctl_ainput;
+
+		if (index == 0)
+			strcpy(a->name, "Television");
+		else
+			strcpy(a->name, "Line In");
+
+		a->capability = V4L2_AUDCAP_STEREO;
+		a->index = index;
+		return 0;
+	}
+	case VIDIOC_S_AUDIO:
+	{
+		struct v4l2_audio *a = arg;
+
+		if (a->index != dev->ctl_ainput)
+			return -EINVAL;
+
+		return 0;
+	}
+
+	/* --- controls ---------------------------------------------- */
+	case VIDIOC_QUERYCTRL:
+	{
+		struct v4l2_queryctrl *qc = arg;
+		int i, id = qc->id;
+		int retval;
+		if (fh->type == EM28XX_RADIO)
+			return -EINVAL;
+
+		memset(qc, 0, sizeof(*qc));
+		qc->id = id;
+
+		if (!dev->has_msp34xx) {
+			for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) {
+				if (qc->id && qc->id == em28xx_qctrl[i].id) {
+					memcpy(qc, &(em28xx_qctrl[i]),
+					sizeof(*qc));
+					return 0;
+				}
+			}
+		}
+		if (dev->em28xx_qctrl) {
+			retval = dev->em28xx_qctrl(arg);
+			if (retval == 0)
+				return 0;
+		}
+
+		em28xx_i2c_call_clients(dev, cmd, qc);
+
+		if (qc->type)
+			return 0;
+		else
+			return -EINVAL;
+		break;
+	}
+	case VIDIOC_G_CTRL:
+	{
+		struct v4l2_control *ctrl = arg;
+		int retval = -EINVAL;
+		if (fh->type == EM28XX_RADIO)
+			return -EINVAL;
+
+		if (!dev->has_msp34xx)
+			retval = em28xx_get_ctrl(dev, ctrl);
+		if (retval == -EINVAL) {
+			if (dev->decoder == EM28XX_TVP5150) {
+				em28xx_i2c_call_clients(dev, cmd, arg);
+				return 0;
+			}
+
+			return saa711x_get_ctrl(dev, ctrl);
+		} else if (dev->em28xx_gctrl)
+			return dev->em28xx_ctrl(dev, arg);
+		else
+			return retval;
+	}
+	case VIDIOC_S_CTRL:
+	{
+		struct v4l2_control *ctrl = arg;
+		u8 i;
+		if (fh->type == EM28XX_RADIO)
+			return -EINVAL;
+
+		if (!dev->has_msp34xx) {
+			for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) {
+				if (ctrl->id == em28xx_qctrl[i].id) {
+					if (ctrl->value <
+					em28xx_qctrl[i].minimum
+					|| ctrl->value >
+					em28xx_qctrl[i].maximum)
+						return -ERANGE;
+					return em28xx_set_ctrl(dev, ctrl);
+				}
+			}
+		}
+
+		if (dev->decoder == EM28XX_TVP5150) {
+			em28xx_i2c_call_clients(dev, cmd, arg);
+			return 0;
+		} else if (!dev->has_msp34xx) {
+			for (i = 0; i < ARRAY_SIZE(em28xx_qctrl); i++) {
+				if (ctrl->id == em28xx_qctrl[i].id) {
+					if (ctrl->value <
+					em28xx_qctrl[i].minimum
+					|| ctrl->value >
+					em28xx_qctrl[i].maximum)
+						return -ERANGE;
+					return em28xx_set_ctrl(dev, ctrl);
+				}
+			}
+			for (i = 0; i < ARRAY_SIZE(saa711x_qctrl); i++) {
+				if (ctrl->id == saa711x_qctrl[i].id) {
+					if (ctrl->value <
+					saa711x_qctrl[i].minimum
+					|| ctrl->value >
+					saa711x_qctrl[i].maximum)
+						return -ERANGE;
+					return saa711x_set_ctrl(dev, ctrl);
+				}
+			}
+		}
+
+		return -EINVAL;
+	}
+	/* --- tuner ioctls ------------------------------------------ */
+	case VIDIOC_G_TUNER:
+	{
+		struct v4l2_tuner *t = arg;
+		static unsigned int tv_range[2] = { 44, 958 };
+		unsigned short int lock_status = 0;
+
+		if (0 != t->index)
+			return -EINVAL;
+
+		switch (fh->type) {
+		case EM28XX_RADIO:
+			t->type = V4L2_TUNER_RADIO;
+			/* em2882 + cx2584x + xc5000 uses the videodecoder
+			   for radio detection */
+			em28xx_i2c_call_clients(dev, cmd, t);
+			break;
+		case EM28XX_VBI:
+		case EM28XX_VIDEO:
+			memset(t, 0, sizeof(*t));
+			strcpy(t->name, "Tuner");
+			t->type = V4L2_TUNER_ANALOG_TV;
+			t->capability |= V4L2_TUNER_CAP_NORM;
+			t->rangelow = tv_range[0] * 16;
+			t->rangehigh = tv_range[1] * 16;
+
+			if (dev->tuner && dev->tuner->get_lock_status) {
+				dev->tuner->get_lock_status(dev->tuner, &lock_status);
+				if (lock_status == 1)
+					t->signal = 0xffff;
+			} else
+				em28xx_i2c_call_clients(dev, cmd, t);
+
+			em28xx_videodbg("VIDIO_G_TUNER: signal = %x, afc = %x\n", t->signal,
+					t->afc);
+		}
+		return 0;
+	}
+	case VIDIOC_S_TUNER:
+	{
+		struct v4l2_tuner *t = arg;
+
+		if (0 != t->index)
+			return -EINVAL;
+		/* there are only a very few devices with more than one tuner
+		   available, this can be updated to work correctly later */
+		return 0;
+	}
+	case VIDIOC_G_FREQUENCY:
+	{
+		struct v4l2_frequency *f = arg;
+
+		memset(f, 0, sizeof(*f));
+		switch (fh->type) {
+		case EM28XX_RADIO:
+			f->type = V4L2_TUNER_RADIO;
+			f->frequency = dev->rctl_freq;
+			break;
+		case EM28XX_VIDEO:
+		case EM28XX_VBI:
+			f->type = V4L2_TUNER_ANALOG_TV;
+			f->frequency = dev->vctl_freq;
+			break;
+		}
+		return 0;
+	}
+	case VIDIOC_S_FREQUENCY:
+	{
+		struct v4l2_frequency *f = arg;
+
+		if (0 != f->tuner)
+			return -EINVAL;
+
+		if ((fh->type == EM28XX_RADIO && f->type != V4L2_TUNER_RADIO) ||
+			(fh->type == EM28XX_VIDEO && f->type != V4L2_TUNER_ANALOG_TV))
+			return -EINVAL;
+
+		switch (fh->type) {
+		case EM28XX_RADIO:
+			dev->rctl_freq = f->frequency;
+			break;
+		case EM28XX_VIDEO:
+		case EM28XX_VBI:
+			dev->vctl_freq = f->frequency;
+			break;
+		}
+
+		if (dev->tuner) {
+			switch (fh->type) {
+			case EM28XX_VBI:
+			case EM28XX_VIDEO:
+				dev->tuner->set_frequency(dev->tuner, (unsigned long)f->frequency*1000/16*1000);
+				break;
+			case EM28XX_RADIO:
+				dev->tuner->set_frequency(dev->tuner, (unsigned long)f->frequency/16*1000);
+				break;
+			}
+		} else
+			em28xx_i2c_call_clients(dev, VIDIOC_S_FREQUENCY, f);
+
+		return 0;
+	}
+#if 0				/* ioctl is optional */
+	case VIDIOC_G_PARM:
+	{
+		struct v4l2_captureparm *parm = arg;
+		memset(parm, 0, sizeof(*parm));
+		return 0;
+	}
+#endif
+	case VIDIOC_CROPCAP:
+	{
+		struct v4l2_cropcap *cc = arg;
+
+		if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+			return -EINVAL;
+		cc->bounds.left = 0;
+		cc->bounds.top = 0;
+		cc->bounds.width = dev->width;
+		cc->bounds.height = dev->height;
+		cc->defrect = cc->bounds;
+		cc->pixelaspect.numerator = 54;	/* 4:3 FIXME: remove magic numbers */
+		cc->pixelaspect.denominator = 59;
+		return 0;
+	}
+	case VIDIOC_STREAMON:
+	{
+		int *type = arg;
+		if (fh->type == EM28XX_VBI) {
+			dev->vbi_stream = STREAM_ON;
+			return 0;
+		}
+
+		if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE
+			|| dev->video_io != IO_MMAP)
+			return -EINVAL;
+#if 0
+		if (list_empty(&dev->inqueue))
+			return -EINVAL;
+#endif
+
+		dev->video_stream = STREAM_ON;
+
+		em28xx_videodbg("VIDIOC_STREAMON: starting stream\n");
+
+		return 0;
+
+	}
+	case VIDIOC_STREAMOFF:
+	{
+		int *type = arg;
+		int ret;
+
+		if (fh->type == EM28XX_VBI) {
+			dev->vbi_reader = 0;
+			fh->reader = 0;
+			if (dev->vbi_stream == STREAM_ON) {
+				em28xx_videodbg("VIDIOC_STREAMOFF: interrupting stream\n");
+				ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+				if (ret)
+					return ret;
+			}
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+			dev->vbi_stream = STREAM_OFF;
+			return 0;
+		}
+
+		if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE
+			|| dev->video_io != IO_MMAP)
+			return -EINVAL;
+
+		if (dev->video_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_STREAMOFF: interrupting stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+			if (ret)
+				return ret;
+		}
+		dev->video_reader = 0;
+		dev->video_stream = STREAM_OFF;
+		fh->reader = 0;
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+
+		return 0;
+	}
+	default:
+		return v4l_compat_translate_ioctl(inode, filp, cmd, arg,
+						  driver_ioctl);
+	}
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_do_ioctl()
+ * This function is _not_ called directly, but from
+ * em28xx_v4l2_ioctl. Userspace
+ * copying is done already, arg is a kernel pointer.
+ */
+static int em28xx_video_do_ioctl(struct inode *inode, struct file *filp,
+				 unsigned int cmd, void *arg)
+{
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+	if (!dev)
+		return -ENODEV;
+
+	if (video_debug > 1)
+		v4l_print_ioctl(dev->name, cmd);
+
+	switch (cmd) {
+
+		/* --- capabilities ------------------------------------------ */
+	case VIDIOC_QUERYCAP:
+		{
+		struct v4l2_capability *cap = arg;
+
+		memset(cap, 0, sizeof(*cap));
+		strlcpy(cap->driver, "em28xx", sizeof(cap->driver));
+		strlcpy(cap->card, em28xx_boards[dev->model].name,
+			sizeof(cap->card));
+		strlcpy(cap->bus_info, dev->udev->dev.bus_id,
+			sizeof(cap->bus_info));
+		cap->version = EM28XX_VERSION_CODE;
+		cap->capabilities =
+#if 0
+				V4L2_CAP_SLICED_VBI_CAPTURE |
+#endif
+				V4L2_CAP_VIDEO_CAPTURE |
+				V4L2_CAP_AUDIO |
+				V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+		if (dev->has_tuner)
+			cap->capabilities |= V4L2_CAP_TUNER;
+		if (dev->dev_modes & EM28XX_RADIO)
+			cap->capabilities |= V4L2_CAP_TUNER;
+		if (dev->dev_modes & EM28XX_VBI)
+			cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
+		return 0;
+	}
+	/* --- capture ioctls ---------------------------------------- */
+	case VIDIOC_ENUM_FMT:
+	{
+		struct v4l2_fmtdesc *fmtd = arg;
+		int index = fmtd->index;
+
+		if (fmtd->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+			return -EINVAL;
+
+		if (index >= ARRAY_SIZE(em28xx_out_fmt))
+			return -EINVAL;
+
+		memset(fmtd, 0, sizeof(*fmtd));
+		fmtd->index = index;
+		fmtd->type = em28xx_out_fmt[index].fmt.type;
+		strcpy(fmtd->description, em28xx_out_fmt[index].fmt.description);
+		fmtd->pixelformat = em28xx_out_fmt[index].fmt.pixelformat;
+		return 0;
+	}
+	case VIDIOC_G_FMT:
+		return em28xx_get_fmt(dev, (struct v4l2_format *) arg);
+
+	case VIDIOC_TRY_FMT:
+	case VIDIOC_S_FMT:
+	{
+		struct v4l2_format *fmt = arg;
+		if ((dev->video_reader == 1 && fh->reader == 0) ||
+		   (dev->vbi_reader == 1 && fh->reader == 0)) {
+
+			/* this is something to struggle around however you need it
+			   mplayer tries to set 640x480 and ongoing formats till
+			   it hits 720x576 if requested, if the first try will fail
+			   the ongoing tries will also fail and mplayer will not
+			   work with the em28xx driver
+
+			   the enabled code is mplayer aware, commented out code
+			   would be a small workaround not perfect either but
+			   slightly better.
+			 */
+			if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+				fmt->fmt.pix.height = dev->height;
+				fmt->fmt.pix.width = dev->width;
+				return 0;
+#if 0
+				if (dev->width == (fmt->fmt.pix.width & 0xfffe) &&
+						dev->height == (fmt->fmt.pix.height & 0xfffe)) {
+					fmt->fmt.pix.height &= 0xfffe;
+					fmt->fmt.pix.width &= 0xfffe;
+					printk(KERN_INFO"everything ok not changing anything and returning 0\n");
+					return 0;
+				}
+#endif
+			}
+			printk("em28xx-video.c: device is currently busy!\n");
+			return -EBUSY;
+		}
+		return em28xx_set_fmt(dev, cmd, (struct v4l2_format *)arg);
+	}
+
+	/* obsolete v4l1 implementation, but it's still used by some applications -
+	 * it should be implemented but we don't support it
+	 */
+
+	case VIDIOCGMBUF:
+	{
+		struct video_mbuf *mbuf = arg;
+		int i;
+
+		int gbuffers = EM28XX_NUM_READ_FRAMES;
+		int gbufsize = PAGE_ALIGN(dev->frame_size); /* max resolution of em28xx based devices */
+
+		if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+			return -EINVAL;
+		}
+
+		if (dev->video_io != IO_NONE) {
+			em28xx_videodbg("method is set to read;"
+				" close and open the device again to"
+				" choose the mmap I/O method\n");
+			printk(KERN_INFO"VIDEO_IO is busy\n");
+			return -EBUSY;
+		}
+
+		memset(mbuf, 0, sizeof(*mbuf));
+		mbuf->frames = gbuffers;
+
+		mbuf->size = gbufsize * gbuffers;
+		for (i = 0; i < gbuffers; i++) {
+			mbuf->offsets[i] = i*gbufsize;
+		}
+
+		/* FIXME: setting resolution using the v4l1 PICT command */
+
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+		em28xx_release_buffers(dev, EM28XX_VIDEO);
+
+		if (!em28xx_request_buffers(dev, EM28XX_NUM_READ_FRAMES, EM28XX_VIDEO)) {
+			printk(KERN_INFO"em28xx-video.c: unable to map"
+					" buffers\n");
+			return -ENOMEM;
+		}
+
+		dev->video_stream = STREAM_ON;
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+		dev->frame_current = NULL;
+		dev->video_io = IO_MMAP;
+		return 0;
+	}
+	case VIDIOCMCAPTURE:
+	{
+		struct video_mmap *vm = arg;
+		unsigned long lock_flags;
+
+		if (vm->format != VIDEO_PALETTE_YUV422)
+			return -EINVAL;
+
+		if (dev->frame[vm->frame].state != F_UNUSED) {
+			return -EAGAIN;
+		}
+
+		dev->frame[vm->frame].state = F_QUEUED;
+
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		list_add_tail(&dev->frame[vm->frame].frame, &dev->inqueue);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+		return 0;
+	}
+	case VIDIOCSYNC:
+	{
+		/* int *frame = arg; FIXME - argument not used?*/
+		struct em28xx_frame_t *f;
+		unsigned long lock_flags;
+		int ret = 0;
+
+		if (list_empty(&dev->outqueue)) {
+			if (dev->video_stream == STREAM_OFF) {
+				return -EINVAL;
+			}
+			if (filp->f_flags & O_NONBLOCK) {
+				return -EAGAIN;
+			}
+			ret = wait_event_interruptible(
+					dev->wait_frame,
+					(!list_empty(&dev->outqueue)) ||
+					(dev->state&DEV_DISCONNECTED));
+			if (ret) {
+				return ret;
+			}
+			if (dev->state & DEV_DISCONNECTED) {
+				return -ENODEV;
+			}
+		}
+
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		f = list_entry(dev->outqueue.next, struct em28xx_frame_t, frame);
+		list_del(dev->outqueue.next);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+		f->state = F_UNUSED;
+		return 0;
+	}
+
+	/* end v4l1 compat code */
+
+	case VIDIOC_REQBUFS:
+	{
+		struct v4l2_requestbuffers *rb = arg;
+		u32 i;
+		int ret;
+
+		if (rb->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				rb->memory == V4L2_MEMORY_MMAP) {
+			if (dev->vbi_reader == 1 && fh->reader == 0)
+				return -EBUSY;
+
+			if (dev->vbi_io == IO_READ) {
+				printk("method is set to read;"
+				       " close and open the device again to"
+				       " choose the mmap I/O method\n");
+				return -EBUSY;
+			}
+			em28xx_empty_framequeues(dev, EM28XX_VBI);
+
+			if (dev->vbi_stream == STREAM_ON) {
+				em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n");
+				ret = em28xx_stream_interrupt(dev, EM28XX_VBI);
+				if (ret) {
+					printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 4\n");
+					return ret;
+				}
+			}
+
+			em28xx_release_buffers(dev, EM28XX_VBI);
+
+			if (rb->count)
+				rb->count = em28xx_request_buffers(dev, rb->count, EM28XX_VBI);
+
+			dev->vbi_frame_current = NULL;
+
+			dev->vbi_stream = STREAM_ON;
+			em28xx_videodbg("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n",
+					rb->count);
+
+			dev->vbi_io = rb->count ? IO_MMAP : IO_NONE;
+			fh->reader = 1;
+			dev->vbi_reader = 1;
+			return 0;
+		}
+
+		if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+			rb->memory != V4L2_MEMORY_MMAP) {
+			return -EINVAL;
+		}
+
+		if (dev->video_io == IO_READ) {
+			em28xx_videodbg("method is set to read;"
+				" close and open the device again to"
+				" choose the mmap I/O method\n");
+			printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 2\n");
+			return -EBUSY;
+		}
+
+		for (i = 0; i < dev->num_frames; i++)
+			if (dev->frame[i].vma_use_count) {
+				em28xx_videodbg("VIDIOC_REQBUFS failed; previous buffers are still mapped\n");
+				printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 3\n");
+				return -EINVAL;
+			}
+
+		if (dev->video_stream == STREAM_ON) {
+			em28xx_videodbg("VIDIOC_REQBUFS: interrupting stream\n");
+			ret = em28xx_stream_interrupt(dev, EM28XX_VIDEO);
+			if (ret) {
+				printk(KERN_INFO"em28xx-video.c: VIDIOC_REQBUFS 4\n");
+				return ret;
+			}
+		}
+
+		em28xx_empty_framequeues(dev, EM28XX_VIDEO);
+
+		em28xx_release_buffers(dev, EM28XX_VIDEO);
+		if (rb->count)
+			rb->count =
+				em28xx_request_buffers(dev, rb->count, EM28XX_VIDEO);
+
+		dev->frame_current = NULL;
+
+		dev->video_stream = STREAM_ON;
+		em28xx_videodbg("VIDIOC_REQBUFS: setting io method to mmap: num bufs %i\n",
+				rb->count);
+		dev->video_io = rb->count ? IO_MMAP : IO_NONE;
+		return 0;
+	}
+	case VIDIOC_QUERYBUF:
+	{
+		struct v4l2_buffer *b = arg;
+
+		if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				b->index < dev->vbi_num_frames &&
+				dev->vbi_io == IO_MMAP) {
+			memcpy(b, &dev->vbi_frame[b->index].buf, sizeof(*b));
+
+			if (dev->vbi_frame[b->index].vma_use_count)
+				b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+			if (dev->frame[b->index].state == F_DONE)
+				b->flags |= V4L2_BUF_FLAG_DONE;
+			else if (dev->frame[b->index].state != F_UNUSED)
+				b->flags |= V4L2_BUF_FLAG_QUEUED;
+
+			return 0;
+		}
+
+		if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+			b->index >= dev->num_frames || dev->video_io != IO_MMAP)
+			return -EINVAL;
+
+		memcpy(b, &dev->frame[b->index].buf, sizeof(*b));
+
+		if (dev->frame[b->index].vma_use_count)
+			b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+		if (dev->frame[b->index].state == F_DONE)
+			b->flags |= V4L2_BUF_FLAG_DONE;
+		else if (dev->frame[b->index].state != F_UNUSED)
+			b->flags |= V4L2_BUF_FLAG_QUEUED;
+
+		return 0;
+	}
+	case VIDIOC_QBUF:
+	{
+		struct v4l2_buffer *b = arg;
+		unsigned long lock_flags;
+
+		if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				b->index < dev->vbi_num_frames &&
+				dev->vbi_io == IO_MMAP) {
+			if (dev->vbi_frame[b->index].state != F_UNUSED)
+				return -EAGAIN;
+
+			dev->vbi_frame[b->index].state = F_QUEUED;
+
+			spin_lock_irqsave(&dev->queue_lock, lock_flags);
+			list_add_tail(&dev->vbi_frame[b->index].frame,
+					&dev->vbi_inqueue);
+			spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+			return 0;
+		}
+
+		if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+			b->index >= dev->num_frames || dev->video_io != IO_MMAP) {
+			return -EINVAL;
+		}
+
+		if (dev->frame[b->index].state != F_UNUSED)
+			return -EAGAIN;
+
+		dev->frame[b->index].state = F_QUEUED;
+
+		/* add frame to fifo */
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		list_add_tail(&dev->frame[b->index].frame,
+				&dev->inqueue);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+
+		return 0;
+	}
+	case VIDIOC_DQBUF:
+	{
+		struct v4l2_buffer *b = arg;
+		struct em28xx_frame_t *f;
+		unsigned long lock_flags;
+		int ret = 0;
+
+		if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE &&
+				b->index < dev->vbi_num_frames &&
+				dev->vbi_io == IO_MMAP) {
+			if (list_empty(&dev->vbi_outqueue)) {
+				if (dev->vbi_stream == STREAM_OFF)
+					return -EINVAL;
+				if (filp->f_flags & O_NONBLOCK)
+					return -EAGAIN;
+				ret = wait_event_interruptible(dev->wait_vbi_frame, (!list_empty(&dev->vbi_outqueue)) ||
+						(dev->state & DEV_DISCONNECTED));
+
+				if (ret)
+					return ret;
+
+				if (dev->state & DEV_DISCONNECTED)
+					return -ENODEV;
+			}
+
+			spin_lock_irqsave(&dev->queue_lock, lock_flags);
+			f = list_entry(dev->vbi_outqueue.next,
+					struct em28xx_frame_t, frame);
+			list_del(dev->vbi_outqueue.next);
+			spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+
+			f->state = F_UNUSED;
+			memcpy(b, &f->buf, sizeof(*b));
+
+			if (f->vma_use_count)
+				b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+			return 0;
+		}
+
+		if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE
+			|| dev->video_io != IO_MMAP)
+			return -EINVAL;
+
+		if (list_empty(&dev->outqueue)) {
+			if (dev->video_stream == STREAM_OFF)
+				return -EINVAL;
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+			ret = wait_event_interruptible
+				(dev->wait_frame,
+				(!list_empty(&dev->outqueue)) ||
+				(dev->state & DEV_DISCONNECTED));
+			if (ret)
+				return ret;
+			if (dev->state & DEV_DISCONNECTED)
+				return -ENODEV;
+		}
+
+		spin_lock_irqsave(&dev->queue_lock, lock_flags);
+		f = list_entry(dev->outqueue.next,
+				struct em28xx_frame_t, frame);
+		list_del(dev->outqueue.next);
+		spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
+
+		f->state = F_UNUSED;
+		memcpy(b, &f->buf, sizeof(*b));
+
+		if (f->vma_use_count)
+			b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+		return 0;
+	}
+	default:
+		return em28xx_do_ioctl(inode, filp, dev, cmd, arg,
+				       em28xx_video_do_ioctl);
+	}
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_ioctl()
+ * handle v4l2 ioctl the main action happens in em28xx_v4l2_do_ioctl()
+ */
+static int em28xx_v4l2_ioctl(struct inode *inode, struct file *filp,
+			     unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+	struct em28xx_fh *fh = filp->private_data;
+	struct em28xx *dev = fh->dev;
+
+
+	if (dev->state & DEV_DISCONNECTED) {
+		em28xx_errdev("v4l2 ioctl: device not present\n");
+		return -ENODEV;
+	}
+
+	if (dev->state & DEV_MISCONFIGURED) {
+		em28xx_errdev
+		    ("v4l2 ioctl: device is misconfigured; close and open it again\n");
+		return -EIO;
+	}
+
+	ret = video_usercopy(inode, filp, cmd, arg, em28xx_video_do_ioctl);
+
+	return ret;
+}
+
+static struct file_operations em28xx_v4l_fops = {
+	.owner = THIS_MODULE,
+	.open = em28xx_v4l2_open,
+	.release = em28xx_v4l2_close,
+	.ioctl = em28xx_v4l2_ioctl,
+	.read = em28xx_v4l2_read,
+	.poll = em28xx_v4l2_poll,
+	.mmap = em28xx_v4l2_mmap,
+	.llseek = no_llseek,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
+#if 0
+	.compat_ioctl   = v4l_compat_ioctl32,
+#endif
+#endif
+
+};
+
+int em28xx_aad_control(void *priv, unsigned int command, void *ptr) {
+    struct em28xx *dev = (struct em28xx*)priv;
+    int ret = 0;
+    switch(command) {
+    case EM28XX_ENABLE_AUDIO:
+        ret = dev->em28xx_write_reg_bits(dev, R0F_XCLK_REG, 0x80, 0x80);
+	if(dev->alt == 0 ) {
+		int errCode;
+		dev->alt = 7;
+		errCode = usb_set_interface(dev->udev, dev->usb_interface, 7);
+		printk("changing alternate number to 7\n");
+	}
+        break;
+    case EM28XX_DISABLE_AUDIO:
+        if(dev->alt != 0) {
+            int errCode;
+            dev->alt = 0;
+            errCode = usb_set_interface(dev->udev, dev->usb_interface, 0);
+            printk("changing alternate number to 0\n");
+        }
+        break;
+    default:
+        break;
+    }
+    return ret;
+}
+
+/******************************** usb interface *****************************************/
+
+/*
+ * em28xx_init_dev()
+ * allocates and inits the device structs, registers i2c bus and v4l device
+ */
+static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
+			   int minor, int model)
+{
+	struct em28xx *dev = *devhandle;
+	int retval = -ENOMEM;
+	int errCode, i;
+	unsigned int maxh, maxw;
+	int ret;
+	int gpio_arg;
+	struct list_head *pos = NULL;
+	struct em28xx_ops *ops = NULL;
+	u8 value;
+
+	dev->model = model;
+	dev->dvb_dev = NULL;
+	init_waitqueue_head(&dev->open);
+
+	mutex_init(&dev->input_lock);
+
+	dev->em_type = em28xx_boards[model].em_type;
+	dev->has_tuner = em28xx_boards[model].has_tuner;
+	dev->has_inttuner = em28xx_boards[model].has_inttuner;
+	dev->powersaving = em28xx_boards[model].powersaving;
+	dev->has_msp34xx = em28xx_boards[model].has_msp34xx;
+	dev->tda9887_conf = em28xx_boards[model].tda9887_conf;
+	dev->decoder = em28xx_boards[model].decoder;
+	dev->em28xx_ctrl = em28xx_boards[model].ctrl;
+	dev->em28xx_qctrl = em28xx_boards[model].qctrl;
+	dev->em28xx_gctrl = em28xx_boards[model].gctrl;
+	dev->dev_modes = em28xx_boards[model].dev_modes;
+	dev->board = &em28xx_boards[model];
+	dev->outfmt = &em28xx_out_fmt[0]; /* default to YUYV */
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	INIT_DELAYED_WORK(&dev->request_module_wk, request_module_async);
+#endif
+	dev->tvnorm = &em28xx_boards[model].tvnorms[0];
+	dev->dvbnorm = &em28xx_boards[model].dvbnorms[0];
+	dev->fmnorm = &em28xx_boards[model].fmnorms[0];
+	dev->qamnorm = &em28xx_boards[model].qamnorms[0];
+	dev->atscnorm = &em28xx_boards[model].atscnorms[0];
+
+	dev->vbi_interlaced = vbi_interlaced;
+
+	dev->em28xx_gpio_control = em28xx_gpio_control;
+	dev->manual_gpio = em28xx_boards[model].manual_gpio;
+        dev->em28xx_aad_control = em28xx_aad_control;
+
+	/* check if gpio config is stored onboard */
+	dev->qamnorm = &em28xx_boards[model].qamnorms[0];
+	dev->atscnorm = &em28xx_boards[model].atscnorms[0];
+
+	em28xx_write_regs_req(dev, 0x02, 0xa0, "\x23", 1);
+	ret = dev->em28xx_read_reg(dev, 0x05);
+
+	if (ret == 0) {
+		value = em28xx_read_reg_req(dev, 0x2, 0xa0);
+		/* keep it overridable */
+		if (dev->manual_gpio == 0)
+			dev->manual_gpio = (em28xx_read_reg_req(dev, 0x2, 0xa0)>>3)&1;
+	}
+
+
+	if (dev->udev == NULL)
+		dev->udev = udev;
+
+	if (dev->dev_modes == EM28XX_DVBT || dev->dev_modes == EM28XX_ATSC)
+		dev->device_mode = 1;
+	else
+		dev->device_mode = device_mode;
+
+	if (vbi_mode == 0)
+		dev->dev_modes &= ~EM28XX_VBI;
+
+	if (dev->dev_modes & EM28XX_VBI)
+		em28xx_set_vbi(dev, 1);
+
+	if (tuner >= 0)
+		dev->tuner_type = tuner;
+	else
+		dev->tuner_type = em28xx_boards[model].tuner_type;
+
+	/* Do board specific init and eeprom reading */
+	ret = em28xx_card_setup(dev);
+
+	if (ret)
+		return ret;
+
+	gpio_arg = EM28XX_REG_OFF;
+	em28xx_gpio_control(dev, EM28XX_LED1_ON,      &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_TS1_ON,       &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_XC3028_SECAM, &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_TUNER2_ON,    &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_DECODER_SLEEP, &gpio_arg);
+	mdelay(100);
+	gpio_arg = EM28XX_REG_ON;
+	em28xx_gpio_control(dev, EM28XX_ANALOG_ON,    &gpio_arg);
+	em28xx_gpio_control(dev, EM28XX_TUNER1_ON,    &gpio_arg);
+	mdelay(100);
+
+	em28xx_gpio_control(dev, EM28XX_DECODER_RESET, NULL);
+
+#ifdef CONFIG_MODULES
+	/* request some modules */
+	if (dev->has_tuner && dev->has_inttuner == 0)
+		request_module("tuner");
+	if (dev->decoder == EM28XX_SAA7113 || dev->decoder == EM28XX_SAA7114)
+		request_module("saa7115");
+	if (dev->decoder == EM28XX_TVP5150)
+		request_module("tvp5150");
+	if (dev->tda9887_conf)
+		request_module("tda9887");
+	if (dev->decoder == EM28XX_CX25843)
+		request_module("em28xx-cx25843");
+#endif
+	spin_lock_init(&dev->queue_lock);
+	spin_lock_init(&dev->vbi_queue_lock);
+	init_waitqueue_head(&dev->wait_frame);
+	init_waitqueue_head(&dev->wait_vbi_frame);
+	init_waitqueue_head(&dev->video_wait_stream);
+	init_waitqueue_head(&dev->vbi_wait_stream);
+
+	if (ret < 0) {
+		printk("%s:%u:%s(): em28xx_card_setup() failed, ret = %i\n", __FILE__, __LINE__, __FUNCTION__, ret);
+		return ret;
+	}
+
+	em28xx_i2c_register(dev);
+
+	em28xx_config_i2c(dev);
+
+	if (dev->has_inttuner) {
+		switch (dev->tuner_type) {
+		case TUNER_XCEIVE_XC3028:
+		{
+			struct xc3028_config config;
+			memset(&config, 0x0, sizeof(struct xc3028_config));
+			config.new_tv_mode_ptr = em28xx_boards[model].tvnorms[0].tv_mode;
+			config.new_channel_map_ptr = em28xx_boards[model].tvnorms[0].channelmap;
+			config.callback = em28xx_gpio_control_translate;
+			config.dev = dev;
+			config.adap = &dev->i2c_adap;
+			config.i2c_address = 0xc2>>1;
+#if 0
+			em28xx_gpio_control(dev, EM28XX_XC3028_SECAM, arg);
+#endif
+			dev->tuner = tuner_chip_attach(xc3028_tuner_attach, &config);
+			break;
+		}
+		case TUNER_XCEIVE_XC5000:
+		{
+			struct xc5000_config config;
+			printk("trying to attach xc5000\n");
+			memset(&config, 0x0, sizeof(struct xc5000_config));
+			config.callback = em28xx_gpio_control_translate;
+			config.dev = dev;
+			config.adap = &dev->i2c_adap;
+			config.i2c_address = 0xc2>>1;
+			dev->tuner = tuner_chip_attach(xc5000_tuner_attach, &config, XC5000_firmware_SEQUENCE);
+			break;
+		}
+		}
+
+		/* configure the device */
+
+		if (dev->tuner == NULL)
+			printk(KERN_INFO"unable to attach tuner\n");
+		else
+			printk(KERN_INFO"successfully attached tuner\n");
+	}
+
+	dev->em28xx_acquire = em28xx_acquire;
+
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 1);
+
+	list_add_tail(&dev->devlist, &em28xx_devlist);
+
+	if (dev->dev_modes&EM28XX_RADIO) {
+		dev->rdev = video_device_alloc();
+		memset(dev->rdev, 0x0, sizeof(struct video_device));
+
+		if (NULL == dev->rdev) {
+			list_del(&dev->devlist);
+			kfree(dev);
+			return -ENOMEM;
+		}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+		dev->rdev->type = VID_TYPE_TUNER;
+		dev->rdev->dev = &dev->udev->dev;
+#else
+		dev->rdev->vfl_type = VID_TYPE_TUNER;
+		dev->rdev->parent = &dev->udev->dev;
+#endif
+		dev->rdev->fops = &em28xx_v4l_fops;
+		dev->rdev->minor = -1;
+		dev->rdev->release = video_device_release;
+		snprintf(dev->rdev->name, sizeof(dev->rdev->name), "%s#%d %s",
+				"em28xx", dev->devno, "radio");
+
+		if (video_register_device(dev->rdev, VFL_TYPE_RADIO,
+					radio_nr[dev->devno]) < 0) {
+			printk(KERN_INFO"unable to register radio device\n");
+			list_del(&dev->devlist);
+			kfree(dev);
+			return -ENODEV;
+		}
+		printk(KERN_INFO"radio device registered as /dev/radio%d\n",
+					dev->rdev->minor & 0x1f);
+	}
+
+	if (dev->dev_modes&EM28XX_VIDEO) {
+
+		dev->video_inputs = em28xx_boards[model].vchannels;
+
+		dev->tvnorm = &em28xx_boards[model].tvnorms[0];	/* set default norm */
+		for (i = 0; em28xx_boards[model].tvnorms[i].id; i++)
+			if (em28xx_boards[model].norm == em28xx_boards[model].tvnorms[i].id) {
+				dev->tvnorm = &em28xx_boards[model].tvnorms[i];	/* set default norm */
+				break;
+			}
+
+		em28xx_videodbg("tvnorm = %s\n", dev->tvnorm->name);
+
+		maxw = norm_maxw(dev);
+		maxh = norm_maxh(dev);
+
+		/* set default image size */
+		dev->width = maxw;
+		dev->height = maxh;
+		dev->interlaced = EM28XX_INTERLACED_DEFAULT;
+		dev->field_size = dev->width * dev->height;
+		dev->frame_size =
+			dev->interlaced ? dev->field_size << 1 : dev->field_size;
+		dev->bytesperline = dev->width * 2;
+		dev->hscale = 0;
+		dev->vscale = 0;
+		dev->ctl_input = 2;
+
+		if (dev->dev_modes&EM28XX_VIDEO) {
+			errCode = em28xx_config(dev); /* TODO: check for errorvalue */
+			if (errCode) {
+				em28xx_errdev("error configuring device\n");
+				em28xx_devused &= ~(1<<dev->devno);
+				list_del(&dev->devlist);
+				kfree(dev);
+				return -ENOMEM;
+			}
+		}
+
+#ifdef CONFIG_MODULES
+		/* FIXME: is this module really required? some devices just spit errors
+			  when using that modules */
+		if (dev->has_msp34xx)
+			request_module("msp3400");
+#endif
+
+		/* allocate and fill v4l2 device struct */
+		dev->vdev = video_device_alloc();
+		if (NULL == dev->vdev) {
+			em28xx_errdev("cannot allocate video_device.\n");
+			em28xx_devused &= ~(1<<dev->devno);
+			list_del(&dev->devlist);
+			kfree(dev);
+			return -ENOMEM;
+		}
+		memset(dev->vdev, 0x0, sizeof(struct video_device));
+
+
+		/* Fills CAPTURE device info */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+		dev->vdev->type = VID_TYPE_CAPTURE;
+		if (dev->has_tuner)
+			dev->vdev->type |= VID_TYPE_TUNER;
+		dev->vdev->dev = &dev->udev->dev;
+#else
+		dev->vdev->vfl_type = VID_TYPE_CAPTURE;
+		if (dev->has_tuner)
+			dev->vdev->vfl_type |= VID_TYPE_TUNER;
+		dev->vdev->parent = &dev->udev->dev;
+#endif
+		dev->vdev->fops = &em28xx_v4l_fops;
+		dev->vdev->minor = -1;
+		dev->vdev->release = video_device_release;
+		snprintf(dev->vdev->name, sizeof(dev->vbi_dev->name), "%s#%d %s",
+				"em28xx", dev->devno, "video");
+
+		/* register v4l2 device */
+		if ((retval = video_register_device(dev->vdev, VFL_TYPE_GRABBER,
+						video_nr[dev->devno]))) {
+			em28xx_errdev("unable to register video device (error = %i).\n",
+					retval);
+			list_del(&dev->devlist);
+			video_device_release(dev->vdev);
+			em28xx_devused &= ~(1<<dev->devno);
+			kfree(dev);
+			return -ENODEV;
+		}
+		if (dev->dev_modes&EM28XX_VBI) {
+
+			dev->vbi_dev = video_device_alloc();
+			if (NULL == dev->vbi_dev) {
+				em28xx_errdev("cannot allocate video_device.\n");
+				em28xx_devused &= ~(1<<dev->devno);
+				list_del(&dev->devlist);
+				video_device_release(dev->vdev);
+				kfree(dev);
+				return -ENOMEM;
+			}
+			memset(dev->vbi_dev, 0x0, sizeof(struct video_device));
+
+			/* Fills VBI device info */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+			dev->vbi_dev->type = VFL_TYPE_VBI;
+			dev->vbi_dev->dev = &dev->udev->dev;
+#else
+			dev->vbi_dev->vfl_type = VFL_TYPE_VBI;
+			dev->vbi_dev->parent = &dev->udev->dev;
+#endif
+			dev->vbi_dev->fops = &em28xx_v4l_fops;
+			dev->vbi_dev->minor = -1;
+			dev->vbi_dev->release = video_device_release;
+			snprintf(dev->vbi_dev->name, sizeof(dev->vbi_dev->name), "%s#%d %s",
+					"em28xx", dev->devno, "vbi");
+
+			if (video_register_device(dev->vbi_dev, VFL_TYPE_VBI,
+						vbi_nr[dev->devno]) < 0) {
+				printk(KERN_INFO"unable to register vbi device\n");
+				list_del(&dev->devlist);
+				video_device_release(dev->vbi_dev);
+				video_device_release(dev->vdev);
+				em28xx_devused &= ~(1<<dev->devno);
+				kfree(dev);
+				return -ENODEV;
+			}
+			em28xx_info("V4L2 VBI device registered as /dev/vbi%d\n",
+					dev->vbi_dev->num);
+		}
+		video_mux(dev, 0);
+		em28xx_info("V4L2 device registered as /dev/video%d\n",
+				dev->vdev->num);
+	}
+
+	/* when reattaching the device reinitialize the attached submodules */
+	mutex_lock(&em28xx_extension_devlist_lock);
+	if (!list_empty(&em28xx_extension_devlist)) {
+		list_for_each(pos, &em28xx_extension_devlist) {
+			ops = list_entry(pos, struct em28xx_ops, next);
+			if (ops->id & dev->dev_modes)
+				ops->init(dev);
+		}
+	}
+	mutex_unlock(&em28xx_extension_devlist_lock);
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	request_modules(dev);
+#else
+	dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+#endif
+	if (dev->board->ir_i2c == 0 &&
+			dev->board->ir_keytab &&
+			dev->board->ir_getkey) 
+		em2880_ir_attach(dev);
+
+	return 0;
+}
+
+/*
+ * em28xx_generic_probe()
+ * probes generic em28xx devices
+ * TODO: add em2800 based card detection (cinergy 200)
+ *
+ */
+
+struct drequest_t {
+	int brequest;
+	int index;
+	char *buffer;
+	int retval;
+	int blen;
+	int msecs;
+};
+
+static int em28xx_generic_probe(int *model, struct usb_device *udev,
+		struct em28xx *dev)
+{
+	int i;
+	char buffer = -1;
+	char *sendbuf;
+	int ret;
+	int rv = 0;
+	switch ((*model)) {
+		case EM2870_BOARD_TERRATEC_XS:
+		{
+			sendbuf = kmalloc(1, GFP_KERNEL);
+			memcpy(sendbuf, "\x00", 1);
+			ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02,
+					USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+					0x0000, 0x00c0, sendbuf, 1, HZ);
+			kfree(sendbuf);
+			ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02,
+					USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+					0x0000, 0x00c0, &buffer, 1, HZ);
+			if (buffer == 0x63) {
+				(*model) = EM2870_BOARD_TERRATEC_XS_MT2060;
+				printk(KERN_INFO"em28xx-video.c: New Terratec XS Detected\n");
+			}
+		}
+		return 0;
+		case EM2800_BOARD_LEADTEK_WINFAST_USBII:
+		{
+			/* seems like leadtek didn't change the product id for em2820/em2800 based
+			   devices */
+			ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+					USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+					0x0000, 0x000a, &buffer, 1, HZ);
+			switch (buffer) {
+			case 0x12:
+				printk("EM2820 based device detected\n");
+				(*model) = EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE;
+				return 0;
+			}
+		}
+		case EM2800_BOARD_GENERIC:
+		{
+			u8 i2c_devs[128];
+			int nr_i2c_devs = 0;
+			printk(KERN_INFO"em28xx: generic EM2800 board trying to guess card by i2c addresses\n");
+			/* do a i2c scan */
+			for (i = 0; i < 128; i++) {
+				if (!em2800_i2c_check_for_device(dev, i<<1))
+					i2c_devs[nr_i2c_devs++] = i<<1;
+			}
+			/* something went wrong */
+			if (!nr_i2c_devs)
+				break;
+			/* now loop through all em2800 cards */
+			for (i = 0; i < em28xx_bcount; i++) {
+				/* if the card definition contains a list of i2c devices */
+				if (em28xx_boards[i].em_type == EM2800 &&
+						em28xx_boards[i].i2c_devs) {
+					int x;
+					/* compare the list to the scan results */
+					for (x = 0; x < nr_i2c_devs &&
+						em28xx_boards[i].i2c_devs[x];
+						x++) {
+						if (i2c_devs[x] !=
+						   em28xx_boards[i].i2c_devs[x])
+							break;
+					}
+					if (x == nr_i2c_devs  && !em28xx_boards[i].i2c_devs[x]) {
+						(*model) = i;
+						return 0;
+					}
+				}
+			}
+		}
+		break;
+		case EM2861_BOARD_GENERIC:
+		{
+			struct drequest_t yakumo[] = {
+				{0x03, 0xb8, "\x00", 0x00, 1, 0}
+				/* this is the only i2c port which is
+				   available */
+			};
+			for (i = 0, rv = 0; i < ARRAY_SIZE(yakumo); i++) {
+				sendbuf = kmalloc(yakumo[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, yakumo[i].buffer, yakumo[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), yakumo[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, yakumo[i].index, sendbuf, yakumo[i].blen, HZ);
+				kfree(sendbuf);
+				if (yakumo[i].msecs)
+					msleep(yakumo[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != yakumo[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2861_BOARD_YAKUMO_MOVIE_MIXER;
+				return 0;
+			}
+
+		}
+		break;
+		case EM2820_BOARD_GENERIC:
+		{
+			struct drequest_t videology[] = { /* todo videology
+							     has a device
+							     identifier
+							     somewhere in the
+							     eeprom */
+				{0x03, 0xa2, "\x00", 0x00, 1, 0},
+				{0x03, 0xa0, "\x00", 0x00, 1, 0},
+				{0x03, 0x68, "\x00", 0x00, 1, 0},
+				{0x03, 0xa4, "\x00", 0x00, 1, 0},
+				{0x03, 0xa6, "\x00", 0x00, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x10, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+			};
+
+			struct drequest_t hercules_stv[] = {
+				{0x03, 0x42, "\x00", 0x10, 1, 0},
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+				{0x03, 0x60, "\x00", 0x00, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x68, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xa0, "\x00", 0x00, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x10, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc6, "\x00", 0x00, 1, 0},
+			};
+
+			struct drequest_t gadmei_utv_310[] = {
+				{0x03, 0x42, "\x00", 0x10, 1, 0},
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+				{0x03, 0x60, "\x00", 0x10, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x68, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xa0, "\x00", 0x10, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x10, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+			};
+
+			struct drequest_t msi_vox[] = {
+				{0x03, 0x42, "\x00", 0x00, 1, 0},
+				{0x03, 0x4a, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x00, 1, 0},
+				{0x03, 0xa0, "\x00", 0x10, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x00, 1, 0},
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+			};
+			struct drequest_t msi_vox_ntsc[] = {
+				{0x03, 0x42, "\x00", 0x00, 1, 0},
+/*					{0x03, 0x66, "\x00", 0x00, 1, 0}, */
+/*					{0x03, 0x68, "\x00", 0x00, 1, 0}, */
+				{0x03, 0xa0, "\x00", 0x10, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x00, 1, 0},
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+			};
+
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(videology); i++) {
+				/* usb_control_msg() expects kmalloced memory, otherwise the host controller will die */
+				sendbuf = kmalloc(videology[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, videology[i].buffer, videology[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), videology[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, videology[i].index, sendbuf, videology[i].blen, HZ);
+				kfree(sendbuf);
+				if (videology[i].msecs)
+					msleep(videology[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != videology[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_VIDEOLOGY_20K14XUSB;
+				return 0;
+			}
+
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(msi_vox); i++) {
+				sendbuf = kmalloc(msi_vox[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, msi_vox[i].buffer, msi_vox[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), msi_vox[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, msi_vox[i].index, sendbuf, msi_vox[i].blen, HZ);
+				kfree(sendbuf);
+				if (msi_vox[i].msecs)
+					msleep(msi_vox[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != msi_vox[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_MSI_VOX_USB_2;
+				return 0;
+			}
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(hercules_stv); i++) {
+				sendbuf = kmalloc(hercules_stv[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, hercules_stv[i].buffer, hercules_stv[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), hercules_stv[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, hercules_stv[i].index, sendbuf, hercules_stv[i].blen, HZ);
+				kfree(sendbuf);
+				if (hercules_stv[i].msecs)
+					msleep(hercules_stv[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != hercules_stv[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_HERCULES_SMART_TV_USB2;
+				return 0;
+			}
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(gadmei_utv_310); i++) {
+				sendbuf = kmalloc(gadmei_utv_310[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, gadmei_utv_310[i].buffer, gadmei_utv_310[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), gadmei_utv_310[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, gadmei_utv_310[i].index, sendbuf, gadmei_utv_310[i].blen, HZ);
+				kfree(sendbuf);
+				if (gadmei_utv_310[i].msecs)
+					msleep(gadmei_utv_310[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != gadmei_utv_310[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_GADMEI_UTV310;
+				return 0;
+			}
+			for (i = 0, rv = 0; i < ARRAY_SIZE(msi_vox_ntsc); i++) {
+				sendbuf = kmalloc(msi_vox_ntsc[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, msi_vox_ntsc[i].buffer, msi_vox_ntsc[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), msi_vox_ntsc[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, msi_vox_ntsc[i].index, sendbuf, msi_vox_ntsc[i].blen, HZ);
+				kfree(sendbuf);
+				if (msi_vox_ntsc[i].msecs)
+					msleep(msi_vox_ntsc[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != msi_vox_ntsc[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2820_BOARD_MSI_VOX_USB_2;
+				return 0;
+			}
+		}
+		break;
+		case EM2750_BOARD_GENERIC:
+		{
+			/* TODO */
+			(*model) = EM2750_BOARD_DLCW_130;
+			return 0;
+		}
+		break;
+		case EM2860_BOARD_GENERIC:
+		{
+			struct drequest_t gadmei_utv_330[] = {
+				{0x03, 0x42, "\x00", 0x10, 1, 0},
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+				{0x03, 0x60, "\x00", 0x10, 1, 0},
+				{0x03, 0x66, "\x00", 0x10, 1, 0},
+				{0x03, 0x68, "\x00", 0x10, 1, 0},
+				{0x03, 0x86, "\x00", 0x10, 1, 0},
+				{0x03, 0xa0, "\x00", 0x00, 1, 0},
+				{0x03, 0xa2, "\x00", 0x10, 1, 0},
+				{0x03, 0xc0, "\x00", 0x00, 1, 0},
+				{0x03, 0xc2, "\x00", 0x10, 1, 0},
+			};
+			struct drequest_t netgmbh_cam[] = {
+#if 0
+				{0x02, 0x48, "\x00", 0x00, 1, 0},
+#endif
+				{0x03, 0x5a, "\x00", 0x00, 1, 0},
+			};
+
+			struct drequest_t typhoon_dvdmaker[] = {
+				{0x03, 0x4a, "\x00", 0x00, 1, 0},
+			};
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(gadmei_utv_330);
+					i++) {
+				sendbuf = kmalloc(gadmei_utv_330[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, gadmei_utv_330[i].buffer, gadmei_utv_330[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), gadmei_utv_330[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, gadmei_utv_330[i].index, sendbuf, gadmei_utv_330[i].blen, HZ);
+				kfree(sendbuf);
+				if (gadmei_utv_330[i].msecs)
+					msleep(gadmei_utv_330[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != gadmei_utv_330[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2860_BOARD_GADMEI_UTV330;
+				return 0;
+			}
+			for (i = 0, rv = 0; i < ARRAY_SIZE(netgmbh_cam); i++) {
+				sendbuf = kmalloc(netgmbh_cam[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, netgmbh_cam[i].buffer, netgmbh_cam[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), netgmbh_cam[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, netgmbh_cam[i].index, sendbuf, netgmbh_cam[i].blen, HZ);
+				kfree(sendbuf);
+				if (netgmbh_cam[i].msecs)
+					msleep(netgmbh_cam[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != netgmbh_cam[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2860_BOARD_NETGMBH_CAM;
+				return 0;
+			}
+
+
+			for (i = 0, rv = 0; i < ARRAY_SIZE(typhoon_dvdmaker); i++) {
+				sendbuf = kmalloc(typhoon_dvdmaker[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, typhoon_dvdmaker[i].buffer, typhoon_dvdmaker[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), typhoon_dvdmaker[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, typhoon_dvdmaker[i].index, sendbuf, typhoon_dvdmaker[i].blen, HZ);
+				kfree(sendbuf);
+				if (typhoon_dvdmaker[i].msecs)
+					msleep(typhoon_dvdmaker[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != typhoon_dvdmaker[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2860_BOARD_TYPHOON_DVD_MAKER;
+				return 0;
+			}
+		}
+		break;
+		case EM2821_BOARD_GENERIC:
+			{
+				struct drequest_t usbgear[] = {
+					{0x03, 0xa2, "\x00", 0x10, 1, 0},
+					{0x03, 0xa0, "\x00", 0x10, 1, 0},
+					{0x03, 0x68, "\x00", 0x10, 1, 0},
+					{0x03, 0x4a, "\x00", 0x00, 1, 0},
+					{0x03, 0x66, "\x00", 0x10, 1, 0},
+					{0x03, 0x68, "\x00", 0x10, 1, 0},
+					{0x03, 0x86, "\x00", 0x10, 1, 0},
+					{0x03, 0xc0, "\x00", 0x10, 1, 0},
+					{0x03, 0xc2, "\x00", 0x10, 1, 0},
+				};
+				struct drequest_t siig_prolink[] = {
+					{0x03, 0x4a, "\x00", 0x00, 1, 0},
+					{0x03, 0x60, "\x00", 0x00, 1, 0},
+					{0x03, 0xa0, "\x00", 0x00, 1, 0},
+					{0x03, 0xc6, "\x00", 0x00, 1, 0},
+				};
+				struct drequest_t supercomp[] = {
+					{0x03, 0x4a, "\x00", 0x00, 1, 0},
+#if 0
+					{0x03, 0x60, "\x00", 0x00, 1, 0},
+#endif
+					{0x03, 0xc2, "\x00", 0x00, 1, 0},
+					{0x03, 0xc6, "\x00", 0x00, 1, 0},
+				};
+
+				for (i = 0, rv = 0; i < ARRAY_SIZE(usbgear); i++) {
+					sendbuf = kmalloc(usbgear[i].blen, GFP_KERNEL);
+					memcpy(sendbuf, usbgear[i].buffer, usbgear[i].blen);
+					ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), usbgear[i].brequest,
+							USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, usbgear[i].index, sendbuf, usbgear[i].blen, HZ);
+					kfree(sendbuf);
+					if (usbgear[i].msecs)
+						msleep(usbgear[i].msecs);
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, 0x05, &buffer, 1, HZ);
+					if (buffer != usbgear[i].retval) {
+						rv = 1;
+						break;
+					}
+				}
+				if (rv == 0) {
+					(*model) = EM2821_BOARD_USBGEAR_VD204;
+					return 0;
+				}
+
+				for (i = 0, rv = 0; i < ARRAY_SIZE(siig_prolink); i++) {
+					sendbuf = kmalloc(siig_prolink[i].blen, GFP_KERNEL);
+					memcpy(sendbuf, siig_prolink[i].buffer, siig_prolink[i].blen);
+					ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), siig_prolink[i].brequest,
+							USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, siig_prolink[i].index, sendbuf, siig_prolink[i].blen, HZ);
+					kfree(sendbuf);
+					if (siig_prolink[i].msecs)
+						msleep(siig_prolink[i].msecs);
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, 0x05, &buffer, 1, HZ);
+					if (buffer != siig_prolink[i].retval) {
+						rv = 1;
+						break;
+					}
+				}
+				if (rv == 0) {
+					(*model) = EM2821_BOARD_PROLINK_PLAYTV_USB2;
+					return 0;
+				}
+				for (i = 0, rv = 0; i < ARRAY_SIZE(supercomp); i++) {
+					sendbuf = kmalloc(supercomp[i].blen, GFP_KERNEL);
+					memcpy(sendbuf, supercomp[i].buffer, supercomp[i].blen);
+					ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), supercomp[i].brequest,
+							USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, supercomp[i].index, sendbuf, supercomp[i].blen, HZ);
+					kfree(sendbuf);
+					if (supercomp[i].msecs)
+						msleep(supercomp[i].msecs);
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, 0x05, &buffer, 1, HZ);
+					if (buffer != supercomp[i].retval) {
+						rv = 1;
+						break;
+					}
+				}
+				if (rv == 0) {
+					(*model) = EM2821_BOARD_SUPERCOMP_USB_2;
+					return 0;
+				}
+			}
+			break;
+		case EM2870_BOARD_GENERIC:
+		{
+			struct drequest_t pinnacle_pctv[] = {
+				/* {0x03, 0xb8, "\x00", 0x00, 1, 0}, tvp5150 -- it's possible that this IC isn't reachable*/
+				{0x03, 0xa0, "\x00", 0x00, 1, 0}, /* eeprom */
+				{0x03, 0xc0, "\x00", 0x00, 1, 0}, /* mt2060 */
+			};
+			for (i = 0, rv = 0; i < ARRAY_SIZE(pinnacle_pctv);
+					i++) {
+				sendbuf = kmalloc(pinnacle_pctv[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, pinnacle_pctv[i].buffer, pinnacle_pctv[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), pinnacle_pctv[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, pinnacle_pctv[i].index, sendbuf, pinnacle_pctv[i].blen, HZ);
+				kfree(sendbuf);
+				if (pinnacle_pctv[i].msecs)
+					msleep(pinnacle_pctv[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (buffer != pinnacle_pctv[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2870_BOARD_PINNACLE_PCTV_DVB;
+				return 0;
+			}
+		}
+		break;
+		/* no other card has been available yet with that IC,
+		   so we assume HVR_950 by default */
+		case EM2883_BOARD_GENERIC:
+		{
+			(*model) = EM2883_BOARD_EMPIA_HYBRID_ATSC;
+			return 0;
+		}
+		break;
+		case EM2881_BOARD_GENERIC:
+		{
+			struct drequest_t dnt_hybrid[] = {
+				{0x03, 0xa0, "\x15", 0x56, 1, 0},
+#if 0
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x08", 0x00, 1, 20},
+				{0x00, 0x08, "\x6f", 0x00, 1, 20},
+				{0x00, 0x08, "\x7f", 0x00, 1, 20},
+				{0x03, 0xb8, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x0c", 0x00, 1, 20},
+				{0x00, 0x08, "\x6e", 0x00, 1, 20},
+				{0x00, 0x08, "\x7e", 0x00, 1, 20},
+				{0x03, 0x1e, "\x00", 0x00, 1, 0},
+#endif
+			};
+			struct drequest_t pinnacle_hybrid_pro[] = {
+				{0x03, 0xa0, "\x15", 0x57, 1, 0},
+#if 0
+				{0x03, 0xc2, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x08", 0x00, 1, 20},
+				{0x00, 0x08, "\x6f", 0x00, 1, 20},
+				{0x00, 0x08, "\x7f", 0x00, 1, 20},
+				{0x03, 0xb8, "\x00", 0x00, 1, 0},
+				{0x00, 0x04, "\x0c", 0x00, 1, 20},
+				{0x00, 0x08, "\x6e", 0x00, 1, 20},
+				{0x00, 0x08, "\x7e", 0x00, 1, 20},
+				{0x03, 0x1e, "\x00", 0x00, 1, 0},
+#endif
+			};
+			for (i = 0, rv = 0; i < ARRAY_SIZE(pinnacle_hybrid_pro); i++)
+			{
+				sendbuf = kmalloc(pinnacle_hybrid_pro[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, pinnacle_hybrid_pro[i].buffer, pinnacle_hybrid_pro[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), pinnacle_hybrid_pro[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, pinnacle_hybrid_pro[i].index, sendbuf, pinnacle_hybrid_pro[i].blen, HZ);
+				kfree(sendbuf);
+				if (pinnacle_hybrid_pro[i].msecs)
+					msleep(pinnacle_hybrid_pro[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (pinnacle_hybrid_pro[i].index == 0xa0)
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, pinnacle_hybrid_pro[i].index, &buffer, 1, HZ);
+
+				if (buffer != pinnacle_hybrid_pro[i].retval) {
+					printk("FAILED: 0x%02x 0x%02x\n", pinnacle_hybrid_pro[i].index, buffer);
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2881_BOARD_PINNACLE_HYBRID_PRO;
+				return 0;
+			}
+			for (i = 0, rv = 0; i < ARRAY_SIZE(dnt_hybrid); i++)
+			{
+				sendbuf = kmalloc(dnt_hybrid[i].blen, GFP_KERNEL);
+				memcpy(sendbuf, dnt_hybrid[i].buffer, dnt_hybrid[i].blen);
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), dnt_hybrid[i].brequest,
+						USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, dnt_hybrid[i].index, sendbuf, dnt_hybrid[i].blen, HZ);
+				kfree(sendbuf);
+				if (dnt_hybrid[i].msecs)
+					msleep(dnt_hybrid[i].msecs);
+				ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+						USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+						0x0000, 0x05, &buffer, 1, HZ);
+				if (dnt_hybrid[i].index == 0xa0)
+					ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x02,
+							USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+							0x0000, pinnacle_hybrid_pro[i].index, &buffer, 1, HZ);
+				if (buffer != dnt_hybrid[i].retval) {
+					rv = 1;
+					break;
+				}
+			}
+			if (rv == 0) {
+				(*model) = EM2881_BOARD_DNT_DA2_HYBRID;
+				return 0;
+			}
+			}
+			break;
+		default:
+			return 0;
+	}
+
+	em28xx_errdev("Your board has no eeprom inside it and thus can't\n"
+			"%s: be autodetected.  Please pass card = <n> insmod option to\n"
+			"%s: workaround that.  Redirect complaints to the vendor of\n"
+			"%s: the TV card. Generic type will be used.\n"
+			"%s: Best regards, \n"
+			"%s:         -- tux\n",
+			dev->name, dev->name, dev->name, dev->name, dev->name);
+	em28xx_errdev("%s: Here is a list of valid choices for the card = <n> insmod option:\n",
+			dev->name);
+	for (i = 0; i < em28xx_bcount; i++) {
+		em28xx_errdev("    card = %d -> %s\n", i,
+				em28xx_boards[i].name);
+	}
+	return 0;
+}
+
+/*
+ * em28xx_usb_probe()
+ * checks for supported devices
+ */
+
+static int em28xx_usb_probe(struct usb_interface *interface,
+			    const struct usb_device_id *id)
+{
+	const struct usb_endpoint_descriptor *endpoint;
+	struct usb_device *udev;
+	struct usb_interface *uif;
+	struct em28xx *dev = NULL;
+	int retval = -ENODEV;
+	int model, i, nr, ifnum, is_em2875 = 0;
+
+	udev = usb_get_dev(interface_to_usbdev(interface));
+
+	if (udev == NULL) {
+		printk("something's weird here udev returned 0!\n");
+		return -EINVAL;
+	}
+
+	ifnum = interface->altsetting[0].desc.bInterfaceNumber;
+
+	/* Check to see next free device and mark as used */
+	nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS);
+	em28xx_devused |= 1<<nr;
+
+	/* Don't register audio interfaces */
+	if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+		printk(KERN_INFO "audio device (%04x:%04x): interface %i, class %i\n",
+				udev->descriptor.idVendor, udev->descriptor.idProduct,
+				ifnum,
+				interface->altsetting[0].desc.bInterfaceClass);
+
+		em28xx_devused &= ~(1<<nr);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "em28xx: new video device (%04x:%04x): interface %i, class %i\n",
+			udev->descriptor.idVendor, udev->descriptor.idProduct,
+			ifnum,
+			interface->altsetting[0].desc.bInterfaceClass);
+
+
+	/* check if the device has the iso in endpoint at the correct place */
+
+        endpoint = &interface->cur_altsetting->endpoint[0].desc;
+	if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+			USB_ENDPOINT_XFER_ISOC &&
+		(interface->altsetting[1].endpoint[0].desc.wMaxPacketSize == 940)) {
+		printk(KERN_INFO "em28xx: DTV Device detected\n");
+		is_em2875 = 1;
+	} else {
+		endpoint = &interface->cur_altsetting->endpoint[1].desc;
+		if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
+				USB_ENDPOINT_XFER_ISOC) {
+			em28xx_err("em28xx: probing error: endpoint is non-ISO endpoint!\n");
+			em28xx_devused &= ~(1<<nr);
+			return -ENODEV;
+		}
+		if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) {
+			em28xx_err("em28xx: probing error: endpoint is ISO OUT endpoint!\n");
+			em28xx_devused &= ~(1<<nr);
+			return -ENODEV;
+		}
+	}
+
+	switch (udev->speed) {
+	case USB_SPEED_HIGH:
+		printk(KERN_INFO"em28xx: device is attached to a USB 2.0 bus\n");
+		break;
+	case USB_SPEED_FULL:
+		printk(KERN_INFO"em28xx: setting up device on a USB 1.1 bus\n");
+		/* passthrough */
+	default:
+		printk(KERN_INFO"em28xx: your device won't work properly when\n");
+		printk(KERN_INFO"em28xx: not attached to a USB 2.0 highspeed bus\n");
+		printk(KERN_INFO"em28xx: more information:\n");
+		printk(KERN_INFO"em28xx: http://mcentral.de/wiki/index.php5/Talk:Em2880\n");
+	}
+
+	model = id->driver_info;
+
+	if (nr >= EM28XX_MAXBOARDS) {
+		printk(DRIVER_NAME ": Supports only %i em28xx boards.\n", EM28XX_MAXBOARDS);
+		em28xx_devused &= ~(1<<nr);
+		return -ENOMEM;
+	}
+
+	/* allocate memory for our device state and initialize it */
+	dev = kzalloc(sizeof(struct em28xx), GFP_KERNEL);
+	if (dev == NULL) {
+		em28xx_err(DRIVER_NAME ": out of memory!\n");
+		em28xx_devused &= ~(1<<nr);
+		return -ENOMEM;
+	}
+
+	snprintf(dev->name, 28, "em28xx #%d", nr);
+	dev->devno = nr;
+
+	dev->udev = udev;
+
+	dev->em28xx_write_regs = em28xx_write_regs;
+	dev->em28xx_read_reg = em28xx_read_reg;
+	dev->em28xx_read_reg_req_len = em28xx_read_reg_req_len;
+	dev->em28xx_write_regs_req = em28xx_write_regs_req;
+	dev->em28xx_read_reg_req = em28xx_read_reg_req;
+	dev->em28xx_write_reg_bits = em28xx_write_reg_bits;	
+	dev->usb_interface = ifnum;
+
+	/* compute alternate max packet sizes */
+	uif = udev->actconfig->interface[ifnum];
+
+	dev->num_alt = uif->num_altsetting;
+	dev->uif = uif;
+
+	em28xx_info("Alternate settings: %i\n", dev->num_alt);
+	dev->alt_max_pkt_size = kmalloc((dev->num_alt+1)*sizeof(int),
+			GFP_KERNEL);
+	if (dev->alt_max_pkt_size == NULL) {
+		em28xx_errdev("out of memory!\n");
+		em28xx_devused &= ~(1<<nr);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < dev->num_alt ; i++) {
+		u16 tmp = le16_to_cpu(uif->altsetting[i].endpoint[is_em2875?0:1].desc.
+							wMaxPacketSize);
+
+		dev->alt_max_pkt_size[i] =
+		    (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+		em28xx_info("Alternate setting %i, max size= %i\n", i,
+							dev->alt_max_pkt_size[i]);
+	}
+
+	if ((card[nr] >= 0) && (card[nr] < em28xx_bcount))
+		model = card[nr];
+
+	em28xx_generic_probe(&model, udev, dev);
+
+	/* allocate device struct */
+	retval = em28xx_init_dev(&dev, udev, nr, model);
+	if (retval)
+		return retval;
+
+	em28xx_info("Found %s\n", em28xx_boards[model].name);
+
+	/* save our data pointer in this interface device */
+	usb_set_intfdata(interface, dev);
+	return 0;
+}
+
+/*
+ * em28xx_usb_disconnect()
+ * called when the device gets diconencted
+ * video device will be unregistered on v4l2_close in case it is still open
+ */
+static void em28xx_usb_disconnect(struct usb_interface *interface)
+{
+	struct em28xx *dev = usb_get_intfdata(interface);
+	struct list_head *pos;
+	struct em28xx_ops *ops = 0;
+
+	/* might be racy */
+	dev->state |= DEV_DISCONNECTED;
+
+	usb_set_intfdata(interface, NULL);
+
+	/* TODO: kernel versions <=2.6.21 */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	if (delayed_work_pending(&dev->request_module_wk))
+		cancel_rearming_delayed_work(&dev->request_module_wk);
+#endif
+
+	if (!dev)
+		return;
+
+	down_write(&em28xx_disconnect);
+
+	em28xx_card_disconnect(dev);
+
+	mutex_lock(&em28xx_extension_devlist_lock);
+	if (!list_empty(&em28xx_extension_devlist)) {
+		list_for_each(pos, &em28xx_extension_devlist) {
+			ops = list_entry(pos, struct em28xx_ops, next);
+			ops->fini(dev);
+		}
+	}
+	mutex_unlock(&em28xx_extension_devlist_lock);
+
+	wake_up_interruptible_all(&dev->open);
+
+	if (dev->users) {
+		dev->state |= DEV_MISCONFIGURED;
+		em28xx_uninit_isoc(dev);
+		wake_up_interruptible(&dev->wait_frame);
+		wake_up_interruptible(&dev->video_wait_stream);
+		if (dev->dev_modes&EM28XX_VBI) {
+			wake_up_interruptible(&dev->wait_vbi_frame);
+			wake_up_interruptible(&dev->vbi_wait_stream);
+		}
+	} else {
+		em28xx_release_resources(dev);
+		kfree(dev->alt_max_pkt_size);
+		tuner_chip_detach(dev->tuner);
+		kfree(dev);
+	}
+
+	up_write(&em28xx_disconnect);
+}
+
+static struct usb_driver em28xx_usb_driver = {
+#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2, 6, 15)
+	.owner = THIS_MODULE,
+#endif
+	.name = "em28xx",
+	.probe = em28xx_usb_probe,
+	.disconnect = em28xx_usb_disconnect,
+	.id_table = em28xx_id_table,
+};
+
+int em28xx_register_extension(struct em28xx_ops *ops)
+{
+	struct em28xx *dev = NULL;
+	struct list_head *list;
+
+	mutex_lock(&em28xx_extension_devlist_lock);
+	list_add_tail(&ops->next, &em28xx_extension_devlist);
+	
+	/* TODO: defer the work put the module request into a linked list
+	   lock the device when the corresponding node got closed and load
+	   the module:
+	 
+	    problem here to consider is if module autoloading is not enabled
+	    to make it more safe it would be good to lock the driver and unlock
+	    it when the corresponding extension got unlocked
+
+	    better ideas are welcome of course
+	 
+	 */
+
+	list_for_each(list, &em28xx_devlist) {
+		dev = list_entry(list, struct em28xx, devlist);
+		if (dev &&
+		    (dev->state & DEV_DISCONNECTED) == 0 &&
+		    (dev->state & DEV_MISCONFIGURED) == 0 &&
+		    dev->radio_user == 0 &&
+		    dev->video_user == 0 &&
+		    dev->vbi_user == 0 && 
+		    dev->fe_user == 0 &&
+		    dev->dev_modes & ops->id) {
+			dev->em28xx_acquire(dev, EM28XX_LOCK, 1);
+
+			ops->init(dev);
+			printk("Em28xx: Initialized (%s) extension\n",ops->name);
+
+			dev->em28xx_acquire(dev, EM28XX_LOCK, 0);
+		}
+	}
+
+	mutex_unlock(&em28xx_extension_devlist_lock);
+	return 0;
+}
+
+void em28xx_unregister_extension(struct em28xx_ops *ops)
+{
+	struct em28xx *dev = NULL;
+	struct list_head *list;
+
+	mutex_lock(&em28xx_extension_devlist_lock);
+	list_for_each(list, &em28xx_devlist) {
+		dev = list_entry(list, struct em28xx, devlist);
+		if (dev)
+			ops->fini(dev);
+	}
+	printk(KERN_INFO"Em28xx: Removed (%s) extension\n", ops->name);
+	list_del(&ops->next);
+	mutex_unlock(&em28xx_extension_devlist_lock);
+}
+
+
+static int __init em28xx_module_init(void)
+{
+	int result;
+
+	printk(KERN_INFO DRIVER_NAME " v4l2 driver version %d.%d.%d loaded\n",
+	       (EM28XX_VERSION_CODE >> 16) & 0xff,
+	       (EM28XX_VERSION_CODE >> 8) & 0xff, EM28XX_VERSION_CODE & 0xff);
+#ifdef SNAPSHOT
+	printk(KERN_INFO DRIVER_NAME " snapshot date %04d-%02d-%02d\n",
+	       SNAPSHOT / 10000, (SNAPSHOT / 100) % 100, SNAPSHOT % 100);
+#endif
+
+	em28xx_wq = create_singlethread_workqueue("em28xx-worker");
+	if (em28xx_wq == NULL)
+		return -ENOMEM;
+
+	/* register this driver with the USB subsystem */
+	result = usb_register(&em28xx_usb_driver);
+	if (result)
+		em28xx_err(DRIVER_NAME
+			   " usb_register failed. Error number %d.\n", result);
+
+	return result;
+}
+
+static void __exit em28xx_module_exit(void)
+{
+	/* deregister this driver with the USB subsystem */
+	usb_deregister(&em28xx_usb_driver);
+	destroy_workqueue(em28xx_wq);
+}
+
+module_init(em28xx_module_init);
+module_exit(em28xx_module_exit);
+EXPORT_SYMBOL(em28xx_register_extension);
+EXPORT_SYMBOL(em28xx_unregister_extension);
+
diff --git a/drivers/media/video/empia/em28xx-webcam.c b/drivers/media/video/empia/em28xx-webcam.c
new file mode 100644
index 0000000..228c04e
--- /dev/null
+++ b/drivers/media/video/empia/em28xx-webcam.c
@@ -0,0 +1,375 @@
+/*
+   em28xx-vy.c - driver for em28xx based webcams
+
+   Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com>
+
+   I got a few specs but none of them were clear enough to get anything
+   work. Finally I end up with writing usbreplay and stepping through
+   the sniffed logfiles...
+
+   all this is quite device specific (em2820 specific) so I decided to
+   not write an I2C client no other device will behave like this one
+
+   Thanks to Walter Grom from MT (www.mt.com) for implementing
+   the reversed specs
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <media/v4l2-common.h>
+
+#include "em28xx.h"
+
+#define VY_FEATURE_REGISTER_1       0x02 /* Gain control en, gain control
+					    values */
+#define VY_FEATURE_REGISTER_2       0x04 /* WBM, Shutter, Mirror */
+#define VY_FEATURE_REGISTER_3       0x7c /* edge enhance */
+#define VY_FEATURE_REGISTER_4       0x7b /* edge enhance en */
+#define VY_FEATURE_REGISTER_5       0x7e /* edge enhance again? */
+#define VY_FEATURE_REGISTER_6       0x13 /* BLC ?? */
+#define VY_FEATURE_REGISTER_7	    0x1d /* gain control values */
+#define VY_FEATURE_REGISTER_8	    0x42 /* RGain */
+#define VY_FEATURE_REGISTER_9       0x43 /* BGain */
+
+#define V4L2_VY_WBM_MASK                 0x03
+#define V4L2_VY_SHUTTER_MASK             0x78
+#define V4L2_VY_MIRROR_MASK              0x80
+#define V4L2_VY_GAIN_CONTROL_EN_MASK     0x02
+#define V4L2_VY_GAIN_CONTROL_MASK        0xfe
+#define V4L2_VY_EDGE_ENHANCE_EN_MASK     0x03
+#define V4L2_VY_EDGE_ENHANCE_MASK        0x1f
+#define V4L2_VY_BLC_MASK                 0x00 /* FIXME */
+#define V4L2_VY_RGAIN_MASK               0xFF
+#define V4L2_VY_BGAIN_MASK               0xFF
+
+/* videology specific data */
+struct v4l2_queryctrl em28xx_vy_ctrl[] = {
+	{
+		.id = V4L2_VY_WBM,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "whiteblc",
+		.minimum = 0x0,
+		.maximum = 0x3,
+		.step = 0x1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_SHUTTER,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "shuttermode",
+		.minimum = 0x0,
+		.maximum = 0xf,
+		.step = 1,
+		.default_value = 0xf,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_MIRROR,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "mirror",
+		.minimum = 0x0,
+		.maximum = 0x1,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_GAIN_CONTROL_EN,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "gain_en",
+		.minimum = 0x0,
+		.maximum = 0x1,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_GAIN_CONTROL,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "gainctrl",
+		.minimum = 0x0,
+		.maximum = 0x7f,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_EDGE_ENHANCE_EN,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "edge_en",
+		.minimum = 0x0,
+		.maximum = 0x1,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_EDGE_ENHANCE,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "edgectrl",
+		.minimum = 0x0,
+		.maximum = 0x1f,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_BLC, /* wonder what this really is? */
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "blc",
+		.minimum = 0x0,
+		.maximum = 0x40,
+		.step = 1,
+		.default_value = 0x0,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_RGAIN,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "rgain",
+		.minimum = 0x0,
+		.maximum = 0xff,
+		.step = 1,
+		.default_value = 0x3b,
+		.flags = 0,
+	}, {
+		.id = V4L2_VY_BGAIN,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "bgain",
+		.minimum = 0x0,
+		.maximum = 0xff,
+		.step = 1,
+		.default_value = 0x74,
+		.flags = 0,
+	}
+};
+
+
+static int em28xx_vy_readmap(struct em28xx *dev, u8 reg, int mask)
+{
+	int value;
+
+	em28xx_write_regs_req(dev, 0x02, 0xa2, &reg, 1);
+	value = em28xx_read_reg_req(dev, 0x2, 0xa2);
+	value &= mask;
+	return value;
+}
+
+
+/* --------------------------------------------- */
+int em28xx_vy_write(struct em28xx *dev, u8 reg_index, u8 value, u8 mask)
+{
+	u8 msg[2];
+	u8 val;
+	u8 oldval;
+	u8 state = 1; /* 1 == OK */
+
+	/* write index of desired register to adress 0xa2 */
+	em28xx_write_regs_req(dev, 0x02, 0xa2, &reg_index, 1);
+	/* read old register value from eeprom */
+	oldval = em28xx_read_reg_req(dev, 0x2, 0xa2);
+	/* write again index of desired register to adress 0xa2 */
+	em28xx_write_regs_req(dev, 0x02, 0xa2, &reg_index, 1);
+
+	/* calculate new register value */
+	msg[0] = reg_index;
+	msg[1] = (oldval & ~mask)|(value&mask);
+	/* write new register value to eeprom */
+	dev->em28xx_write_regs_req(dev, 0x02, 0xa0, msg, 2);
+
+	/* instruct microcontroller to write values into the eeprom */
+	msg[0] = 0x11;
+	msg[1] = reg_index;
+	dev->em28xx_write_regs_req(dev, 0x02, 0x68, msg, 2);
+	dev->em28xx_write_regs_req(dev, 0x03, 0x68, "\x00", 1);
+	val = em28xx_read_reg_req(dev, 0x2, 0x68);
+	if (val == 0x05)
+		; /* everything's fine */
+	else
+		state = 0;
+
+	/* write new register value to eeprom again */
+	msg[0] = reg_index;
+	msg[1] = (oldval & ~mask)|(value&mask);
+	dev->em28xx_write_regs_req(dev, 0x02, 0xa2, msg, 2);
+
+	/* instruct microcontroller for verification */
+	msg[0] = 0x21;
+	msg[1] = reg_index;
+	em28xx_write_regs_req(dev, 0x02, 0x68, msg, 2);
+	em28xx_write_regs_req(dev, 0x03, 0x68, "\x00", 1);
+	val = em28xx_read_reg_req(dev, 0x2, 0x68);
+	if (val == 0x05)
+		; /* everything's fine */
+	else
+		state = 0;
+
+	if (state == 0)
+		return oldval;  /* error */
+	else
+		return value;
+}
+
+/* embedded QUERYCTRL */
+int em28xx_vy_qctrl(struct v4l2_queryctrl *qctrl)
+{
+	struct v4l2_queryctrl *qc = qctrl;
+	int i;
+	for (i = 0; i < ARRAY_SIZE(em28xx_vy_ctrl); i++) {
+		if (qc->id && qc->id == em28xx_vy_ctrl[i].id) {
+			memcpy(qc, &(em28xx_vy_ctrl[i]),
+					sizeof(*qc));
+			return 0;
+		}
+	}
+	return -EINVAL; /* no ctrl found here */
+}
+
+/* VIDIOC_G_CTRL */
+int em28xx_vy_gctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	switch (ctrl->id) {
+	case V4L2_VY_WBM:
+		ctrl->value = em28xx_vy_readmap(dev,
+				VY_FEATURE_REGISTER_2,
+				V4L2_VY_WBM_MASK);
+		break;
+	case V4L2_VY_SHUTTER:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_2,
+					V4L2_VY_SHUTTER_MASK)) >> 3;
+		break;
+	case V4L2_VY_MIRROR:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_2,
+					V4L2_VY_MIRROR_MASK)) >> 7;
+		break;
+	case V4L2_VY_GAIN_CONTROL_EN:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_1,
+					V4L2_VY_GAIN_CONTROL_EN_MASK)) >> 1;
+		break;
+	case V4L2_VY_GAIN_CONTROL:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_7,
+					V4L2_VY_GAIN_CONTROL_MASK)) >> 1;
+		break;
+	case V4L2_VY_EDGE_ENHANCE_EN:
+		ctrl->value = (em28xx_vy_readmap(dev,
+					VY_FEATURE_REGISTER_4,
+					V4L2_VY_EDGE_ENHANCE_EN_MASK) & 0x01) ?
+					0 : 1;
+		break;
+	case V4L2_VY_EDGE_ENHANCE:
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_3,
+					       V4L2_VY_EDGE_ENHANCE_MASK);
+		break;
+	case V4L2_VY_BLC:
+#if 0
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_6,
+				V4L2_VY_BLC_MASK);
+#endif
+		printk(KERN_INFO"em28xx-vy.c V4L2_VY_BLC: not implemented\n");
+		break;
+	case V4L2_VY_RGAIN:
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_8,
+					       V4L2_VY_RGAIN_MASK);
+		break;
+	case V4L2_VY_BGAIN:
+		ctrl->value = em28xx_vy_readmap(dev, VY_FEATURE_REGISTER_9,
+					       V4L2_VY_BGAIN_MASK);
+		break;
+	default:
+		printk(KERN_INFO"em28xx-vy.c: unknown command!\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/* VIDIOC_S_CTRL */
+int em28xx_vy_cctrl(struct em28xx *dev, struct v4l2_control *ctrl)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(em28xx_vy_ctrl); i++) {
+		if (ctrl->id == em28xx_vy_ctrl[i].id) {
+			if (em28xx_vy_ctrl[i].minimum >= ctrl->value &&
+					em28xx_vy_ctrl[i].maximum <=
+					ctrl->value) {
+				printk(KERN_INFO"em28xx-vy.c: value out of "
+						"range\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	switch (ctrl->id) {
+	case V4L2_VY_WBM:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2,
+				(u8)ctrl->value, V4L2_VY_WBM_MASK);
+		break;
+	case V4L2_VY_SHUTTER:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2,
+				(u8)(ctrl->value)<<3,
+				V4L2_VY_SHUTTER_MASK);
+		break;
+	case V4L2_VY_MIRROR:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_2,
+				(u8)(ctrl->value)<<7,
+				V4L2_VY_MIRROR_MASK);
+		break;
+	case V4L2_VY_GAIN_CONTROL_EN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_1,
+				(u8)(ctrl->value)<<1,
+				V4L2_VY_GAIN_CONTROL_EN_MASK);
+		break;
+	case V4L2_VY_GAIN_CONTROL:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_7,
+				(u8)(ctrl->value)<<1,
+				V4L2_VY_GAIN_CONTROL_MASK);
+		break;
+	case V4L2_VY_EDGE_ENHANCE_EN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_4,
+				(u8)(~(((ctrl->value)<<1) | (ctrl->value))),
+				V4L2_VY_EDGE_ENHANCE_EN_MASK);
+		break;
+	case V4L2_VY_EDGE_ENHANCE:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_3,
+				(u8)(ctrl->value),
+				V4L2_VY_EDGE_ENHANCE_MASK);
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_5,
+				(u8)(ctrl->value),
+				V4L2_VY_EDGE_ENHANCE_MASK);
+		break;
+	case V4L2_VY_RGAIN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_8,
+				(u8)(ctrl->value),
+				V4L2_VY_RGAIN_MASK);
+		break;
+	case V4L2_VY_BGAIN:
+		em28xx_vy_write(dev, (u8)VY_FEATURE_REGISTER_9,
+				(u8)(ctrl->value),
+				V4L2_VY_BGAIN_MASK);
+		break;
+	case V4L2_VY_BLC:
+		printk(KERN_INFO"em28xx-vy.c V4l2_VY_BLC not implemented\n");
+		break;
+	default:
+		printk(KERN_INFO"em28xx-vy.c: unknown command!\n");
+	}
+	return 0;
+}
+
+
diff --git a/drivers/media/video/empia/em28xx.h b/drivers/media/video/empia/em28xx.h
new file mode 100644
index 0000000..069dd5a
--- /dev/null
+++ b/drivers/media/video/empia/em28xx.h
@@ -0,0 +1,1153 @@
+/*
+   em28xx.h - driver for Empia EM2800/EM2820/2840/2880 USB video capture devices
+
+   Copyright (C) 2005 Markus Rechberger <mrechberger@gmail.com>
+		      Ludovico Cavedon <cavedon@sssup.it>
+		      Mauro Carvalho Chehab <mchehab@infradead.org>
+
+   Based on the em2800 driver from Sascha Sommer <saschasommer@freenet.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; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _EM28XX_H
+#define _EM28XX_H
+
+#include <linux/version.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/dvb/frontend.h>
+#include "dmxdev.h"
+#include "dvb_demux.h"
+#include "dvb_net.h"
+#include "dvb_frontend.h"
+#include <linux/soundcard.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
+#include <sound/driver.h>
+#endif
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "xc3028/xc3028_control.h"
+#include "cx25843/em28xx-cx25843.h"
+#include "media/tuner.h"
+#include "em28xx-aad.h"
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+#include <linux/mutex.h>
+#endif
+#include <media/ir-kbd-i2c.h>
+
+/* Boards supported by driver */
+
+#define EM2800_BOARD_GENERIC			  0
+#define EM2820_BOARD_GENERIC			  1
+#define EM2821_BOARD_GENERIC                      2
+#define EM2870_BOARD_GENERIC                      3
+#define EM2881_BOARD_GENERIC                      4
+#define EM2860_BOARD_GENERIC 			  5
+#define EM2861_BOARD_GENERIC 			  6
+#define EM2820_BOARD_TERRATEC_CINERGY_250	  7
+#define EM2820_BOARD_PINNACLE_USB_2		  8
+#define EM2820_BOARD_HAUPPAUGE_WINTV_USB_2        9
+#define EM2820_BOARD_MSI_VOX_USB_2                10
+#define EM2800_BOARD_TERRATEC_CINERGY_200         11
+#define EM2800_BOARD_LEADTEK_WINFAST_USBII        12
+#define EM2800_BOARD_KWORLD_USB2800               13
+#define EM2820_BOARD_PINNACLE_DVC_90		  14
+#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900	  15
+#define EM2880_BOARD_TERRATEC_HYBRID_XS		  16
+#define EM2880_BOARD_TERRATEC_HYBRID_XS_FR	  17
+#define EM2820_BOARD_KWORLD_PVRTV2800RF		  18
+#define EM2880_BOARD_TERRATEC_PRODIGY_XS	  19
+#define EM2820_BOARD_VIDEOLOGY_20K14XUSB	  20
+#define EM2821_BOARD_USBGEAR_VD204                21
+#define EM2870_BOARD_TERRATEC_XS                  22
+#define EM2870_BOARD_PINNACLE_PCTV_DVB            23
+#define EM2881_BOARD_DNT_DA2_HYBRID               24
+#define EM2881_BOARD_PINNACLE_HYBRID_PRO          25
+#define EM2820_BOARD_HERCULES_SMART_TV_USB2	  26
+#define EM2870_BOARD_COMPRO_VIDEOMATE             27
+#define EM2880_BOARD_KWORLD_DVB_310U              28
+#define EM2821_BOARD_PROLINK_PLAYTV_USB2          29
+#define EM2870_BOARD_TERRATEC_XS_MT2060           30
+#define EM2880_BOARD_MSI_DIGIVOX_AD               31
+#define EM2820_BOARD_DLINK_USB_TV                 32
+#define EM2820_BOARD_GADMEI_UTV310                33
+#define EM2870_BOARD_KWORLD_355U		  34
+#define EM2821_BOARD_SUPERCOMP_USB_2              35
+#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2	  36
+#define EM2860_BOARD_GADMEI_UTV330		  37
+#define EM2800_BOARD_VGEAR_POCKETTV               38
+#define EM2870_BOARD_KWORLD_350U		  39
+#define EM2882_BOARD_TERRATEC_HYBRID_XS		  40
+#define EM2820_BOARD_PINNACLE_DVC_100		  41
+#define EM2861_BOARD_YAKUMO_MOVIE_MIXER           43
+#define EM2750_BOARD_DLCW_130                     44
+#define EM2750_BOARD_GENERIC			  42
+#define EM2883_BOARD_GENERIC                      45
+#define EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950      46
+#define EM2883_BOARD_PINNACLE_PCTV_HD_PRO	  47
+#define EM2882_BOARD_PINNACLE_HYBRID_PRO          48
+#define EM2820_BOARD_HAUPPAUGE_WINTV_USB_2_R2     49
+#define EM2860_BOARD_NETGMBH_CAM		  50
+#define EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE 51
+#define EM2880_BOARD_MSI_DIGIVOX_AD_II            52
+#define EM2860_BOARD_TYPHOON_DVD_MAKER            53
+#define EM2820_BOARD_PINNACLE_USB_2_FM1216ME      54
+#define EM2751_BOARD_EMPIA_SAMPLE		  55
+#define EM2880_BOARD_KWORLD_DVB_305U              56
+#define EM2861_BOARD_KWORLD_PVRTV_300U 		  57
+#define EM2883_BOARD_KWORLD_HYBRID_A316 	  58
+#define EM2860_BOARD_TERRATEC_HYBRID_XS		  59
+#define EM2861_BOARD_PLEXTOR_PX_TV100U            60
+#define EM2883_BOARD_TERRATEC_HYBRID_XS_FM        61
+#define EM2870_BOARD_HAUPPAUGE_WINTV_USB_2_R2     62
+#define EM2883_BOARD_EMPIA_HYBRID_ATSC            63
+#define EM2863_BOARD_EMPIA_GENERIC                64
+#define EM2883_BOARD_KWORLD_HYBRID_F306		  65
+#define EM2888_BOARD_KWORLD_HYBRID_E329		  66
+#define EM2883_BOARD_KWORLD_HYBRID_E323		  67
+#define EM2883_BOARD_KWORLD_A340		  68
+#define EM2888_BOARD_LINCOLN_TV_FM		  69
+#define EM2888_BOARD_DVB_TC_HYBRID		  70
+#define EM2888_BOARD_EMPIA_HYBRID                 71
+#define EM2883_BOARD_ATI_TVWONDER600		  72
+#define EM2875_BOARD_SAMPLE_ISDBT                 73
+#define EM2879_BOARD_SAMPLE_DMB                   74
+#define EM2860_BOARD_KAIOMY_TVNPC_U2              75
+#define EM2861_BOARD_POLLIN_USB_R1                76
+#define EM2883_BOARD_EQUINUX_TUBESTICK_ATSC       77
+#define EM2882_BOARD_LEADTEK_PALMTOP_DTV_200H     78
+#define EM2820_BOARD_COMPRO_VIDEO_MATE            79
+
+#ifndef TUNER_XCEIVE_XC5000
+#define TUNER_XCEIVE_XC5000 10000
+#endif
+
+#ifndef TUNER_XCEIVE_XC3028
+#define TUNER_XCEIVE_XC3028 10001
+#endif
+
+#ifndef TUNER_MT2060
+#define TUNER_MT2060 10002
+#endif
+
+#ifndef TUNER_QT1010
+#define TUNER_QT1010 10003
+#endif
+
+#ifndef TUNER_PHILIPS_TDA18271
+#define TUNER_PHILIPS_TDA18271 10004
+#endif
+
+#ifndef TUNER_ADIMTV102
+#define TUNER_ADIMTV102 10005
+#endif
+
+
+#define UNSET -1
+
+/* maximum number of em28xx boards */
+/* TODO: we should distinct here between
+   * DVB-T
+   * analogue only
+   * hybrid devices
+   1 analogue tuner requires up to 170 mbit
+   2 analogue tuners will work in alt 3 mode, but the default
+     is alt 7 at the moment (it's possible to override
+     with the module parameter alt=3), though it should be
+     implemented properly.
+     2 devices take up to 340 mbit which should work.
+     (3 tuners would be 510 mbit which is too much for
+     one usb controller)
+
+   DVB-T tuner only take around 15 mbit, so we should be able
+   to support more than the limit below.
+
+   I think a controller based bandwidth table would do here
+   and guarantee that the initialized devices will work
+*/
+#define EM28XX_MAXBOARDS 3 /* FIXME: should be bigger */
+
+/* maximum number of frames that can be queued */
+#define EM28XX_NUM_FRAMES 5
+/* number of frames that get used for v4l2_read() */
+#define EM28XX_NUM_READ_FRAMES 2
+
+/* number of buffers for isoc transfers */
+#define EM28XX_NUM_BUFS 5
+#define EM2880_DVB_NUM_BUFS 5
+#define EM28XX_AUDIO_BUFS 3
+#define EM28XX_NUM_AUDIO_PACKETS 64
+#define EM28XX_AUDIO_MAX_PACKET_SIZE 196 /* static value */
+
+
+/* number of packets for each buffer
+   windows requests only 40 packets .. so we better do the same
+   this is what I found out for all alternate numbers there!
+ */
+#define EM28XX_NUM_PACKETS 64
+
+/* default alternate; 0 means choose the best */
+#define EM28XX_PINOUT 0
+
+#define EM28XX_INTERLACED_DEFAULT 1
+
+/*
+#define (use usbview if you want to get the other alternate number infos)
+#define
+#define alternate number 2
+#define 			Endpoint Address: 82
+			Direction: in
+			Attribute: 1
+			Type: Isoc
+			Max Packet Size: 1448
+			Interval: 125us
+
+  alternate number 7
+
+			Endpoint Address: 82
+			Direction: in
+			Attribute: 1
+			Type: Isoc
+			Max Packet Size: 3072
+			Interval: 125us
+*/
+
+/* time to wait when stopping the isoc transfer */
+#define EM28XX_URB_TIMEOUT \
+	msecs_to_jiffies(EM28XX_NUM_BUFS * EM28XX_NUM_PACKETS)
+
+/* time in msecs to wait for i2c writes to finish */
+#define EM2800_I2C_WRITE_TIMEOUT 20
+
+enum em28xx_fe_bandwidth {
+	EM28XX_BANDWIDTH_8_MHZ,
+	EM28XX_BANDWIDTH_7_MHZ,
+	EM28XX_BANDWIDTH_6_MHZ,
+	EM28XX_BANDWIDTH_AUTO
+};
+
+extern struct list_head em28xx_devlist;
+
+/* the various frame states */
+enum em28xx_frame_state {
+	F_UNUSED = 0,
+	F_QUEUED,
+	F_GRABBING,
+	F_DONE,
+	F_ERROR,
+};
+
+/* stream states */
+enum em28xx_stream_state {
+	STREAM_OFF,
+	STREAM_ON,
+	STREAM_INTERRUPT,
+};
+
+/* frames */
+struct em28xx_frame_t {
+	void *bufmem;
+	struct v4l2_buffer buf;
+	enum em28xx_frame_state state;
+	struct list_head frame;
+	unsigned long vma_use_count;
+	int top_field;
+	int fieldbytesused;
+};
+
+/* io methods */
+enum em28xx_io_method {
+	IO_NONE,
+	IO_READ,
+	IO_MMAP,
+};
+
+/* Colorspace Output */
+
+/* To be discussed */
+
+#ifndef V4L2_PIX_FMT_YUV211
+#define V4L2_PIX_FMT_YUV211	v4l2_fourcc('Y','2','1','1')
+#endif
+
+#ifndef V4L2_PIX_FMT_YUY1
+#define V4L2_PIX_FMT_YUY1	v4l2_fourcc('Y','U','Y','1')
+#endif
+
+#ifndef V4L2_PIX_FMT_Y21P
+#define V4L2_PIX_FMT_Y21P	v4l2_fourcc('Y','2','1','P')
+#endif
+
+struct em28xx_output_fmt {
+	struct v4l2_fmtdesc fmt;
+	u8 config;
+};
+
+/* inputs */
+
+#define MAX_EM28XX_INPUT 4
+#define MAX_EM28XX_TVNORMS 10
+#define MAX_EM28XX_DVBNORMS 5
+#define MAX_EM28XX_FMNORMS 2
+#define MAX_EM28XX_ATSCNORMS 2
+#define MAX_EM28XX_QAMNORMS 2
+
+enum enum28xx_itype {
+	EM28XX_VMUX_COMPOSITE1 = 1,
+	EM28XX_VMUX_COMPOSITE2,
+	EM28XX_VMUX_COMPOSITE3,
+	EM28XX_VMUX_COMPOSITE4,
+	EM28XX_VMUX_SVIDEO,
+	EM28XX_VMUX_TELEVISION,
+	EM28XX_VMUX_CABLE,
+	EM28XX_VMUX_DVB,
+	EM28XX_VMUX_DEBUG,
+	EM28XX_AMUX_RADIO,
+};
+
+enum enum28xx_mixchannel {
+	EM28XX_MIX_NOTOUCH = 0,
+	EM28XX_MIX_LINE_IN = 1,
+	EM28XX_MIX_VIDEO = 2,
+};
+
+struct em28xx_input {
+	enum enum28xx_itype type;
+	unsigned int vmux;
+	unsigned int amux;
+	enum enum28xx_mixchannel amix;
+};
+
+#define INPUT(nr) (&em28xx_boards[dev->model].input[nr])
+
+enum em28xx_decoder {
+	EM28XX_TVP5150,
+	EM28XX_SAA7113,
+	EM28XX_SAA7114,
+	EM28XX_CX25843,
+};
+
+struct em28xx;
+
+/* 0x0 - undef */
+enum empia_type {
+	EM2800 = 1,
+	EM2820,
+	EM2840,
+	EM2750,
+	EM2751,
+	EM2860,
+	EM2875,
+	EM2880,
+	EM2881,
+	EM2882,
+	EM2883,
+	EM2888,
+	EM2889
+};
+
+#define EM28XX_VIDEO    0x01
+#define EM28XX_DVBT     0x02
+#define EM28XX_DVBC     0x04
+#define EM28XX_ATSC     0x08
+#define EM28XX_DMB      0x10
+#define EM28XX_ISDB     0x20
+#define EM28XX_VBI      0x40
+#define EM28XX_REMOTE   0x80
+#define EM28XX_AUDIO    0x100 /* vendor specific audiodriver */
+#define EM28XX_AUDIO2   0x200 /* vendor specific audiodriver, different interface number */
+#define EM28XX_RADIO    0x400
+#define EM28XX_LOCK	0x800
+
+#define EM28XX_CAPTURE_STREAM_EN 1
+
+/* used by the audio driver */
+#define EM28XX_ENABLE_AUDIO 1
+#define EM28XX_DISABLE_AUDIO 2
+
+/* tvnorms */
+struct em28xx_tvnorm {
+	char *name;
+	XC3028_TV_MODE *tv_mode;
+	XC3028_CHANNEL_MAP *channelmap;
+	int index; /* xc5000 configuration */
+
+	/* digital tv */
+	enum em28xx_fe_bandwidth bandwidth;
+	/* analog tv */
+	v4l2_std_id id;
+	u32 vbi_sample_rate;
+	u16 vbi_samples_per_line;
+	u16 vbi_lines;
+	u16 vbi_offset;
+	u16 vbi_start_0;
+	u16 vbi_start_1;
+	u8 vbi_count_0;
+	u8 vbi_count_1;
+
+	u8 vbi_h_start;
+	u8 vbi_v_start;
+	u8 vbi_w;
+	u8 vbi_h;
+};
+
+/* gpio definitions */
+#define EM28XX_GPIO_EN 0x1
+
+#define EM28XX_GOP0    0x10
+#define EM28XX_GOP1    0x11
+#define EM28XX_GOP2    0x12
+#define EM28XX_GOP3    0x13
+
+#define EM28XX_GPIO0   0x0
+#define EM28XX_GPIO1   0x1
+#define EM28XX_GPIO2   0x2
+#define EM28XX_GPIO3   0x3
+#define EM28XX_GPIO4   0x4
+#define EM28XX_GPIO5   0x5
+#define EM28XX_GPIO6   0x6
+#define EM28XX_GPIO7   0x7
+
+/* helpers */
+#define _BIT_VAL(reg, val, reset) (reg  | 0x80 | (reset?1<<6:0) | (val<<5))
+
+/* internal gpio controls */
+#define EM28XX_TS1_ON         1
+#define EM28XX_ANALOG_ON      2
+#define EM28XX_LED1_ON        3
+#define EM28XX_XC3028_SECAM   4
+#define EM28XX_MODESWITCH     5
+#define EM28XX_DECODER_SLEEP  6
+#define EM28XX_LED2_ON        7
+#define EM28XX_RF             8
+#define EM28XX_DVB1_ON        9
+#define EM28XX_DVB2_ON        10
+#define EM28XX_TUNER1_ON      11
+#define EM28XX_TUNER2_ON      12
+#define EM28XX_DEMOD1_RESET   13
+#define EM28XX_TUNER1_RESET   14
+#define EM28XX_DECODER_RESET  15
+#define EM28XX_DEMOD2_RESET   16
+#define EM28XX_TUNER2_RESET   17
+#define EM28XX_I2C_BUS        18
+
+/* internal gpio control arguments */
+#define EM28XX_REG_ON       1 
+#define EM28XX_REG_OFF      2
+
+struct em28xx_gpio {
+	u16 d1_reset;
+	u16 d2_reset;
+	u16 t1_reset;
+	u16 t2_reset;
+	u16 dc_reset;
+	u16 a_on;
+	u16 l1_on;
+	u16 l2_on;
+	u16 xc3028_sec;
+	u16 m_switch;
+	u16 d_sleep;
+	u16 ts1_on;
+	u16 ts2_on;
+	u16 t1_on;
+	u16 t2_on;
+	u16 rf;
+	u16 dvbs_lnb;
+	u16 dvbs_v;
+};
+
+struct em28xx_board {
+	char *name;
+	int vchannels;
+	v4l2_std_id norm;
+	int tuner_type;
+	int tuner_addr;
+
+	/* i2c flags */
+	unsigned int em_type;
+	/* null terminated list of used i2c addresses */
+	/* used to autodetect some em2800 devices without eeprom */
+	const u8 *i2c_devs;
+	u8 tda9887_conf;
+
+	u8 has_tuner:1;
+	u8 has_inttuner:1;
+	u8 has_msp34xx:1;
+	u8 has_radio:1;
+	u8 powersaving:1;
+
+	u8 manual_gpio:1;
+	u8 ir_i2c:1; /* remote is i2c based */
+
+	struct em28xx_gpio gpio_regs;
+
+	u16 dev_modes;
+
+	enum em28xx_decoder decoder;
+
+	struct em28xx_tvnorm  tvnorms[MAX_EM28XX_TVNORMS];
+	struct em28xx_tvnorm  dvbnorms[MAX_EM28XX_DVBNORMS];
+	struct em28xx_tvnorm  fmnorms[MAX_EM28XX_FMNORMS];
+	struct em28xx_tvnorm  atscnorms[MAX_EM28XX_ATSCNORMS];
+	struct em28xx_tvnorm  qamnorms[MAX_EM28XX_QAMNORMS];
+
+	int (*ctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*gctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*qctrl)(struct v4l2_queryctrl *qctrl);
+
+	IR_KEYTAB_TYPE *ir_keytab;
+	int (*ir_getkey)(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+
+	struct em28xx_input       input[MAX_EM28XX_INPUT];
+};
+
+struct em28xx_eeprom {
+	u32 id;			/* 0x9567eb1a */
+	u16 vendor_ID;
+	u16 product_ID;
+
+	u16 chip_conf;
+
+	u16 board_conf;
+
+	u16 string1, string2, string3;
+
+	u8 string_idx_table;
+};
+
+/* device states */
+enum em28xx_dev_state {
+	DEV_INITIALIZED = 0x01,
+	DEV_DISCONNECTED = 0x02,
+	DEV_MISCONFIGURED = 0x04,
+};
+
+
+/* digital main device struct */
+
+enum mtype {
+	EM28XX_ZL10353,
+	EM28XX_MT352,
+	EM28XX_DRX3975D,
+	EM28XX_LGDT330X,
+};
+
+struct em2880_dvb {
+	char *name;
+	struct dvb_demux demux;
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+	struct mutex sem;
+#else
+	struct semaphore sem;
+#endif
+	int streaming;
+	enum mtype mod_type;
+	struct dvb_adapter adapter;
+	struct dvb_frontend        *frontend;
+	struct dvb_device *fedev;
+	struct dmxdev dmxdev;
+	struct em28xx *em28xx_dev; /* please get rid of it lateron */
+	struct dvb_net dvbnet;
+	struct usb_device *udev;	/* the usb device */
+	char *transfer_buffer[EM2880_DVB_NUM_BUFS];     /* transfer buffers for isoc transfer */
+	struct urb *urb[EM2880_DVB_NUM_BUFS];   /* urb for isoc transfers */
+	int (*demod_init)(struct dvb_frontend *fe);
+	int bw_index; /* bandwidth index */
+	int (*init_override)(struct dvb_frontend *fe);
+	u16 dtv_packetsize; 
+};
+
+struct em28xx_audio {
+	char name[50];
+	char *transfer_buffer[EM28XX_AUDIO_BUFS];
+	struct urb *urb[EM28XX_AUDIO_BUFS];
+	struct usb_device *udev;
+	unsigned int capture_transfer_done;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16)
+	snd_pcm_substream_t        *capture_pcm_substream;
+#else
+	struct snd_pcm_substream   *capture_pcm_substream;
+#endif
+
+	unsigned int hwptr_done_capture;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16)
+	snd_card_t                 *sndcard;
+#else
+	struct snd_card            *sndcard;
+#endif
+
+	int users;
+	unsigned int shutdown:1;
+	spinlock_t slock; /* for protecting the alsa buffer */
+	enum em28xx_stream_state capture_stream;
+	wait_queue_head_t audio_wait_stream, open;
+	u16 max_pck;
+	u8 alt_max;
+	/* states */
+	enum em28xx_dev_state state;
+	u8 alt;		/* alternate */
+	struct usb_interface usb_dev;
+};
+
+
+struct em2880_ir {
+	u8      old;
+	u8      sequence[4];
+	IR_KEYTAB_TYPE		*keymap;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	struct work_struct work;
+#else
+	struct delayed_work work;
+#endif
+
+	struct input_dev	*input;
+	struct em28xx		*dev;
+	int                     keypressed;
+	struct timer_list       timer;
+	u32                     keycode;
+
+	u32			oldval;
+	u32			oldtimeoutval;
+	u32			key;
+	u8			btn;
+
+	u8                      name[50];
+	u8			phys[50];
+	u8			released:1;
+	u8			state;
+	struct mutex state_lock;
+#define EM28XX_REMOTE_IDLE      0
+#define EM28XX_REMOTE_POLLING   1
+#define EM28XX_REMOTE_INTERRUPT 2
+	int (*get_key)(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+	wait_queue_head_t	remote_loop;
+};
+
+struct em28xx_fh {
+	struct em28xx *dev;
+	unsigned int reader:1;
+	int type;
+};
+
+
+/* main device struct */
+struct em28xx {
+	/* 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 */
+	unsigned int em_type;
+	u8 video_inputs;	/* number of video inputs */
+	struct list_head	devlist;
+	u8 has_tuner:1;
+	u8 has_inttuner:1;
+	u8 has_msp34xx:1;
+	u8 has_tda9887:1;
+	u8 has_vbi:1;
+	u8 device_mode:1; /* EM28XX_VIDEO | EM28XX_DVB  */
+	u16 dev_modes;
+	u8 manual_gpio:1;
+	u8 powersaving:1;
+
+	u8 modules_requested;
+#define EM28XX_MODULES_REQUESTED 0
+#define EM28XX_MODULES_PENDING   1
+	wait_queue_head_t mod_request;
+
+	void (*request_modules)(struct em28xx *dev);
+
+	u8 vbi_interlaced:1;
+
+	u8 vbi_frame_done:1;
+
+	u8 usb_interface;
+
+	u8 audio_user;
+	u8 video_user;
+	u8 radio_user;
+	u8 fe_user;
+	u8 vbi_user;
+	u8 mode_lock;
+
+
+	int mode;
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 17)
+	struct delayed_work request_module_wk;
+#endif
+
+	struct em28xx_board *board;
+	struct usb_interface *uif;
+	struct em28xx_output_fmt *outfmt;
+
+	u32 i2s_speed;		/* I2S speed for audio digital stream */
+
+	enum em28xx_decoder decoder;
+
+	int tuner_type;		/* type of the tuner */
+	u8 tuner_addr;		/* tuner address */
+	int tda9887_conf;
+
+	struct tuner_module *tuner;
+	/* i2c i/o */
+	struct i2c_adapter i2c_adap;
+	struct i2c_client i2c_client;
+
+	/* dvb */
+	struct em2880_dvb *dvb_dev;
+	struct em28xx_audio *adev;
+	struct em2880_ir *ir_em2880;
+
+	/* video for linux */
+	u8 users;		/* user count for exclusive use */
+
+	struct video_device *rdev;	/* video for linux device struct */
+	struct video_device *vdev;	/* video for linux device struct */
+	struct video_device *vbi_dev;
+
+	struct em28xx_tvnorm *tvnorm;	/* selected tv norm */
+	struct em28xx_tvnorm *dvbnorm;
+	struct em28xx_tvnorm *atscnorm;
+	struct em28xx_tvnorm *qamnorm;
+	struct em28xx_tvnorm *fmnorm;
+	unsigned int tv_std;
+
+	unsigned long rctl_freq;		/* selected radio frequency */
+	unsigned long vctl_freq;		/* selected video frequency */
+	unsigned long dctl_freq;		/* selected dvb-t/atsc frequency */
+	u8 ctl_input;	/* selected input */
+	u8 ctl_ainput;	/* selected audio input */
+	enum enum28xx_mixchannel ctl_amix;	/* slected audio mixer channel */
+	u8 mute;
+	int volume;
+	/* frame properties */
+	struct em28xx_frame_t frame[EM28XX_NUM_FRAMES];	/* list of frames */
+	struct em28xx_frame_t vbi_frame[EM28XX_NUM_FRAMES];	/* list of frames */
+	u8 num_frames;		/* number of frames currently in use */
+	u8 vbi_num_frames;
+	u32 frame_count;	/* total number of transfered frames */
+	u32 vbi_frame_count;	/* total number of transfered frames */
+	struct em28xx_frame_t *frame_current;	/* the frame that is being filled */
+	struct em28xx_frame_t *vbi_frame_current;	/* the frame that is being filled */
+	u16 width;		/* current frame width */
+	u16 height;		/* current frame height */
+	u32 frame_size;		/* current frame size */
+	u32 field_size;		/* current field size */
+	u16 vbi_frame_size;
+	u16 vbi_field_size;
+	u16 bytesperline;
+	u16 vbi_bytesperline;
+	u16 hscale;		/* horizontal scale factor (see datasheet) */
+	u16 vscale;		/* vertical scale factor (see datasheet) */
+	u8 interlaced:1;	/* 1=interlace fileds, 0=just top fileds */
+	int type;
+
+	u8 reader:1;
+	u8 vbi_reader:1;
+	u8 video_reader:1;
+
+	/* states */
+	enum em28xx_dev_state state;
+
+	enum em28xx_stream_state stream;
+	enum em28xx_stream_state video_stream;
+	enum em28xx_stream_state vbi_stream;
+
+	enum em28xx_io_method io;
+
+	enum em28xx_io_method video_io;
+	enum em28xx_io_method vbi_io;
+
+	/* locks */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 15)
+	struct mutex lock, fileop_lock, vbi_fileop_lock, input_lock;
+#else
+	struct semaphore lock, fileop_lock, vbi_fileop_lock, input_lock;
+#endif
+	spinlock_t queue_lock;
+	spinlock_t vbi_queue_lock;
+
+	struct list_head inqueue, outqueue, vbi_inqueue, vbi_outqueue;
+	wait_queue_head_t open, wait_frame, wait_vbi_frame, vbi_wait_stream;
+	wait_queue_head_t video_wait_stream;
+	u32 video_bytesread;
+	u16 vbi_bytesread;
+	u16 vbi_dropbytes;
+
+	unsigned char eedata[256];
+
+	/* usb transfer */
+	struct usb_device *udev;	/* the usb device */
+	u8 alt;		/* alternate */
+	u16 max_pkt_size;	/* max packet size of isoc transaction */
+	u8 num_alt;		/* Number of alternative settings */
+	unsigned int *alt_max_pkt_size;	/* array of wMaxPacketSize */
+	struct urb *urb[EM28XX_NUM_BUFS];	/* urb for isoc transfers */
+	char *transfer_buffer[EM28XX_NUM_BUFS];	/* transfer buffers for isoc transfer */
+
+	/* helper funcs that call usb_control_msg */
+
+	int (*em28xx_acquire)(struct em28xx *dev, int mode, int lock);
+	int (*em28xx_callback)(void *priv, int ptr, int mode);
+
+	int (*em28xx_write_regs) (struct em28xx *dev, u16 reg, char *buf,
+				  int len);
+	int (*em28xx_read_reg) (struct em28xx *dev, u16 reg);
+	int (*em28xx_read_reg_req_len) (struct em28xx *dev, u8 req, u16 reg,
+					char *buf, int len);
+	int (*em28xx_write_regs_req) (struct em28xx *dev, u8 req, u16 reg,
+				      char *buf, int len);
+	int (*em28xx_read_reg_req) (struct em28xx *dev, u8 req, u16 reg);
+	int (*em28xx_write_reg_bits)(struct em28xx *dev, u16 reg, u8 val,
+			u8 bitmask);
+
+	int (*em28xx_ctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*em28xx_gctrl)(struct em28xx *dev, struct v4l2_control *ctrl);
+	int (*em28xx_qctrl)(struct v4l2_queryctrl *qctrl);
+
+	int (*em28xx_gpio_control)(void *priv, unsigned int command, void *ptr);
+        int (*em28xx_aad_control)(void *priv, unsigned int command, void *ptr);
+
+        struct em28xx_aad_info *aad;
+
+};
+
+struct em28xx_ops {
+	struct list_head next;
+	char *name;
+	int id;
+	int (*init)(struct em28xx *);
+	int (*fini)(struct em28xx *);
+};
+
+/* Provided by em28xx-i2c.c */
+int em2800_i2c_check_for_device(struct em28xx *dev, unsigned char addr);
+void em28xx_i2c_call_clients(struct em28xx *dev, unsigned int cmd, void *arg);
+int em28xx_i2c_register(struct em28xx *dev);
+int em28xx_i2c_unregister(struct em28xx *dev);
+
+/* Provided by em28xx-input.c */
+
+void em28xx_set_ir(struct em28xx *dev, struct IR_i2c *ir);
+
+/* Provided by em28xx-core.c */
+
+u32 em28xx_request_buffers(struct em28xx *dev, u32 count, int type);
+void em28xx_queue_unusedframes(struct em28xx *dev, int type);
+void em28xx_release_buffers(struct em28xx *dev, int type);
+
+int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg,
+			    char *buf, int len);
+int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg);
+int em28xx_read_reg(struct em28xx *dev, u16 reg);
+int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf,
+			  int len);
+int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len);
+int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val,
+			  u8 bitmask);
+int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val);
+int em28xx_audio_analog_set(struct em28xx *dev);
+int em28xx_colorlevels_set_default(struct em28xx *dev);
+int em28xx_capture_start(struct em28xx *dev, int start);
+int em28xx_outfmt_set_yuv422(struct em28xx *dev);
+int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax, u8 ymin,
+			   u8 ymax);
+int em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart,
+			    u16 width, u16 height);
+int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v);
+int em28xx_resolution_set(struct em28xx *dev);
+int em28xx_init_isoc(struct em28xx *dev);
+void em28xx_uninit_isoc(struct em28xx *dev);
+int em28xx_set_alternate(struct em28xx *dev);
+int em28xx_register_extension(struct em28xx_ops *dev);
+void em28xx_unregister_extension(struct em28xx_ops *dev);
+int em2880_ir_detach(struct em28xx *dev);
+int em2880_ir_attach(struct em28xx *dev);
+int em2880_get_key_terratec(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em2880_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em2888_get_key_empia(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em2860_get_key_kaiomy(struct em28xx *dev, u32 *ir_key, u32 *keystatus);
+int em28xx_set_vbi(struct em28xx *dev, int enable);
+
+
+extern struct workqueue_struct *em28xx_wq;
+
+
+/* Provided by em28xx-cards.c */
+extern int em2800_variant_detect(struct usb_device* udev, int model);
+extern int em28xx_card_setup(struct em28xx *dev);
+extern void em28xx_card_disconnect(struct em28xx *dev);
+extern struct em28xx_board em28xx_boards[];
+extern struct usb_device_id em28xx_id_table[];
+extern const unsigned int em28xx_bcount;
+extern int em28xx_gpio_control(void *priv, unsigned int command, void *ptr);
+extern int em28xx_gpio_control_translate(void *priv, unsigned int command, void *ptr);
+extern int em28xx_qt1010_reset_control(void *priv, int command, void *ptr);
+
+/* Videology specific functions */
+
+/* white balance mode */
+#define V4L2_VY_WBM                 (V4L2_CID_PRIVATE_BASE+0)
+/* shutter speed */
+#define V4L2_VY_SHUTTER             (V4L2_CID_PRIVATE_BASE+1)
+/* mirror mode */
+#define V4L2_VY_MIRROR              (V4L2_CID_PRIVATE_BASE+2)
+/* gain control on/off */
+#define V4L2_VY_GAIN_CONTROL_EN     (V4L2_CID_PRIVATE_BASE+3)
+/* gain control values 0x00 - 0x7f */
+#define V4L2_VY_GAIN_CONTROL        (V4L2_CID_PRIVATE_BASE+4)
+/* enable edge enhance */
+#define V4L2_VY_EDGE_ENHANCE_EN     (V4L2_CID_PRIVATE_BASE+5)
+/* edge enhance values 0x00 - 0x1f */
+#define V4L2_VY_EDGE_ENHANCE        (V4L2_CID_PRIVATE_BASE+6)
+/* BLC (???) values 0x00 (off) - 0x40 */
+#define V4L2_VY_BLC                 (V4L2_CID_PRIVATE_BASE+7)
+/* RGain value    0x00-0xff */
+#define V4L2_VY_RGAIN               (V4L2_CID_PRIVATE_BASE+8)
+/* BGain value    0x00-0xff */
+#define V4L2_VY_BGAIN               (V4L2_CID_PRIVATE_BASE+9)
+
+
+int em28xx_vy_cctrl(struct em28xx *dev, struct v4l2_control *ctrl);
+int em28xx_vy_gctrl(struct em28xx *dev, struct v4l2_control *ctrl);
+int em28xx_vy_qctrl(struct v4l2_queryctrl *qctrl);
+
+/* em28xx registers */
+#define R06_I2C_CLK_REG         0x06
+#define R0A_CHIPID_REG		0x0a
+#define R0C_USBSUSP_REG		0x0c
+
+#define R0E_AUDIOSRC_REG	0x0e
+#define R0F_XCLK_REG  		0x0f
+
+#define R10_VINMODE_REG        	0x10
+#define R11_VINCTRL_REG        	0x11
+#define R12_VINENABLE_REG      	0x12
+
+#define R14_GAMMA_REG  		0x14
+#define R15_RGAIN_REG  		0x15
+#define R16_GGAIN_REG  		0x16
+#define R17_BGAIN_REG  		0x17
+#define R18_ROFFSET_REG        	0x18
+#define R19_GOFFSET_REG        	0x19
+#define R1A_BOFFSET_REG        	0x1a
+
+#define R1B_OFLOW_REG  		0x1b
+#define R1C_HSTART_REG 		0x1c
+#define R1D_VSTART_REG 		0x1d
+#define R1E_CWIDTH_REG 		0x1e
+#define R1F_CHEIGHT_REG        	0x1f
+
+#define R20_YGAIN_REG  		0x20
+#define R21_YOFFSET_REG        	0x21
+#define R22_UVGAIN_REG 		0x22
+#define R23_UOFFSET_REG        	0x23
+#define R24_VOFFSET_REG        	0x24
+#define R25_SHARPNESS_REG      	0x25
+
+#define R26_COMPR_REG  		0x26
+#define R27_OUTFMT_REG 		0x27
+
+#define R28_XMIN_REG   		0x28
+#define R29_XMAX_REG   		0x29
+#define R2A_YMIN_REG   		0x2a
+#define R2B_YMAX_REG   		0x2b
+
+#define R30_HSCALELOW_REG      	0x30
+#define R31_HSCALEHIGH_REG     	0x31
+#define R32_VSCALELOW_REG      	0x32
+#define R33_VSCALEHIGH_REG     	0x33
+
+#define R40_AC97LSB_REG        	0x40
+#define R41_AC97MSB_REG        	0x41
+#define R42_AC97ADDR_REG       	0x42
+#define R43_AC97BUSY_REG       	0x43
+
+/* em202 registers */
+#define R02_MASTER_AC97     	0x02
+#define R10_LINE_IN_AC97        0x10
+#define R14_VIDEO_AC97 		0x14
+
+/* em2800 registers */
+#define EM2800_AUDIOSRC_REG 	0x08
+
+/* register settings */
+#define EM2800_AUDIO_SRC_TUNER  0x0d
+#define EM2800_AUDIO_SRC_LINE   0x0c
+#define EM28XX_AUDIO_SRC_TUNER	0xc0
+#define EM28XX_AUDIO_SRC_LINE	0x80
+
+/* printk macros */
+
+#define em28xx_err(fmt, arg...) do {\
+	printk(KERN_ERR fmt , ##arg); } while (0)
+
+#define em28xx_errdev(fmt, arg...) do {\
+	printk(KERN_ERR "%s: "fmt,\
+			dev->name , ##arg); } while (0)
+
+#define em28xx_info(fmt, arg...) do {\
+	printk(KERN_INFO "%s: "fmt,\
+			dev->name , ##arg); } while (0)
+#define em28xx_warn(fmt, arg...) do {\
+	printk(KERN_WARNING "%s: "fmt,\
+			dev->name , ##arg); } while (0)
+
+void em28xx_config_i2c(struct em28xx *dev);
+int em28xx_config(struct em28xx *dev);
+
+static inline int em28xx_audio_source(struct em28xx *dev, int input)
+{
+	if (dev->em_type == EM2800) {
+		u8 tmp = EM2800_AUDIO_SRC_TUNER;
+		if (input == EM28XX_AUDIO_SRC_LINE)
+			tmp = EM2800_AUDIO_SRC_LINE;
+		em28xx_write_regs(dev, EM2800_AUDIOSRC_REG, &tmp, 1);
+	}
+	return em28xx_write_reg_bits(dev, R0E_AUDIOSRC_REG, input, 0xc0);
+}
+
+/* FIXME: return something sane here */
+
+#define EM28XX_MUTED   1
+#define EM28XX_UNMUTED 0
+
+static inline int em28xx_audio_usb_mute(struct em28xx *dev, int mute)
+{
+	switch (dev->em_type) {
+	case EM2750:
+		em28xx_write_regs(dev, R0F_XCLK_REG, "\x0a", 1);
+		break;
+	default:
+		em28xx_write_reg_bits(dev, R0F_XCLK_REG, mute ? 0x00 : 0x80, 0x80);
+	}
+	return 0;
+}
+
+static inline int em28xx_audio_analog_setup(struct em28xx *dev)
+{
+	/* unmute video mixer with default volume level */
+	return em28xx_write_ac97(dev, R14_VIDEO_AC97, "\x08\x08");
+}
+
+static inline int em28xx_audio_set_mixer(struct em28xx *dev, enum  enum28xx_mixchannel chan)
+{
+	int ret = 0;
+
+	if (chan == EM28XX_MIX_NOTOUCH)
+		return ret;
+	if ((ret = em28xx_write_ac97(dev, R10_LINE_IN_AC97,
+					chan == EM28XX_MIX_LINE_IN ?
+					"\x08\x08" : "\x08\x88")))
+		return ret;
+	if ((ret = em28xx_write_ac97(dev, R14_VIDEO_AC97, chan == EM28XX_MIX_VIDEO ?
+					"\x08\x08":"\x08\x88")))
+		return ret;
+	return ret;
+}
+
+static inline int em28xx_compression_disable(struct em28xx *dev)
+{
+	/* side effect of disabling scaler and mixer */
+	return em28xx_write_regs(dev, R26_COMPR_REG, "\x00", 1);
+}
+
+static inline int em28xx_contrast_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R20_YGAIN_REG) & 0x1f;
+}
+
+static inline int em28xx_brightness_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R21_YOFFSET_REG);
+}
+
+static inline int em28xx_saturation_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R22_UVGAIN_REG) & 0x1f;
+}
+
+static inline int em28xx_u_balance_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R23_UOFFSET_REG);
+}
+
+static inline int em28xx_v_balance_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R24_VOFFSET_REG);
+}
+
+static inline int em28xx_gamma_get(struct em28xx *dev)
+{
+	return em28xx_read_reg(dev, R14_GAMMA_REG) & 0x3f;
+}
+
+static inline int em28xx_contrast_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R20_YGAIN_REG, &tmp, 1);
+}
+
+static inline int em28xx_brightness_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R21_YOFFSET_REG, &tmp, 1);
+}
+
+static inline int em28xx_saturation_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R22_UVGAIN_REG, &tmp, 1);
+}
+
+static inline int em28xx_u_balance_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R23_UOFFSET_REG, &tmp, 1);
+}
+
+static inline int em28xx_v_balance_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R24_VOFFSET_REG, &tmp, 1);
+}
+
+static inline int em28xx_gamma_set(struct em28xx *dev, s32 val)
+{
+	u8 tmp = (u8) val;
+	return em28xx_write_regs(dev, R14_GAMMA_REG, &tmp, 1);
+}
+
+/*FIXME: especially em2800 devices have a too small framebuffer
+  for 720/YUY2 frames */
+static inline unsigned int norm_maxw(struct em28xx *dev)
+{
+	switch (dev->model) {
+		default: return 720;
+	}
+}
+
+static inline unsigned int norm_maxh(struct em28xx *dev)
+{
+	switch (dev->model) {
+		default: return (dev->tvnorm->id & V4L2_STD_625_50) ? 576 : 480;
+	}
+}
+
+#endif

             reply	other threads:[~2008-10-22 21:15 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-10-22 21:14 Markus Rechberger [this message]
2008-10-22 22:09 ` Greg KH
2008-10-22 22:24   ` Markus Rechberger
2008-10-22 22:26     ` Markus Rechberger
2008-10-22 22:27     ` Greg KH
2008-10-22 22:35       ` Markus Rechberger
2008-10-22 22:49         ` Greg KH
2008-10-23  8:53           ` el es
2008-11-26 19:12     ` Aidan Thornton
2008-11-27  4:25       ` Markus Rechberger
2008-11-27  9:33         ` Pekka Enberg
2008-11-28 15:48           ` Devin Heitmueller
2008-11-28 20:09           ` Greg KH
2008-11-30  6:23             ` Markus Rechberger
2008-10-23  9:29   ` Alan Cox
2008-10-23 11:10     ` Markus Rechberger
2008-11-01 13:59 ` Hans Verkuil
2008-11-02  4:27   ` Mauro Carvalho Chehab
     [not found]     ` <a2aa6e3a0811072150t535e802cge3375a7b88ee6287@mail.gmail.com>
2008-11-08 10:15       ` Mauro Carvalho Chehab
2008-11-08 10:22         ` Markus Rechberger
2008-11-08 10:37           ` Mauro Carvalho Chehab
2008-11-08 10:42             ` Markus Rechberger
2008-11-08 10:46               ` Markus Rechberger
2008-11-08 10:56               ` Mauro Carvalho Chehab
2008-11-08 11:02                 ` Markus Rechberger
2008-11-26 20:36   ` Aidan Thornton
     [not found] <d9def9db0810221359h5118b8d2pd6d2b3f4f95496ce@mail.gmail.com>
     [not found] ` <20081024153509.0f51d676@pedra.chehab.org>
2008-10-24 20:15   ` Markus Rechberger
2008-10-24 20:20     ` Markus Rechberger
2008-11-01 14:05 Hans Verkuil

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=d9def9db0810221414w5348acf3re31a033ea7179462@mail.gmail.com \
    --to=mrechberger@gmail.com \
    --cc=acano@fastmail.fm \
    --cc=akelmanson@gmail.com \
    --cc=em28xx@mcentral.de \
    --cc=fn@kernelport.de \
    --cc=freebeer.bouwsma@gmail.com \
    --cc=jelledejong@powercraft.nl \
    --cc=john.stowers.lists@gmail.com \
    --cc=kreiser@informatik.hu-berlin.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lukas.kuna@evkanet.net \
    --cc=s.berberig@arcor.de \
    --cc=stefan.vonolfen@gmail.com \
    --cc=thomas.giesecke@ibgmbh-naumburg.de \
    --cc=vwool@ru.mvista.com \
    --cc=zhen78@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®