v4.19.13 snapshot.
diff --git a/drivers/media/usb/pvrusb2/Kconfig b/drivers/media/usb/pvrusb2/Kconfig
new file mode 100644
index 0000000..1ad913f
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/Kconfig
@@ -0,0 +1,64 @@
+config VIDEO_PVRUSB2
+	tristate "Hauppauge WinTV-PVR USB2 support"
+	depends on VIDEO_V4L2 && I2C
+	select VIDEO_TUNER
+	select VIDEO_TVEEPROM
+	select VIDEO_CX2341X
+	select VIDEO_SAA711X
+	select VIDEO_CX25840
+	select VIDEO_MSP3400
+	select VIDEO_WM8775
+	select VIDEO_CS53L32A
+	---help---
+	  This is a video4linux driver for Conexant 23416 based
+	  usb2 personal video recorder devices.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pvrusb2
+
+config VIDEO_PVRUSB2_SYSFS
+	bool "pvrusb2 sysfs support"
+	default y
+	depends on VIDEO_PVRUSB2 && SYSFS
+	---help---
+	  This option enables the operation of a sysfs based
+	  interface for query and control of the pvrusb2 driver.
+
+	  This is not generally needed for v4l applications,
+	  although certain applications are optimized to take
+	  advantage of this feature.
+
+	  If you are in doubt, say Y.
+
+	  Note: This feature is experimental and subject to change.
+
+config VIDEO_PVRUSB2_DVB
+	bool "pvrusb2 ATSC/DVB support"
+	default y
+	depends on VIDEO_PVRUSB2 && DVB_CORE
+	select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT
+	---help---
+	  This option enables a DVB interface for the pvrusb2 driver.
+	  If your device does not support digital television, this
+	  feature will have no affect on the driver's operation.
+
+	  If you are in doubt, say Y.
+
+config VIDEO_PVRUSB2_DEBUGIFC
+	bool "pvrusb2 debug interface"
+	depends on VIDEO_PVRUSB2_SYSFS
+	---help---
+	  This option enables the inclusion of a debug interface
+	  in the pvrusb2 driver, hosted through sysfs.
+
+	  You do not need to select this option unless you plan
+	  on debugging the driver or performing a manual firmware
+	  extraction.
+
+	  If you are in doubt, say N.
diff --git a/drivers/media/usb/pvrusb2/Makefile b/drivers/media/usb/pvrusb2/Makefile
new file mode 100644
index 0000000..9facf68
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/Makefile
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-pvrusb2-sysfs-$(CONFIG_VIDEO_PVRUSB2_SYSFS) := pvrusb2-sysfs.o
+obj-pvrusb2-debugifc-$(CONFIG_VIDEO_PVRUSB2_DEBUGIFC) := pvrusb2-debugifc.o
+obj-pvrusb2-dvb-$(CONFIG_VIDEO_PVRUSB2_DVB) := pvrusb2-dvb.o
+
+pvrusb2-objs	:= pvrusb2-i2c-core.o \
+		   pvrusb2-audio.o \
+		   pvrusb2-encoder.o pvrusb2-video-v4l.o \
+		   pvrusb2-eeprom.o \
+		   pvrusb2-main.o pvrusb2-hdw.o pvrusb2-v4l2.o \
+		   pvrusb2-ctrl.o pvrusb2-std.o pvrusb2-devattr.o \
+		   pvrusb2-context.o pvrusb2-io.o pvrusb2-ioread.o \
+		   pvrusb2-cx2584x-v4l.o pvrusb2-wm8775.o \
+		   pvrusb2-cs53l32a.o \
+		   $(obj-pvrusb2-dvb-y) \
+		   $(obj-pvrusb2-sysfs-y) $(obj-pvrusb2-debugifc-y)
+
+obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2.o
+
+ccflags-y += -Idrivers/media/tuners
+ccflags-y += -Idrivers/media/dvb-frontends
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-audio.c b/drivers/media/usb/pvrusb2/pvrusb2-audio.c
new file mode 100644
index 0000000..356afa2
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-audio.c
@@ -0,0 +1,80 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#include "pvrusb2-audio.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/drv-intf/msp3400.h>
+#include <media/v4l2-common.h>
+
+
+struct routing_scheme {
+	const int *def;
+	unsigned int cnt;
+};
+
+static const int routing_scheme0[] = {
+	[PVR2_CVAL_INPUT_TV]        = MSP_INPUT_DEFAULT,
+	[PVR2_CVAL_INPUT_RADIO]     = MSP_INPUT(MSP_IN_SCART2,
+						MSP_IN_TUNER1,
+						MSP_DSP_IN_SCART,
+						MSP_DSP_IN_SCART),
+	[PVR2_CVAL_INPUT_COMPOSITE] = MSP_INPUT(MSP_IN_SCART1,
+						MSP_IN_TUNER1,
+						MSP_DSP_IN_SCART,
+						MSP_DSP_IN_SCART),
+	[PVR2_CVAL_INPUT_SVIDEO]    = MSP_INPUT(MSP_IN_SCART1,
+						MSP_IN_TUNER1,
+						MSP_DSP_IN_SCART,
+						MSP_DSP_IN_SCART),
+};
+
+static const struct routing_scheme routing_def0 = {
+	.def = routing_scheme0,
+	.cnt = ARRAY_SIZE(routing_scheme0),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0,
+};
+
+void pvr2_msp3400_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+		u32 input;
+
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev msp3400 v4l2 set_stereo");
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+
+		if ((sp != NULL) &&
+		    (hdw->input_val >= 0) &&
+		    (hdw->input_val < sp->cnt)) {
+			input = sp->def[hdw->input_val];
+		} else {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev msp3400 set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		sd->ops->audio->s_routing(sd, input,
+			MSP_OUTPUT(MSP_SC_IN_DSP_SCART1), 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-audio.h b/drivers/media/usb/pvrusb2/pvrusb2-audio.h
new file mode 100644
index 0000000..4f38984
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-audio.h
@@ -0,0 +1,23 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_AUDIO_H
+#define __PVRUSB2_AUDIO_H
+
+#include "pvrusb2-hdw-internal.h"
+void pvr2_msp3400_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *);
+#endif /* __PVRUSB2_AUDIO_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-context.c b/drivers/media/usb/pvrusb2/pvrusb2-context.c
new file mode 100644
index 0000000..d9e8481
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-context.c
@@ -0,0 +1,417 @@
+/*
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include "pvrusb2-context.h"
+#include "pvrusb2-io.h"
+#include "pvrusb2-ioread.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+static struct pvr2_context *pvr2_context_exist_first;
+static struct pvr2_context *pvr2_context_exist_last;
+static struct pvr2_context *pvr2_context_notify_first;
+static struct pvr2_context *pvr2_context_notify_last;
+static DEFINE_MUTEX(pvr2_context_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_sync_data);
+static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_cleanup_data);
+static int pvr2_context_cleanup_flag;
+static int pvr2_context_cleaned_flag;
+static struct task_struct *pvr2_context_thread_ptr;
+
+
+static void pvr2_context_set_notify(struct pvr2_context *mp, int fl)
+{
+	int signal_flag = 0;
+	mutex_lock(&pvr2_context_mutex);
+	if (fl) {
+		if (!mp->notify_flag) {
+			signal_flag = (pvr2_context_notify_first == NULL);
+			mp->notify_prev = pvr2_context_notify_last;
+			mp->notify_next = NULL;
+			pvr2_context_notify_last = mp;
+			if (mp->notify_prev) {
+				mp->notify_prev->notify_next = mp;
+			} else {
+				pvr2_context_notify_first = mp;
+			}
+			mp->notify_flag = !0;
+		}
+	} else {
+		if (mp->notify_flag) {
+			mp->notify_flag = 0;
+			if (mp->notify_next) {
+				mp->notify_next->notify_prev = mp->notify_prev;
+			} else {
+				pvr2_context_notify_last = mp->notify_prev;
+			}
+			if (mp->notify_prev) {
+				mp->notify_prev->notify_next = mp->notify_next;
+			} else {
+				pvr2_context_notify_first = mp->notify_next;
+			}
+		}
+	}
+	mutex_unlock(&pvr2_context_mutex);
+	if (signal_flag) wake_up(&pvr2_context_sync_data);
+}
+
+
+static void pvr2_context_destroy(struct pvr2_context *mp)
+{
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (destroy)",mp);
+	pvr2_hdw_destroy(mp->hdw);
+	pvr2_context_set_notify(mp, 0);
+	mutex_lock(&pvr2_context_mutex);
+	if (mp->exist_next) {
+		mp->exist_next->exist_prev = mp->exist_prev;
+	} else {
+		pvr2_context_exist_last = mp->exist_prev;
+	}
+	if (mp->exist_prev) {
+		mp->exist_prev->exist_next = mp->exist_next;
+	} else {
+		pvr2_context_exist_first = mp->exist_next;
+	}
+	if (!pvr2_context_exist_first) {
+		/* Trigger wakeup on control thread in case it is waiting
+		   for an exit condition. */
+		wake_up(&pvr2_context_sync_data);
+	}
+	mutex_unlock(&pvr2_context_mutex);
+	kfree(mp);
+}
+
+
+static void pvr2_context_notify(struct pvr2_context *mp)
+{
+	pvr2_context_set_notify(mp,!0);
+}
+
+
+static void pvr2_context_check(struct pvr2_context *mp)
+{
+	struct pvr2_channel *ch1, *ch2;
+	pvr2_trace(PVR2_TRACE_CTXT,
+		   "pvr2_context %p (notify)", mp);
+	if (!mp->initialized_flag && !mp->disconnect_flag) {
+		mp->initialized_flag = !0;
+		pvr2_trace(PVR2_TRACE_CTXT,
+			   "pvr2_context %p (initialize)", mp);
+		/* Finish hardware initialization */
+		if (pvr2_hdw_initialize(mp->hdw,
+					(void (*)(void *))pvr2_context_notify,
+					mp)) {
+			mp->video_stream.stream =
+				pvr2_hdw_get_video_stream(mp->hdw);
+			/* Trigger interface initialization.  By doing this
+			   here initialization runs in our own safe and
+			   cozy thread context. */
+			if (mp->setup_func) mp->setup_func(mp);
+		} else {
+			pvr2_trace(PVR2_TRACE_CTXT,
+				   "pvr2_context %p (thread skipping setup)",
+				   mp);
+			/* Even though initialization did not succeed,
+			   we're still going to continue anyway.  We need
+			   to do this in order to await the expected
+			   disconnect (which we will detect in the normal
+			   course of operation). */
+		}
+	}
+
+	for (ch1 = mp->mc_first; ch1; ch1 = ch2) {
+		ch2 = ch1->mc_next;
+		if (ch1->check_func) ch1->check_func(ch1);
+	}
+
+	if (mp->disconnect_flag && !mp->mc_first) {
+		/* Go away... */
+		pvr2_context_destroy(mp);
+		return;
+	}
+}
+
+
+static int pvr2_context_shutok(void)
+{
+	return pvr2_context_cleanup_flag && (pvr2_context_exist_first == NULL);
+}
+
+
+static int pvr2_context_thread_func(void *foo)
+{
+	struct pvr2_context *mp;
+
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread start");
+
+	do {
+		while ((mp = pvr2_context_notify_first) != NULL) {
+			pvr2_context_set_notify(mp, 0);
+			pvr2_context_check(mp);
+		}
+		wait_event_interruptible(
+			pvr2_context_sync_data,
+			((pvr2_context_notify_first != NULL) ||
+			 pvr2_context_shutok()));
+	} while (!pvr2_context_shutok());
+
+	pvr2_context_cleaned_flag = !0;
+	wake_up(&pvr2_context_cleanup_data);
+
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread cleaned up");
+
+	wait_event_interruptible(
+		pvr2_context_sync_data,
+		kthread_should_stop());
+
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread end");
+
+	return 0;
+}
+
+
+int pvr2_context_global_init(void)
+{
+	pvr2_context_thread_ptr = kthread_run(pvr2_context_thread_func,
+					      NULL,
+					      "pvrusb2-context");
+	return IS_ERR(pvr2_context_thread_ptr) ? -ENOMEM : 0;
+}
+
+
+void pvr2_context_global_done(void)
+{
+	pvr2_context_cleanup_flag = !0;
+	wake_up(&pvr2_context_sync_data);
+	wait_event_interruptible(
+		pvr2_context_cleanup_data,
+		pvr2_context_cleaned_flag);
+	kthread_stop(pvr2_context_thread_ptr);
+}
+
+
+struct pvr2_context *pvr2_context_create(
+	struct usb_interface *intf,
+	const struct usb_device_id *devid,
+	void (*setup_func)(struct pvr2_context *))
+{
+	struct pvr2_context *mp = NULL;
+	mp = kzalloc(sizeof(*mp),GFP_KERNEL);
+	if (!mp) goto done;
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (create)",mp);
+	mp->setup_func = setup_func;
+	mutex_init(&mp->mutex);
+	mutex_lock(&pvr2_context_mutex);
+	mp->exist_prev = pvr2_context_exist_last;
+	mp->exist_next = NULL;
+	pvr2_context_exist_last = mp;
+	if (mp->exist_prev) {
+		mp->exist_prev->exist_next = mp;
+	} else {
+		pvr2_context_exist_first = mp;
+	}
+	mutex_unlock(&pvr2_context_mutex);
+	mp->hdw = pvr2_hdw_create(intf,devid);
+	if (!mp->hdw) {
+		pvr2_context_destroy(mp);
+		mp = NULL;
+		goto done;
+	}
+	pvr2_context_set_notify(mp, !0);
+ done:
+	return mp;
+}
+
+
+static void pvr2_context_reset_input_limits(struct pvr2_context *mp)
+{
+	unsigned int tmsk,mmsk;
+	struct pvr2_channel *cp;
+	struct pvr2_hdw *hdw = mp->hdw;
+	mmsk = pvr2_hdw_get_input_available(hdw);
+	tmsk = mmsk;
+	for (cp = mp->mc_first; cp; cp = cp->mc_next) {
+		if (!cp->input_mask) continue;
+		tmsk &= cp->input_mask;
+	}
+	pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk);
+	pvr2_hdw_commit_ctl(hdw);
+}
+
+
+static void pvr2_context_enter(struct pvr2_context *mp)
+{
+	mutex_lock(&mp->mutex);
+}
+
+
+static void pvr2_context_exit(struct pvr2_context *mp)
+{
+	int destroy_flag = 0;
+	if (!(mp->mc_first || !mp->disconnect_flag)) {
+		destroy_flag = !0;
+	}
+	mutex_unlock(&mp->mutex);
+	if (destroy_flag) pvr2_context_notify(mp);
+}
+
+
+void pvr2_context_disconnect(struct pvr2_context *mp)
+{
+	pvr2_hdw_disconnect(mp->hdw);
+	mp->disconnect_flag = !0;
+	pvr2_context_notify(mp);
+}
+
+
+void pvr2_channel_init(struct pvr2_channel *cp,struct pvr2_context *mp)
+{
+	pvr2_context_enter(mp);
+	cp->hdw = mp->hdw;
+	cp->mc_head = mp;
+	cp->mc_next = NULL;
+	cp->mc_prev = mp->mc_last;
+	if (mp->mc_last) {
+		mp->mc_last->mc_next = cp;
+	} else {
+		mp->mc_first = cp;
+	}
+	mp->mc_last = cp;
+	pvr2_context_exit(mp);
+}
+
+
+static void pvr2_channel_disclaim_stream(struct pvr2_channel *cp)
+{
+	if (!cp->stream) return;
+	pvr2_stream_kill(cp->stream->stream);
+	cp->stream->user = NULL;
+	cp->stream = NULL;
+}
+
+
+void pvr2_channel_done(struct pvr2_channel *cp)
+{
+	struct pvr2_context *mp = cp->mc_head;
+	pvr2_context_enter(mp);
+	cp->input_mask = 0;
+	pvr2_channel_disclaim_stream(cp);
+	pvr2_context_reset_input_limits(mp);
+	if (cp->mc_next) {
+		cp->mc_next->mc_prev = cp->mc_prev;
+	} else {
+		mp->mc_last = cp->mc_prev;
+	}
+	if (cp->mc_prev) {
+		cp->mc_prev->mc_next = cp->mc_next;
+	} else {
+		mp->mc_first = cp->mc_next;
+	}
+	cp->hdw = NULL;
+	pvr2_context_exit(mp);
+}
+
+
+int pvr2_channel_limit_inputs(struct pvr2_channel *cp,unsigned int cmsk)
+{
+	unsigned int tmsk,mmsk;
+	int ret = 0;
+	struct pvr2_channel *p2;
+	struct pvr2_hdw *hdw = cp->hdw;
+
+	mmsk = pvr2_hdw_get_input_available(hdw);
+	cmsk &= mmsk;
+	if (cmsk == cp->input_mask) {
+		/* No change; nothing to do */
+		return 0;
+	}
+
+	pvr2_context_enter(cp->mc_head);
+	do {
+		if (!cmsk) {
+			cp->input_mask = 0;
+			pvr2_context_reset_input_limits(cp->mc_head);
+			break;
+		}
+		tmsk = mmsk;
+		for (p2 = cp->mc_head->mc_first; p2; p2 = p2->mc_next) {
+			if (p2 == cp) continue;
+			if (!p2->input_mask) continue;
+			tmsk &= p2->input_mask;
+		}
+		if (!(tmsk & cmsk)) {
+			ret = -EPERM;
+			break;
+		}
+		tmsk &= cmsk;
+		if ((ret = pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk)) != 0) {
+			/* Internal failure changing allowed list; probably
+			   should not happen, but react if it does. */
+			break;
+		}
+		cp->input_mask = cmsk;
+		pvr2_hdw_commit_ctl(hdw);
+	} while (0);
+	pvr2_context_exit(cp->mc_head);
+	return ret;
+}
+
+
+unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *cp)
+{
+	return cp->input_mask;
+}
+
+
+int pvr2_channel_claim_stream(struct pvr2_channel *cp,
+			      struct pvr2_context_stream *sp)
+{
+	int code = 0;
+	pvr2_context_enter(cp->mc_head); do {
+		if (sp == cp->stream) break;
+		if (sp && sp->user) {
+			code = -EBUSY;
+			break;
+		}
+		pvr2_channel_disclaim_stream(cp);
+		if (!sp) break;
+		sp->user = cp;
+		cp->stream = sp;
+	} while (0);
+	pvr2_context_exit(cp->mc_head);
+	return code;
+}
+
+
+// This is the marker for the real beginning of a legitimate mpeg2 stream.
+static char stream_sync_key[] = {
+	0x00, 0x00, 0x01, 0xba,
+};
+
+struct pvr2_ioread *pvr2_channel_create_mpeg_stream(
+	struct pvr2_context_stream *sp)
+{
+	struct pvr2_ioread *cp;
+	cp = pvr2_ioread_create();
+	if (!cp) return NULL;
+	pvr2_ioread_setup(cp,sp->stream);
+	pvr2_ioread_set_sync_key(cp,stream_sync_key,sizeof(stream_sync_key));
+	return cp;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-context.h b/drivers/media/usb/pvrusb2/pvrusb2-context.h
new file mode 100644
index 0000000..13e00c5
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-context.h
@@ -0,0 +1,81 @@
+/*
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_CONTEXT_H
+#define __PVRUSB2_CONTEXT_H
+
+#include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+struct pvr2_hdw;     /* hardware interface - defined elsewhere */
+struct pvr2_stream;  /* stream interface - defined elsewhere */
+
+struct pvr2_context;        /* All central state */
+struct pvr2_channel;        /* One I/O pathway to a user */
+struct pvr2_context_stream; /* Wrapper for a stream */
+struct pvr2_ioread;         /* Low level stream structure */
+
+struct pvr2_context_stream {
+	struct pvr2_channel *user;
+	struct pvr2_stream *stream;
+};
+
+struct pvr2_context {
+	struct pvr2_channel *mc_first;
+	struct pvr2_channel *mc_last;
+	struct pvr2_context *exist_next;
+	struct pvr2_context *exist_prev;
+	struct pvr2_context *notify_next;
+	struct pvr2_context *notify_prev;
+	struct pvr2_hdw *hdw;
+	struct pvr2_context_stream video_stream;
+	struct mutex mutex;
+	int notify_flag;
+	int initialized_flag;
+	int disconnect_flag;
+
+	/* Called after pvr2_context initialization is complete */
+	void (*setup_func)(struct pvr2_context *);
+
+};
+
+struct pvr2_channel {
+	struct pvr2_context *mc_head;
+	struct pvr2_channel *mc_next;
+	struct pvr2_channel *mc_prev;
+	struct pvr2_context_stream *stream;
+	struct pvr2_hdw *hdw;
+	unsigned int input_mask;
+	void (*check_func)(struct pvr2_channel *);
+};
+
+struct pvr2_context *pvr2_context_create(struct usb_interface *intf,
+					 const struct usb_device_id *devid,
+					 void (*setup_func)(struct pvr2_context *));
+void pvr2_context_disconnect(struct pvr2_context *);
+
+void pvr2_channel_init(struct pvr2_channel *,struct pvr2_context *);
+void pvr2_channel_done(struct pvr2_channel *);
+int pvr2_channel_limit_inputs(struct pvr2_channel *,unsigned int);
+unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *);
+int pvr2_channel_claim_stream(struct pvr2_channel *,
+			      struct pvr2_context_stream *);
+struct pvr2_ioread *pvr2_channel_create_mpeg_stream(
+	struct pvr2_context_stream *);
+
+int pvr2_context_global_init(void);
+void pvr2_context_global_done(void);
+
+#endif /* __PVRUSB2_CONTEXT_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c
new file mode 100644
index 0000000..679f3ff
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c
@@ -0,0 +1,78 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   v4l-dvb cs53l32a module.
+
+*/
+
+#include "pvrusb2-cs53l32a.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+
+struct routing_scheme {
+	const int *def;
+	unsigned int cnt;
+};
+
+
+static const int routing_scheme1[] = {
+	[PVR2_CVAL_INPUT_TV] = 2,  /* 1 or 2 seems to work here */
+	[PVR2_CVAL_INPUT_RADIO] = 2,
+	[PVR2_CVAL_INPUT_COMPOSITE] = 0,
+	[PVR2_CVAL_INPUT_SVIDEO] =  0,
+};
+
+static const struct routing_scheme routing_def1 = {
+	.def = routing_scheme1,
+	.cnt = ARRAY_SIZE(routing_scheme1),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_ONAIR] = &routing_def1,
+};
+
+
+void pvr2_cs53l32a_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+		u32 input;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_input(%d)",
+			   hdw->input_val);
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+		if ((sp == NULL) ||
+		    (hdw->input_val < 0) ||
+		    (hdw->input_val >= sp->cnt)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev v4l2 set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		input = sp->def[hdw->input_val];
+		sd->ops->audio->s_routing(sd, input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h
new file mode 100644
index 0000000..90dfb8b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h
@@ -0,0 +1,34 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_CS53L32A_H
+#define __PVRUSB2_CS53L32A_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles device video processing.  This interface is
+   used internally by the driver; higher level code should only
+   interact through the interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+#include "pvrusb2-hdw-internal.h"
+void pvr2_cs53l32a_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *);
+
+#endif /* __PVRUSB2_AUDIO_CS53L32A_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c
new file mode 100644
index 0000000..5f4ba84
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c
@@ -0,0 +1,594 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include "pvrusb2-ctrl.h"
+#include "pvrusb2-hdw-internal.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+
+
+static int pvr2_ctrl_range_check(struct pvr2_ctrl *cptr,int val)
+{
+	if (cptr->info->check_value) {
+		if (!cptr->info->check_value(cptr,val)) return -ERANGE;
+	} else if (cptr->info->type == pvr2_ctl_enum) {
+		if (val < 0) return -ERANGE;
+		if (val >= cptr->info->def.type_enum.count) return -ERANGE;
+	} else {
+		int lim;
+		lim = cptr->info->def.type_int.min_value;
+		if (cptr->info->get_min_value) {
+			cptr->info->get_min_value(cptr,&lim);
+		}
+		if (val < lim) return -ERANGE;
+		lim = cptr->info->def.type_int.max_value;
+		if (cptr->info->get_max_value) {
+			cptr->info->get_max_value(cptr,&lim);
+		}
+		if (val > lim) return -ERANGE;
+	}
+	return 0;
+}
+
+
+/* Set the given control. */
+int pvr2_ctrl_set_value(struct pvr2_ctrl *cptr,int val)
+{
+	return pvr2_ctrl_set_mask_value(cptr,~0,val);
+}
+
+
+/* Set/clear specific bits of the given control. */
+int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *cptr,int mask,int val)
+{
+	int ret = 0;
+	if (!cptr) return -EINVAL;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->set_value) {
+			if (cptr->info->type == pvr2_ctl_bitmask) {
+				mask &= cptr->info->def.type_bitmask.valid_bits;
+			} else if ((cptr->info->type == pvr2_ctl_int)||
+				   (cptr->info->type == pvr2_ctl_enum)) {
+				ret = pvr2_ctrl_range_check(cptr,val);
+				if (ret < 0) break;
+			} else if (cptr->info->type != pvr2_ctl_bool) {
+				break;
+			}
+			ret = cptr->info->set_value(cptr,mask,val);
+		} else {
+			ret = -EPERM;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Get the current value of the given control. */
+int pvr2_ctrl_get_value(struct pvr2_ctrl *cptr,int *valptr)
+{
+	int ret = 0;
+	if (!cptr) return -EINVAL;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		ret = cptr->info->get_value(cptr,valptr);
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's type */
+enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return pvr2_ctl_int;
+	return cptr->info->type;
+}
+
+
+/* Retrieve control's maximum value (int type) */
+int pvr2_ctrl_get_max(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->get_max_value) {
+			cptr->info->get_max_value(cptr,&ret);
+		} else if (cptr->info->type == pvr2_ctl_int) {
+			ret = cptr->info->def.type_int.max_value;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's minimum value (int type) */
+int pvr2_ctrl_get_min(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->get_min_value) {
+			cptr->info->get_min_value(cptr,&ret);
+		} else if (cptr->info->type == pvr2_ctl_int) {
+			ret = cptr->info->def.type_int.min_value;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's default value (any type) */
+int pvr2_ctrl_get_def(struct pvr2_ctrl *cptr, int *valptr)
+{
+	int ret = 0;
+	if (!cptr) return -EINVAL;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->get_def_value) {
+			ret = cptr->info->get_def_value(cptr, valptr);
+		} else {
+			*valptr = cptr->info->default_value;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's enumeration count (enum only) */
+int pvr2_ctrl_get_cnt(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_enum) {
+			ret = cptr->info->def.type_enum.count;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's valid mask bits (bit mask only) */
+int pvr2_ctrl_get_mask(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_bitmask) {
+			ret = cptr->info->def.type_bitmask.valid_bits;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve the control's name */
+const char *pvr2_ctrl_get_name(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return NULL;
+	return cptr->info->name;
+}
+
+
+/* Retrieve the control's desc */
+const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return NULL;
+	return cptr->info->desc;
+}
+
+
+/* Retrieve a control enumeration or bit mask value */
+int pvr2_ctrl_get_valname(struct pvr2_ctrl *cptr,int val,
+			  char *bptr,unsigned int bmax,
+			  unsigned int *blen)
+{
+	int ret = -EINVAL;
+	if (!cptr) return 0;
+	*blen = 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_enum) {
+			const char * const *names;
+			names = cptr->info->def.type_enum.value_names;
+			if (pvr2_ctrl_range_check(cptr,val) == 0) {
+				if (names[val]) {
+					*blen = scnprintf(
+						bptr,bmax,"%s",
+						names[val]);
+				} else {
+					*blen = 0;
+				}
+				ret = 0;
+			}
+		} else if (cptr->info->type == pvr2_ctl_bitmask) {
+			const char **names;
+			unsigned int idx;
+			int msk;
+			names = cptr->info->def.type_bitmask.bit_names;
+			val &= cptr->info->def.type_bitmask.valid_bits;
+			for (idx = 0, msk = 1; val; idx++, msk <<= 1) {
+				if (val & msk) {
+					*blen = scnprintf(bptr,bmax,"%s",
+							  names[idx]);
+					ret = 0;
+					break;
+				}
+			}
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Return V4L ID for this control or zero if none */
+int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return 0;
+	return cptr->info->v4l_id;
+}
+
+
+unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *cptr)
+{
+	unsigned int flags = 0;
+
+	if (cptr->info->get_v4lflags) {
+		flags = cptr->info->get_v4lflags(cptr);
+	}
+
+	if (cptr->info->set_value) {
+		flags &= ~V4L2_CTRL_FLAG_READ_ONLY;
+	} else {
+		flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	}
+
+	return flags;
+}
+
+
+/* Return true if control is writable */
+int pvr2_ctrl_is_writable(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return 0;
+	return cptr->info->set_value != NULL;
+}
+
+
+/* Return true if control has custom symbolic representation */
+int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return 0;
+	if (!cptr->info->val_to_sym) return 0;
+	if (!cptr->info->sym_to_val) return 0;
+	return !0;
+}
+
+
+/* Convert a given mask/val to a custom symbolic value */
+int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *cptr,
+				  int mask,int val,
+				  char *buf,unsigned int maxlen,
+				  unsigned int *len)
+{
+	if (!cptr) return -EINVAL;
+	if (!cptr->info->val_to_sym) return -EINVAL;
+	return cptr->info->val_to_sym(cptr,mask,val,buf,maxlen,len);
+}
+
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *cptr,
+				  const char *buf,unsigned int len,
+				  int *maskptr,int *valptr)
+{
+	if (!cptr) return -EINVAL;
+	if (!cptr->info->sym_to_val) return -EINVAL;
+	return cptr->info->sym_to_val(cptr,buf,len,maskptr,valptr);
+}
+
+
+static unsigned int gen_bitmask_string(int msk,int val,int msk_only,
+				       const char **names,
+				       char *ptr,unsigned int len)
+{
+	unsigned int idx;
+	long sm,um;
+	int spcFl;
+	unsigned int uc,cnt;
+	const char *idStr;
+
+	spcFl = 0;
+	uc = 0;
+	um = 0;
+	for (idx = 0, sm = 1; msk; idx++, sm <<= 1) {
+		if (sm & msk) {
+			msk &= ~sm;
+			idStr = names[idx];
+			if (idStr) {
+				cnt = scnprintf(ptr,len,"%s%s%s",
+						(spcFl ? " " : ""),
+						(msk_only ? "" :
+						 ((val & sm) ? "+" : "-")),
+						idStr);
+				ptr += cnt; len -= cnt; uc += cnt;
+				spcFl = !0;
+			} else {
+				um |= sm;
+			}
+		}
+	}
+	if (um) {
+		if (msk_only) {
+			cnt = scnprintf(ptr,len,"%s0x%lx",
+					(spcFl ? " " : ""),
+					um);
+			ptr += cnt; len -= cnt; uc += cnt;
+			spcFl = !0;
+		} else if (um & val) {
+			cnt = scnprintf(ptr,len,"%s+0x%lx",
+					(spcFl ? " " : ""),
+					um & val);
+			ptr += cnt; len -= cnt; uc += cnt;
+			spcFl = !0;
+		} else if (um & ~val) {
+			cnt = scnprintf(ptr,len,"%s+0x%lx",
+					(spcFl ? " " : ""),
+					um & ~val);
+			ptr += cnt; len -= cnt; uc += cnt;
+			spcFl = !0;
+		}
+	}
+	return uc;
+}
+
+
+static const char *boolNames[] = {
+	"false",
+	"true",
+	"no",
+	"yes",
+};
+
+
+static int parse_token(const char *ptr,unsigned int len,
+		       int *valptr,
+		       const char * const *names, unsigned int namecnt)
+{
+	char buf[33];
+	unsigned int slen;
+	unsigned int idx;
+	int negfl;
+	char *p2;
+	*valptr = 0;
+	if (!names) namecnt = 0;
+	for (idx = 0; idx < namecnt; idx++) {
+		if (!names[idx]) continue;
+		slen = strlen(names[idx]);
+		if (slen != len) continue;
+		if (memcmp(names[idx],ptr,slen)) continue;
+		*valptr = idx;
+		return 0;
+	}
+	negfl = 0;
+	if ((*ptr == '-') || (*ptr == '+')) {
+		negfl = (*ptr == '-');
+		ptr++; len--;
+	}
+	if (len >= sizeof(buf)) return -EINVAL;
+	memcpy(buf,ptr,len);
+	buf[len] = 0;
+	*valptr = simple_strtol(buf,&p2,0);
+	if (negfl) *valptr = -(*valptr);
+	if (*p2) return -EINVAL;
+	return 1;
+}
+
+
+static int parse_mtoken(const char *ptr,unsigned int len,
+			int *valptr,
+			const char **names,int valid_bits)
+{
+	char buf[33];
+	unsigned int slen;
+	unsigned int idx;
+	char *p2;
+	int msk;
+	*valptr = 0;
+	for (idx = 0, msk = 1; valid_bits; idx++, msk <<= 1) {
+		if (!(msk & valid_bits)) continue;
+		valid_bits &= ~msk;
+		if (!names[idx]) continue;
+		slen = strlen(names[idx]);
+		if (slen != len) continue;
+		if (memcmp(names[idx],ptr,slen)) continue;
+		*valptr = msk;
+		return 0;
+	}
+	if (len >= sizeof(buf)) return -EINVAL;
+	memcpy(buf,ptr,len);
+	buf[len] = 0;
+	*valptr = simple_strtol(buf,&p2,0);
+	if (*p2) return -EINVAL;
+	return 0;
+}
+
+
+static int parse_tlist(const char *ptr,unsigned int len,
+		       int *maskptr,int *valptr,
+		       const char **names,int valid_bits)
+{
+	unsigned int cnt;
+	int mask,val,kv,mode,ret;
+	mask = 0;
+	val = 0;
+	ret = 0;
+	while (len) {
+		cnt = 0;
+		while ((cnt < len) &&
+		       ((ptr[cnt] <= 32) ||
+			(ptr[cnt] >= 127))) cnt++;
+		ptr += cnt;
+		len -= cnt;
+		mode = 0;
+		if ((*ptr == '-') || (*ptr == '+')) {
+			mode = (*ptr == '-') ? -1 : 1;
+			ptr++;
+			len--;
+		}
+		cnt = 0;
+		while (cnt < len) {
+			if (ptr[cnt] <= 32) break;
+			if (ptr[cnt] >= 127) break;
+			cnt++;
+		}
+		if (!cnt) break;
+		if (parse_mtoken(ptr,cnt,&kv,names,valid_bits)) {
+			ret = -EINVAL;
+			break;
+		}
+		ptr += cnt;
+		len -= cnt;
+		switch (mode) {
+		case 0:
+			mask = valid_bits;
+			val |= kv;
+			break;
+		case -1:
+			mask |= kv;
+			val &= ~kv;
+			break;
+		case 1:
+			mask |= kv;
+			val |= kv;
+			break;
+		default:
+			break;
+		}
+	}
+	*maskptr = mask;
+	*valptr = val;
+	return ret;
+}
+
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *cptr,
+			   const char *ptr,unsigned int len,
+			   int *maskptr,int *valptr)
+{
+	int ret = -EINVAL;
+	unsigned int cnt;
+
+	*maskptr = 0;
+	*valptr = 0;
+
+	cnt = 0;
+	while ((cnt < len) && ((ptr[cnt] <= 32) || (ptr[cnt] >= 127))) cnt++;
+	len -= cnt; ptr += cnt;
+	cnt = 0;
+	while ((cnt < len) && ((ptr[len-(cnt+1)] <= 32) ||
+			       (ptr[len-(cnt+1)] >= 127))) cnt++;
+	len -= cnt;
+
+	if (!len) return -EINVAL;
+
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_int) {
+			ret = parse_token(ptr,len,valptr,NULL,0);
+			if (ret >= 0) {
+				ret = pvr2_ctrl_range_check(cptr,*valptr);
+			}
+			*maskptr = ~0;
+		} else if (cptr->info->type == pvr2_ctl_bool) {
+			ret = parse_token(ptr,len,valptr,boolNames,
+					  ARRAY_SIZE(boolNames));
+			if (ret == 1) {
+				*valptr = *valptr ? !0 : 0;
+			} else if (ret == 0) {
+				*valptr = (*valptr & 1) ? !0 : 0;
+			}
+			*maskptr = 1;
+		} else if (cptr->info->type == pvr2_ctl_enum) {
+			ret = parse_token(
+				ptr,len,valptr,
+				cptr->info->def.type_enum.value_names,
+				cptr->info->def.type_enum.count);
+			if (ret >= 0) {
+				ret = pvr2_ctrl_range_check(cptr,*valptr);
+			}
+			*maskptr = ~0;
+		} else if (cptr->info->type == pvr2_ctl_bitmask) {
+			ret = parse_tlist(
+				ptr,len,maskptr,valptr,
+				cptr->info->def.type_bitmask.bit_names,
+				cptr->info->def.type_bitmask.valid_bits);
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *cptr,
+				    int mask,int val,
+				    char *buf,unsigned int maxlen,
+				    unsigned int *len)
+{
+	int ret = -EINVAL;
+
+	*len = 0;
+	if (cptr->info->type == pvr2_ctl_int) {
+		*len = scnprintf(buf,maxlen,"%d",val);
+		ret = 0;
+	} else if (cptr->info->type == pvr2_ctl_bool) {
+		*len = scnprintf(buf,maxlen,"%s",val ? "true" : "false");
+		ret = 0;
+	} else if (cptr->info->type == pvr2_ctl_enum) {
+		const char * const *names;
+		names = cptr->info->def.type_enum.value_names;
+		if ((val >= 0) &&
+		    (val < cptr->info->def.type_enum.count)) {
+			if (names[val]) {
+				*len = scnprintf(
+					buf,maxlen,"%s",
+					names[val]);
+			} else {
+				*len = 0;
+			}
+			ret = 0;
+		}
+	} else if (cptr->info->type == pvr2_ctl_bitmask) {
+		*len = gen_bitmask_string(
+			val & mask & cptr->info->def.type_bitmask.valid_bits,
+			~0,!0,
+			cptr->info->def.type_bitmask.bit_names,
+			buf,maxlen);
+	}
+	return ret;
+}
+
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *cptr,
+			   int mask,int val,
+			   char *buf,unsigned int maxlen,
+			   unsigned int *len)
+{
+	int ret;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		ret = pvr2_ctrl_value_to_sym_internal(cptr,mask,val,
+						      buf,maxlen,len);
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h
new file mode 100644
index 0000000..4b9152e
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h
@@ -0,0 +1,108 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_CTRL_H
+#define __PVRUSB2_CTRL_H
+
+struct pvr2_ctrl;
+
+enum pvr2_ctl_type {
+	pvr2_ctl_int = 0,
+	pvr2_ctl_enum = 1,
+	pvr2_ctl_bitmask = 2,
+	pvr2_ctl_bool = 3,
+};
+
+
+/* Set the given control. */
+int pvr2_ctrl_set_value(struct pvr2_ctrl *,int val);
+
+/* Set/clear specific bits of the given control. */
+int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *,int mask,int val);
+
+/* Get the current value of the given control. */
+int pvr2_ctrl_get_value(struct pvr2_ctrl *,int *valptr);
+
+/* Retrieve control's type */
+enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *);
+
+/* Retrieve control's maximum value (int type) */
+int pvr2_ctrl_get_max(struct pvr2_ctrl *);
+
+/* Retrieve control's minimum value (int type) */
+int pvr2_ctrl_get_min(struct pvr2_ctrl *);
+
+/* Retrieve control's default value (any type) */
+int pvr2_ctrl_get_def(struct pvr2_ctrl *, int *valptr);
+
+/* Retrieve control's enumeration count (enum only) */
+int pvr2_ctrl_get_cnt(struct pvr2_ctrl *);
+
+/* Retrieve control's valid mask bits (bit mask only) */
+int pvr2_ctrl_get_mask(struct pvr2_ctrl *);
+
+/* Retrieve the control's name */
+const char *pvr2_ctrl_get_name(struct pvr2_ctrl *);
+
+/* Retrieve the control's desc */
+const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *);
+
+/* Retrieve a control enumeration or bit mask value */
+int pvr2_ctrl_get_valname(struct pvr2_ctrl *,int,char *,unsigned int,
+			  unsigned int *);
+
+/* Return true if control is writable */
+int pvr2_ctrl_is_writable(struct pvr2_ctrl *);
+
+/* Return V4L flags value for control (or zero if there is no v4l control
+   actually under this control) */
+unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *);
+
+/* Return V4L ID for this control or zero if none */
+int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *);
+
+/* Return true if control has custom symbolic representation */
+int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *);
+
+/* Convert a given mask/val to a custom symbolic value */
+int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *,
+				  int mask,int val,
+				  char *buf,unsigned int maxlen,
+				  unsigned int *len);
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *,
+				  const char *buf,unsigned int len,
+				  int *maskptr,int *valptr);
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *,
+			   int mask,int val,
+			   char *buf,unsigned int maxlen,
+			   unsigned int *len);
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *,
+			   const char *buf,unsigned int len,
+			   int *maskptr,int *valptr);
+
+/* Convert a given mask/val to a symbolic value - must already be
+   inside of critical region. */
+int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *,
+			   int mask,int val,
+			   char *buf,unsigned int maxlen,
+			   unsigned int *len);
+
+#endif /* __PVRUSB2_CTRL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c
new file mode 100644
index 0000000..d5bec0f
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c
@@ -0,0 +1,147 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   cx2584x, in kernels 2.6.16 or newer.
+
+*/
+
+#include "pvrusb2-cx2584x-v4l.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <media/drv-intf/cx25840.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+
+
+struct routing_scheme_item {
+	int vid;
+	int aud;
+};
+
+struct routing_scheme {
+	const struct routing_scheme_item *def;
+	unsigned int cnt;
+};
+
+static const struct routing_scheme_item routing_scheme0[] = {
+	[PVR2_CVAL_INPUT_TV] = {
+		.vid = CX25840_COMPOSITE7,
+		.aud = CX25840_AUDIO8,
+	},
+	[PVR2_CVAL_INPUT_RADIO] = { /* Treat the same as composite */
+		.vid = CX25840_COMPOSITE3,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_COMPOSITE] = {
+		.vid = CX25840_COMPOSITE3,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_SVIDEO] = {
+		.vid = CX25840_SVIDEO1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+};
+
+static const struct routing_scheme routing_def0 = {
+	.def = routing_scheme0,
+	.cnt = ARRAY_SIZE(routing_scheme0),
+};
+
+/* Specific to gotview device */
+static const struct routing_scheme_item routing_schemegv[] = {
+	[PVR2_CVAL_INPUT_TV] = {
+		.vid = CX25840_COMPOSITE2,
+		.aud = CX25840_AUDIO5,
+	},
+	[PVR2_CVAL_INPUT_RADIO] = {
+		/* line-in is used for radio and composite.  A GPIO is
+		   used to switch between the two choices. */
+		.vid = CX25840_COMPOSITE1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_COMPOSITE] = {
+		.vid = CX25840_COMPOSITE1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_SVIDEO] = {
+		.vid = (CX25840_SVIDEO_LUMA3|CX25840_SVIDEO_CHROMA4),
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+};
+
+static const struct routing_scheme routing_defgv = {
+	.def = routing_schemegv,
+	.cnt = ARRAY_SIZE(routing_schemegv),
+};
+
+/* Specific to grabster av400 device */
+static const struct routing_scheme_item routing_schemeav400[] = {
+	[PVR2_CVAL_INPUT_COMPOSITE] = {
+		.vid = CX25840_COMPOSITE1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_SVIDEO] = {
+		.vid = (CX25840_SVIDEO_LUMA2|CX25840_SVIDEO_CHROMA4),
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+};
+
+static const struct routing_scheme routing_defav400 = {
+	.def = routing_schemeav400,
+	.cnt = ARRAY_SIZE(routing_schemeav400),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0,
+	[PVR2_ROUTING_SCHEME_GOTVIEW] = &routing_defgv,
+	[PVR2_ROUTING_SCHEME_AV400] = &routing_defav400,
+};
+
+void pvr2_cx25840_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev cx2584x update...");
+	if (hdw->input_dirty || hdw->force_dirty) {
+		enum cx25840_video_input vid_input;
+		enum cx25840_audio_input aud_input;
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+		if ((sp == NULL) ||
+		    (hdw->input_val < 0) ||
+		    (hdw->input_val >= sp->cnt)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev cx2584x set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		vid_input = sp->def[hdw->input_val].vid;
+		aud_input = sp->def[hdw->input_val].aud;
+		pvr2_trace(PVR2_TRACE_CHIPS,
+			   "subdev cx2584x set_input vid=0x%x aud=0x%x",
+			   vid_input, aud_input);
+		sd->ops->video->s_routing(sd, (u32)vid_input, 0, 0);
+		sd->ops->audio->s_routing(sd, (u32)aud_input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h
new file mode 100644
index 0000000..dfddc88
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_CX2584X_V4L_H
+#define __PVRUSB2_CX2584X_V4L_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles combined device audio & video processing.
+   This interface is used internally by the driver; higher level code
+   should only interact through the interface provided by
+   pvrusb2-hdw.h.
+
+*/
+
+
+
+#include "pvrusb2-hdw-internal.h"
+
+void pvr2_cx25840_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *sd);
+
+
+#endif /* __PVRUSB2_CX2584X_V4L_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debug.h b/drivers/media/usb/pvrusb2/pvrusb2-debug.h
new file mode 100644
index 0000000..5cd1629
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-debug.h
@@ -0,0 +1,55 @@
+/*
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_DEBUG_H
+#define __PVRUSB2_DEBUG_H
+
+extern int pvrusb2_debug;
+
+#define pvr2_trace(msk, fmt, arg...) do {if(msk & pvrusb2_debug) printk(KERN_INFO "pvrusb2: " fmt "\n", ##arg); } while (0)
+
+/* These are listed in *rough* order of decreasing usefulness and
+   increasing noise level. */
+#define PVR2_TRACE_INFO       (1 <<  0) /* Normal messages */
+#define PVR2_TRACE_ERROR_LEGS (1 <<  1) /* error messages */
+#define PVR2_TRACE_TOLERANCE  (1 <<  2) /* track tolerance-affected errors */
+#define PVR2_TRACE_TRAP       (1 <<  3) /* Trap & report app misbehavior */
+#define PVR2_TRACE_STD        (1 <<  4) /* Log video standard stuff */
+#define PVR2_TRACE_INIT       (1 <<  5) /* misc initialization steps */
+#define PVR2_TRACE_START_STOP (1 <<  6) /* Streaming start / stop */
+#define PVR2_TRACE_CTL        (1 <<  7) /* commit of control changes */
+#define PVR2_TRACE_STATE      (1 <<  8) /* Device state changes */
+#define PVR2_TRACE_STBITS     (1 <<  9) /* Individual bit state changes */
+#define PVR2_TRACE_EEPROM     (1 << 10) /* eeprom parsing / report */
+#define PVR2_TRACE_STRUCT     (1 << 11) /* internal struct creation */
+#define PVR2_TRACE_OPEN_CLOSE (1 << 12) /* application open / close */
+#define PVR2_TRACE_CTXT       (1 << 13) /* Main context tracking */
+#define PVR2_TRACE_SYSFS      (1 << 14) /* Sysfs driven I/O */
+#define PVR2_TRACE_FIRMWARE   (1 << 15) /* firmware upload actions */
+#define PVR2_TRACE_CHIPS      (1 << 16) /* chip broadcast operation */
+#define PVR2_TRACE_I2C        (1 << 17) /* I2C related stuff */
+#define PVR2_TRACE_I2C_CMD    (1 << 18) /* Software commands to I2C modules */
+#define PVR2_TRACE_I2C_CORE   (1 << 19) /* I2C core debugging */
+#define PVR2_TRACE_I2C_TRAF   (1 << 20) /* I2C traffic through the adapter */
+#define PVR2_TRACE_V4LIOCTL   (1 << 21) /* v4l ioctl details */
+#define PVR2_TRACE_ENCODER    (1 << 22) /* mpeg2 encoder operation */
+#define PVR2_TRACE_BUF_POOL   (1 << 23) /* Track buffer pool management */
+#define PVR2_TRACE_BUF_FLOW   (1 << 24) /* Track buffer flow in system */
+#define PVR2_TRACE_DATA_FLOW  (1 << 25) /* Track data flow */
+#define PVR2_TRACE_DEBUGIFC   (1 << 26) /* Debug interface actions */
+#define PVR2_TRACE_GPIO       (1 << 27) /* GPIO state bit changes */
+#define PVR2_TRACE_DVB_FEED   (1 << 28) /* DVB transport feed debug */
+
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c
new file mode 100644
index 0000000..d3f3bd9
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c
@@ -0,0 +1,318 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include <linux/string.h>
+#include "pvrusb2-debugifc.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+
+struct debugifc_mask_item {
+	const char *name;
+	unsigned long msk;
+};
+
+
+static unsigned int debugifc_count_whitespace(const char *buf,
+					      unsigned int count)
+{
+	unsigned int scnt;
+	char ch;
+
+	for (scnt = 0; scnt < count; scnt++) {
+		ch = buf[scnt];
+		if (ch == ' ') continue;
+		if (ch == '\t') continue;
+		if (ch == '\n') continue;
+		break;
+	}
+	return scnt;
+}
+
+
+static unsigned int debugifc_count_nonwhitespace(const char *buf,
+						 unsigned int count)
+{
+	unsigned int scnt;
+	char ch;
+
+	for (scnt = 0; scnt < count; scnt++) {
+		ch = buf[scnt];
+		if (ch == ' ') break;
+		if (ch == '\t') break;
+		if (ch == '\n') break;
+	}
+	return scnt;
+}
+
+
+static unsigned int debugifc_isolate_word(const char *buf,unsigned int count,
+					  const char **wstrPtr,
+					  unsigned int *wlenPtr)
+{
+	const char *wptr;
+	unsigned int consume_cnt = 0;
+	unsigned int wlen;
+	unsigned int scnt;
+
+	wptr = NULL;
+	wlen = 0;
+	scnt = debugifc_count_whitespace(buf,count);
+	consume_cnt += scnt; count -= scnt; buf += scnt;
+	if (!count) goto done;
+
+	scnt = debugifc_count_nonwhitespace(buf,count);
+	if (!scnt) goto done;
+	wptr = buf;
+	wlen = scnt;
+	consume_cnt += scnt; count -= scnt; buf += scnt;
+
+ done:
+	*wstrPtr = wptr;
+	*wlenPtr = wlen;
+	return consume_cnt;
+}
+
+
+static int debugifc_parse_unsigned_number(const char *buf,unsigned int count,
+					  u32 *num_ptr)
+{
+	u32 result = 0;
+	int radix = 10;
+	if ((count >= 2) && (buf[0] == '0') &&
+	    ((buf[1] == 'x') || (buf[1] == 'X'))) {
+		radix = 16;
+		count -= 2;
+		buf += 2;
+	} else if ((count >= 1) && (buf[0] == '0')) {
+		radix = 8;
+	}
+
+	while (count--) {
+		int val = hex_to_bin(*buf++);
+		if (val < 0 || val >= radix)
+			return -EINVAL;
+		result *= radix;
+		result += val;
+	}
+	*num_ptr = result;
+	return 0;
+}
+
+
+static int debugifc_match_keyword(const char *buf,unsigned int count,
+				  const char *keyword)
+{
+	unsigned int kl;
+	if (!keyword) return 0;
+	kl = strlen(keyword);
+	if (kl != count) return 0;
+	return !memcmp(buf,keyword,kl);
+}
+
+
+int pvr2_debugifc_print_info(struct pvr2_hdw *hdw,char *buf,unsigned int acnt)
+{
+	int bcnt = 0;
+	int ccnt;
+	ccnt = scnprintf(buf, acnt, "Driver hardware description: %s\n",
+			 pvr2_hdw_get_desc(hdw));
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	ccnt = scnprintf(buf,acnt,"Driver state info:\n");
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	ccnt = pvr2_hdw_state_report(hdw,buf,acnt);
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+	return bcnt;
+}
+
+
+int pvr2_debugifc_print_status(struct pvr2_hdw *hdw,
+			       char *buf,unsigned int acnt)
+{
+	int bcnt = 0;
+	int ccnt;
+	int ret;
+	u32 gpio_dir,gpio_in,gpio_out;
+	struct pvr2_stream_stats stats;
+	struct pvr2_stream *sp;
+
+	ret = pvr2_hdw_is_hsm(hdw);
+	ccnt = scnprintf(buf,acnt,"USB link speed: %s\n",
+			 (ret < 0 ? "FAIL" : (ret ? "high" : "full")));
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+	gpio_dir = 0; gpio_in = 0; gpio_out = 0;
+	pvr2_hdw_gpio_get_dir(hdw,&gpio_dir);
+	pvr2_hdw_gpio_get_out(hdw,&gpio_out);
+	pvr2_hdw_gpio_get_in(hdw,&gpio_in);
+	ccnt = scnprintf(buf,acnt,"GPIO state: dir=0x%x in=0x%x out=0x%x\n",
+			 gpio_dir,gpio_in,gpio_out);
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+	ccnt = scnprintf(buf,acnt,"Streaming is %s\n",
+			 pvr2_hdw_get_streaming(hdw) ? "on" : "off");
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+
+	sp = pvr2_hdw_get_video_stream(hdw);
+	if (sp) {
+		pvr2_stream_get_stats(sp, &stats, 0);
+		ccnt = scnprintf(
+			buf,acnt,
+			"Bytes streamed=%u URBs: queued=%u idle=%u ready=%u processed=%u failed=%u\n",
+			stats.bytes_processed,
+			stats.buffers_in_queue,
+			stats.buffers_in_idle,
+			stats.buffers_in_ready,
+			stats.buffers_processed,
+			stats.buffers_failed);
+		bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	}
+
+	return bcnt;
+}
+
+
+static int pvr2_debugifc_do1cmd(struct pvr2_hdw *hdw,const char *buf,
+				unsigned int count)
+{
+	const char *wptr;
+	unsigned int wlen;
+	unsigned int scnt;
+
+	scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+	if (!scnt) return 0;
+	count -= scnt; buf += scnt;
+	if (!wptr) return 0;
+
+	pvr2_trace(PVR2_TRACE_DEBUGIFC,"debugifc cmd: \"%.*s\"",wlen,wptr);
+	if (debugifc_match_keyword(wptr,wlen,"reset")) {
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		if (debugifc_match_keyword(wptr,wlen,"cpu")) {
+			pvr2_hdw_cpureset_assert(hdw,!0);
+			pvr2_hdw_cpureset_assert(hdw,0);
+			return 0;
+		} else if (debugifc_match_keyword(wptr,wlen,"bus")) {
+			pvr2_hdw_device_reset(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"soft")) {
+			return pvr2_hdw_cmd_powerup(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"deep")) {
+			return pvr2_hdw_cmd_deep_reset(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"firmware")) {
+			return pvr2_upload_firmware2(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"decoder")) {
+			return pvr2_hdw_cmd_decoder_reset(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"worker")) {
+			return pvr2_hdw_untrip(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"usbstats")) {
+			pvr2_stream_get_stats(pvr2_hdw_get_video_stream(hdw),
+					      NULL, !0);
+			return 0;
+		}
+		return -EINVAL;
+	} else if (debugifc_match_keyword(wptr,wlen,"cpufw")) {
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		if (debugifc_match_keyword(wptr,wlen,"fetch")) {
+			scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+			if (scnt && wptr) {
+				count -= scnt; buf += scnt;
+				if (debugifc_match_keyword(wptr, wlen,
+							   "prom")) {
+					pvr2_hdw_cpufw_set_enabled(hdw, 2, !0);
+				} else if (debugifc_match_keyword(wptr, wlen,
+								  "ram8k")) {
+					pvr2_hdw_cpufw_set_enabled(hdw, 0, !0);
+				} else if (debugifc_match_keyword(wptr, wlen,
+								  "ram16k")) {
+					pvr2_hdw_cpufw_set_enabled(hdw, 1, !0);
+				} else {
+					return -EINVAL;
+				}
+			}
+			pvr2_hdw_cpufw_set_enabled(hdw,0,!0);
+			return 0;
+		} else if (debugifc_match_keyword(wptr,wlen,"done")) {
+			pvr2_hdw_cpufw_set_enabled(hdw,0,0);
+			return 0;
+		} else {
+			return -EINVAL;
+		}
+	} else if (debugifc_match_keyword(wptr,wlen,"gpio")) {
+		int dir_fl = 0;
+		int ret;
+		u32 msk,val;
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		if (debugifc_match_keyword(wptr,wlen,"dir")) {
+			dir_fl = !0;
+		} else if (!debugifc_match_keyword(wptr,wlen,"out")) {
+			return -EINVAL;
+		}
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		ret = debugifc_parse_unsigned_number(wptr,wlen,&msk);
+		if (ret) return ret;
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (wptr) {
+			ret = debugifc_parse_unsigned_number(wptr,wlen,&val);
+			if (ret) return ret;
+		} else {
+			val = msk;
+			msk = 0xffffffff;
+		}
+		if (dir_fl) {
+			ret = pvr2_hdw_gpio_chg_dir(hdw,msk,val);
+		} else {
+			ret = pvr2_hdw_gpio_chg_out(hdw,msk,val);
+		}
+		return ret;
+	}
+	pvr2_trace(PVR2_TRACE_DEBUGIFC,
+		   "debugifc failed to recognize cmd: \"%.*s\"",wlen,wptr);
+	return -EINVAL;
+}
+
+
+int pvr2_debugifc_docmd(struct pvr2_hdw *hdw,const char *buf,
+			unsigned int count)
+{
+	unsigned int bcnt = 0;
+	int ret;
+
+	while (count) {
+		for (bcnt = 0; bcnt < count; bcnt++) {
+			if (buf[bcnt] == '\n') break;
+		}
+
+		ret = pvr2_debugifc_do1cmd(hdw,buf,bcnt);
+		if (ret < 0) return ret;
+		if (bcnt < count) bcnt++;
+		buf += bcnt;
+		count -= bcnt;
+	}
+
+	return 0;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h
new file mode 100644
index 0000000..fcaaa8d
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_DEBUGIFC_H
+#define __PVRUSB2_DEBUGIFC_H
+
+struct pvr2_hdw;
+
+/* Print general status of driver.  This will also trigger a probe of
+   the USB link.  Unlike print_info(), this one synchronizes with the
+   driver so the information should be self-consistent (but it will
+   hang if the driver is wedged). */
+int pvr2_debugifc_print_info(struct pvr2_hdw *,
+			     char *buf_ptr, unsigned int buf_size);
+
+/* Non-intrusively print some useful debugging info from inside the
+   driver.  This should work even if the driver appears to be
+   wedged. */
+int pvr2_debugifc_print_status(struct pvr2_hdw *,
+			       char *buf_ptr,unsigned int buf_size);
+
+/* Parse a string command into a driver action. */
+int pvr2_debugifc_docmd(struct pvr2_hdw *,
+			const char *buf_ptr,unsigned int buf_size);
+
+#endif /* __PVRUSB2_DEBUGIFC_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.c b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c
new file mode 100644
index 0000000..06de1c8
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c
@@ -0,0 +1,562 @@
+/*
+ *
+ *
+ *  Copyright (C) 2007 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+/*
+
+This source file should encompass ALL per-device type information for the
+driver.  To define a new device, add elements to the pvr2_device_table and
+pvr2_device_desc structures.
+
+*/
+
+#include "pvrusb2-devattr.h"
+#include <linux/usb.h>
+#include <linux/module.h>
+/* This is needed in order to pull in tuner type ids... */
+#include <linux/i2c.h>
+#include <media/tuner.h>
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+#include "pvrusb2-hdw-internal.h"
+#include "lgdt330x.h"
+#include "s5h1409.h"
+#include "s5h1411.h"
+#include "tda10048.h"
+#include "tda18271.h"
+#include "tda8290.h"
+#include "tuner-simple.h"
+#endif
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 29xxx */
+
+static const struct pvr2_device_client_desc pvr2_cli_29xxx[] = {
+	{ .module_id = PVR2_CLIENT_ID_SAA7115 },
+	{ .module_id = PVR2_CLIENT_ID_MSP3400 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+	{ .module_id = PVR2_CLIENT_ID_DEMOD },
+};
+
+#define PVR2_FIRMWARE_29xxx "v4l-pvrusb2-29xxx-01.fw"
+static const char *pvr2_fw1_names_29xxx[] = {
+		PVR2_FIRMWARE_29xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_29xxx = {
+		.description = "WinTV PVR USB2 Model 29xxx",
+		.shortname = "29xxx",
+		.client_table.lst = pvr2_cli_29xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_29xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_29xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_29xxx),
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_29XXX,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 24xxx */
+
+static const struct pvr2_device_client_desc pvr2_cli_24xxx[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+	{ .module_id = PVR2_CLIENT_ID_WM8775 },
+	{ .module_id = PVR2_CLIENT_ID_DEMOD },
+};
+
+#define PVR2_FIRMWARE_24xxx "v4l-pvrusb2-24xxx-01.fw"
+static const char *pvr2_fw1_names_24xxx[] = {
+		PVR2_FIRMWARE_24xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_24xxx = {
+		.description = "WinTV PVR USB2 Model 24xxx",
+		.shortname = "24xxx",
+		.client_table.lst = pvr2_cli_24xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_24xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_24xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_24xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_wm8775 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_24XXX,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* GOTVIEW USB2.0 DVD2 */
+
+static const struct pvr2_device_client_desc pvr2_cli_gotview_2[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+	{ .module_id = PVR2_CLIENT_ID_DEMOD },
+};
+
+static const struct pvr2_device_desc pvr2_device_gotview_2 = {
+		.description = "Gotview USB 2.0 DVD 2",
+		.shortname = "gv2",
+		.client_table.lst = pvr2_cli_gotview_2,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_gotview_2),
+		.flag_has_cx25840 = !0,
+		.default_tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_GOTVIEW,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* GOTVIEW USB2.0 DVD Deluxe */
+
+/* (same module list as gotview_2) */
+
+static const struct pvr2_device_desc pvr2_device_gotview_2d = {
+		.description = "Gotview USB 2.0 DVD Deluxe",
+		.shortname = "gv2d",
+		.client_table.lst = pvr2_cli_gotview_2,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_gotview_2),
+		.flag_has_cx25840 = !0,
+		.default_tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_GOTVIEW,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Terratec Grabster AV400 */
+
+static const struct pvr2_device_client_desc pvr2_cli_av400[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+};
+
+static const struct pvr2_device_desc pvr2_device_av400 = {
+		.description = "Terratec Grabster AV400",
+		.shortname = "av400",
+		.flag_is_experimental = 1,
+		.client_table.lst = pvr2_cli_av400,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_av400),
+		.flag_has_cx25840 = !0,
+		.flag_has_analogtuner = 0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_AV400,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* OnAir Creator */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct lgdt330x_config pvr2_lgdt3303_config = {
+	.demod_chip          = LGDT3303,
+	.clock_polarity_flip = 1,
+};
+
+static int pvr2_lgdt3303_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(lgdt330x_attach, &pvr2_lgdt3303_config,
+			      0x0e,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_lgh06xf_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(simple_tuner_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x61,
+		   TUNER_LG_TDVS_H06XF);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_onair_creator_fe_props = {
+	.frontend_attach = pvr2_lgdt3303_attach,
+	.tuner_attach    = pvr2_lgh06xf_attach,
+};
+#endif
+
+static const struct pvr2_device_client_desc pvr2_cli_onair_creator[] = {
+	{ .module_id = PVR2_CLIENT_ID_SAA7115 },
+	{ .module_id = PVR2_CLIENT_ID_CS53L32A },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+};
+
+static const struct pvr2_device_desc pvr2_device_onair_creator = {
+		.description = "OnAir Creator Hybrid USB tuner",
+		.shortname = "oac",
+		.client_table.lst = pvr2_cli_onair_creator,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_onair_creator),
+		.default_tuner_type = TUNER_LG_TDVS_H06XF,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_digital_requires_cx23416 = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_ONAIR,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_ONAIR,
+		.default_std_mask = V4L2_STD_NTSC_M,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_onair_creator_fe_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* OnAir USB 2.0 */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct lgdt330x_config pvr2_lgdt3302_config = {
+	.demod_chip          = LGDT3302,
+};
+
+static int pvr2_lgdt3302_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(lgdt330x_attach, &pvr2_lgdt3302_config,
+			      0x0e,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_fcv1236d_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(simple_tuner_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x61,
+		   TUNER_PHILIPS_FCV1236D);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_onair_usb2_fe_props = {
+	.frontend_attach = pvr2_lgdt3302_attach,
+	.tuner_attach    = pvr2_fcv1236d_attach,
+};
+#endif
+
+static const struct pvr2_device_client_desc pvr2_cli_onair_usb2[] = {
+	{ .module_id = PVR2_CLIENT_ID_SAA7115 },
+	{ .module_id = PVR2_CLIENT_ID_CS53L32A },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+};
+
+static const struct pvr2_device_desc pvr2_device_onair_usb2 = {
+		.description = "OnAir USB2 Hybrid USB tuner",
+		.shortname = "oa2",
+		.client_table.lst = pvr2_cli_onair_usb2,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_onair_usb2),
+		.default_tuner_type = TUNER_PHILIPS_FCV1236D,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_digital_requires_cx23416 = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_ONAIR,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_ONAIR,
+		.default_std_mask = V4L2_STD_NTSC_M,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_onair_usb2_fe_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 73xxx */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct tda10048_config hauppauge_tda10048_config = {
+	.demod_address  = 0x10 >> 1,
+	.output_mode    = TDA10048_PARALLEL_OUTPUT,
+	.fwbulkwritelen = TDA10048_BULKWRITE_50,
+	.inversion      = TDA10048_INVERSION_ON,
+	.dtv6_if_freq_khz = TDA10048_IF_3300,
+	.dtv7_if_freq_khz = TDA10048_IF_3800,
+	.dtv8_if_freq_khz = TDA10048_IF_4300,
+	.clk_freq_khz   = TDA10048_CLK_16000,
+	.disable_gate_access = 1,
+};
+
+static struct tda829x_config tda829x_no_probe = {
+	.probe_tuner = TDA829X_DONT_PROBE,
+};
+
+static struct tda18271_std_map hauppauge_tda18271_dvbt_std_map = {
+	.dvbt_6   = { .if_freq = 3300, .agc_mode = 3, .std = 4,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+	.dvbt_7   = { .if_freq = 3800, .agc_mode = 3, .std = 5,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+	.dvbt_8   = { .if_freq = 4300, .agc_mode = 3, .std = 6,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config hauppauge_tda18271_dvb_config = {
+	.std_map = &hauppauge_tda18271_dvbt_std_map,
+	.gate    = TDA18271_GATE_ANALOG,
+	.output_opt = TDA18271_OUTPUT_LT_OFF,
+};
+
+static int pvr2_tda10048_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(tda10048_attach, &hauppauge_tda10048_config,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_73xxx_tda18271_8295_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(tda829x_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x42,
+		   &tda829x_no_probe);
+	dvb_attach(tda18271_attach, adap->fe, 0x60,
+		   &adap->channel.hdw->i2c_adap,
+		   &hauppauge_tda18271_dvb_config);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_73xxx_dvb_props = {
+	.frontend_attach = pvr2_tda10048_attach,
+	.tuner_attach    = pvr2_73xxx_tda18271_8295_attach,
+};
+#endif
+
+static const struct pvr2_device_client_desc pvr2_cli_73xxx[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER,
+	  .i2c_address_list = "\x42"},
+};
+
+#define PVR2_FIRMWARE_73xxx "v4l-pvrusb2-73xxx-01.fw"
+static const char *pvr2_fw1_names_73xxx[] = {
+		PVR2_FIRMWARE_73xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_73xxx = {
+		.description = "WinTV HVR-1900 Model 73xxx",
+		.shortname = "73xxx",
+		.client_table.lst = pvr2_cli_73xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_73xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_73xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_fx2_16kb = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_ZILOG,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_73xxx_dvb_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 75xxx */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct s5h1409_config pvr2_s5h1409_config = {
+	.demod_address = 0x32 >> 1,
+	.output_mode   = S5H1409_PARALLEL_OUTPUT,
+	.gpio          = S5H1409_GPIO_OFF,
+	.qam_if        = 4000,
+	.inversion     = S5H1409_INVERSION_ON,
+	.status_mode   = S5H1409_DEMODLOCKING,
+};
+
+static struct s5h1411_config pvr2_s5h1411_config = {
+	.output_mode   = S5H1411_PARALLEL_OUTPUT,
+	.gpio          = S5H1411_GPIO_OFF,
+	.vsb_if        = S5H1411_IF_44000,
+	.qam_if        = S5H1411_IF_4000,
+	.inversion     = S5H1411_INVERSION_ON,
+	.status_mode   = S5H1411_DEMODLOCKING,
+};
+
+static struct tda18271_std_map hauppauge_tda18271_std_map = {
+	.atsc_6   = { .if_freq = 5380, .agc_mode = 3, .std = 3,
+		      .if_lvl = 6, .rfagc_top = 0x37, },
+	.qam_6    = { .if_freq = 4000, .agc_mode = 3, .std = 0,
+		      .if_lvl = 6, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config hauppauge_tda18271_config = {
+	.std_map = &hauppauge_tda18271_std_map,
+	.gate    = TDA18271_GATE_ANALOG,
+	.output_opt = TDA18271_OUTPUT_LT_OFF,
+};
+
+static int pvr2_s5h1409_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(s5h1409_attach, &pvr2_s5h1409_config,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_s5h1411_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(s5h1411_attach, &pvr2_s5h1411_config,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_tda18271_8295_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(tda829x_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x42,
+		   &tda829x_no_probe);
+	dvb_attach(tda18271_attach, adap->fe, 0x60,
+		   &adap->channel.hdw->i2c_adap,
+		   &hauppauge_tda18271_config);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_750xx_dvb_props = {
+	.frontend_attach = pvr2_s5h1409_attach,
+	.tuner_attach    = pvr2_tda18271_8295_attach,
+};
+
+static const struct pvr2_dvb_props pvr2_751xx_dvb_props = {
+	.frontend_attach = pvr2_s5h1411_attach,
+	.tuner_attach    = pvr2_tda18271_8295_attach,
+};
+#endif
+
+#define PVR2_FIRMWARE_75xxx "v4l-pvrusb2-73xxx-01.fw"
+static const char *pvr2_fw1_names_75xxx[] = {
+		PVR2_FIRMWARE_75xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_750xx = {
+		.description = "WinTV HVR-1950 Model 750xx",
+		.shortname = "750xx",
+		.client_table.lst = pvr2_cli_73xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_75xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_75xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_fx2_16kb = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
+		.default_std_mask = V4L2_STD_NTSC_M,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_ZILOG,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_750xx_dvb_props,
+#endif
+};
+
+static const struct pvr2_device_desc pvr2_device_751xx = {
+		.description = "WinTV HVR-1950 Model 751xx",
+		.shortname = "751xx",
+		.client_table.lst = pvr2_cli_73xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_75xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_75xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_fx2_16kb = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
+		.default_std_mask = V4L2_STD_NTSC_M,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_ZILOG,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_751xx_dvb_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+
+struct usb_device_id pvr2_device_table[] = {
+	{ USB_DEVICE(0x2040, 0x2900),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_29xxx},
+	{ USB_DEVICE(0x2040, 0x2950), /* Logically identical to 2900 */
+	  .driver_info = (kernel_ulong_t)&pvr2_device_29xxx},
+	{ USB_DEVICE(0x2040, 0x2400),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_24xxx},
+	{ USB_DEVICE(0x1164, 0x0622),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_gotview_2},
+	{ USB_DEVICE(0x1164, 0x0602),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_gotview_2d},
+	{ USB_DEVICE(0x11ba, 0x1003),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_onair_creator},
+	{ USB_DEVICE(0x11ba, 0x1001),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_onair_usb2},
+	{ USB_DEVICE(0x2040, 0x7300),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_73xxx},
+	{ USB_DEVICE(0x2040, 0x7500),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_750xx},
+	{ USB_DEVICE(0x2040, 0x7501),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_751xx},
+	{ USB_DEVICE(0x0ccd, 0x0039),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_av400},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, pvr2_device_table);
+MODULE_FIRMWARE(PVR2_FIRMWARE_29xxx);
+MODULE_FIRMWARE(PVR2_FIRMWARE_24xxx);
+MODULE_FIRMWARE(PVR2_FIRMWARE_73xxx);
+MODULE_FIRMWARE(PVR2_FIRMWARE_75xxx);
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.h b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h
new file mode 100644
index 0000000..c1e7d48
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h
@@ -0,0 +1,185 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_DEVATTR_H
+#define __PVRUSB2_DEVATTR_H
+
+#include <linux/mod_devicetable.h>
+#include <linux/videodev2.h>
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+#include "pvrusb2-dvb.h"
+#endif
+
+/*
+
+  This header defines structures used to describe attributes of a device.
+
+*/
+
+
+#define PVR2_CLIENT_ID_NULL 0
+#define PVR2_CLIENT_ID_MSP3400 1
+#define PVR2_CLIENT_ID_CX25840 2
+#define PVR2_CLIENT_ID_SAA7115 3
+#define PVR2_CLIENT_ID_TUNER 4
+#define PVR2_CLIENT_ID_CS53L32A 5
+#define PVR2_CLIENT_ID_WM8775 6
+#define PVR2_CLIENT_ID_DEMOD 7
+
+struct pvr2_device_client_desc {
+	/* One ovr PVR2_CLIENT_ID_xxxx */
+	unsigned char module_id;
+
+	/* Null-terminated array of I2C addresses to try in order
+	   initialize the module.  It's safe to make this null terminated
+	   since we're never going to encounter an i2c device with an
+	   address of zero.  If this is a null pointer or zero-length,
+	   then no I2C addresses have been specified, in which case we'll
+	   try some compiled in defaults for now. */
+	unsigned char *i2c_address_list;
+};
+
+struct pvr2_device_client_table {
+	const struct pvr2_device_client_desc *lst;
+	unsigned char cnt;
+};
+
+
+struct pvr2_string_table {
+	const char **lst;
+	unsigned int cnt;
+};
+
+#define PVR2_ROUTING_SCHEME_HAUPPAUGE 0
+#define PVR2_ROUTING_SCHEME_GOTVIEW 1
+#define PVR2_ROUTING_SCHEME_ONAIR 2
+#define PVR2_ROUTING_SCHEME_AV400 3
+
+#define PVR2_DIGITAL_SCHEME_NONE 0
+#define PVR2_DIGITAL_SCHEME_HAUPPAUGE 1
+#define PVR2_DIGITAL_SCHEME_ONAIR 2
+
+#define PVR2_LED_SCHEME_NONE 0
+#define PVR2_LED_SCHEME_HAUPPAUGE 1
+
+#define PVR2_IR_SCHEME_NONE 0
+#define PVR2_IR_SCHEME_24XXX 1 /* FX2-controlled IR */
+#define PVR2_IR_SCHEME_ZILOG 2 /* HVR-1950 style (must be taken out of reset) */
+#define PVR2_IR_SCHEME_24XXX_MCE 3 /* 24xxx MCE device */
+#define PVR2_IR_SCHEME_29XXX 4 /* Original 29xxx device */
+
+/* This describes a particular hardware type (except for the USB device ID
+   which must live in a separate structure due to environmental
+   constraints).  See the top of pvrusb2-hdw.c for where this is
+   instantiated. */
+struct pvr2_device_desc {
+	/* Single line text description of hardware */
+	const char *description;
+
+	/* Single token identifier for hardware */
+	const char *shortname;
+
+	/* List of additional client modules we need to load */
+	struct pvr2_string_table client_modules;
+
+	/* List of defined client modules we need to load */
+	struct pvr2_device_client_table client_table;
+
+	/* List of FX2 firmware file names we should search; if empty then
+	   FX2 firmware check / load is skipped and we assume the device
+	   was initialized from internal ROM. */
+	struct pvr2_string_table fx2_firmware;
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+	/* callback functions to handle attachment of digital tuner & demod */
+	const struct pvr2_dvb_props *dvb_props;
+
+#endif
+	/* Initial standard bits to use for this device, if not zero.
+	   Anything set here is also implied as an available standard.
+	   Note: This is ignored if overridden on the module load line via
+	   the video_std module option. */
+	v4l2_std_id default_std_mask;
+
+	/* V4L tuner type ID to use with this device (only used if the
+	   driver could not discover the type any other way). */
+	int default_tuner_type;
+
+	/* Signal routing scheme used by device, contains one of
+	   PVR2_ROUTING_SCHEME_XXX.  Schemes have to be defined as we
+	   encounter them.  This is an arbitrary integer scheme id; its
+	   meaning is contained entirely within the driver and is
+	   interpreted by logic which must send commands to the chip-level
+	   drivers (search for things which touch this field). */
+	unsigned char signal_routing_scheme;
+
+	/* Indicates scheme for controlling device's LED (if any).  The
+	   driver will turn on the LED when streaming is underway.  This
+	   contains one of PVR2_LED_SCHEME_XXX. */
+	unsigned char led_scheme;
+
+	/* Control scheme to use if there is a digital tuner.  This
+	   contains one of PVR2_DIGITAL_SCHEME_XXX.  This is an arbitrary
+	   integer scheme id; its meaning is contained entirely within the
+	   driver and is interpreted by logic which must control the
+	   streaming pathway (search for things which touch this field). */
+	unsigned char digital_control_scheme;
+
+	/* If set, we don't bother trying to load cx23416 firmware. */
+	unsigned int flag_skip_cx23416_firmware:1;
+
+	/* If set, the encoder must be healthy in order for digital mode to
+	   work (otherwise we assume that digital streaming will work even
+	   if we fail to locate firmware for the encoder).  If the device
+	   doesn't support digital streaming then this flag has no
+	   effect. */
+	unsigned int flag_digital_requires_cx23416:1;
+
+	/* Device has a hauppauge eeprom which we can interrogate. */
+	unsigned int flag_has_hauppauge_rom:1;
+
+	/* Device does not require a powerup command to be issued. */
+	unsigned int flag_no_powerup:1;
+
+	/* Device has a cx25840 - this enables special additional logic to
+	   handle it. */
+	unsigned int flag_has_cx25840:1;
+
+	/* Device has a wm8775 - this enables special additional logic to
+	   ensure that it is found. */
+	unsigned int flag_has_wm8775:1;
+
+	/* Indicate IR scheme of hardware.  If not set, then it is assumed
+	   that IR can work without any help from the driver. */
+	unsigned int ir_scheme:3;
+
+	/* These bits define which kinds of sources the device can handle.
+	   Note: Digital tuner presence is inferred by the
+	   digital_control_scheme enumeration. */
+	unsigned int flag_has_fmradio:1;       /* Has FM radio receiver */
+	unsigned int flag_has_analogtuner:1;   /* Has analog tuner */
+	unsigned int flag_has_composite:1;     /* Has composite input */
+	unsigned int flag_has_svideo:1;        /* Has s-video input */
+	unsigned int flag_fx2_16kb:1;          /* 16KB FX2 firmware OK here */
+
+	/* If this driver is considered experimental, i.e. not all aspects
+	   are working correctly and/or it is untested, mark that fact
+	   with this flag. */
+	unsigned int flag_is_experimental:1;
+};
+
+extern struct usb_device_id pvr2_device_table[];
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-dvb.c b/drivers/media/usb/pvrusb2/pvrusb2-dvb.c
new file mode 100644
index 0000000..4b32b21
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-dvb.c
@@ -0,0 +1,431 @@
+/*
+ *  pvrusb2-dvb.c - linux-dvb api interface to the pvrusb2 driver.
+ *
+ *  Copyright (C) 2007, 2008 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <media/dvbdev.h>
+#include "pvrusb2-debug.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-io.h"
+#include "pvrusb2-dvb.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int pvr2_dvb_feed_func(struct pvr2_dvb_adapter *adap)
+{
+	int ret;
+	unsigned int count;
+	struct pvr2_buffer *bp;
+	struct pvr2_stream *stream;
+
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "dvb feed thread started");
+	set_freezable();
+
+	stream = adap->channel.stream->stream;
+
+	for (;;) {
+		if (kthread_should_stop()) break;
+
+		/* Not sure about this... */
+		try_to_freeze();
+
+		bp = pvr2_stream_get_ready_buffer(stream);
+		if (bp != NULL) {
+			count = pvr2_buffer_get_count(bp);
+			if (count) {
+				dvb_dmx_swfilter(
+					&adap->demux,
+					adap->buffer_storage[
+					    pvr2_buffer_get_id(bp)],
+					count);
+			} else {
+				ret = pvr2_buffer_get_status(bp);
+				if (ret < 0) break;
+			}
+			ret = pvr2_buffer_queue(bp);
+			if (ret < 0) break;
+
+			/* Since we know we did something to a buffer,
+			   just go back and try again.  No point in
+			   blocking unless we really ran out of
+			   buffers to process. */
+			continue;
+		}
+
+
+		/* Wait until more buffers become available or we're
+		   told not to wait any longer. */
+		ret = wait_event_interruptible(
+		    adap->buffer_wait_data,
+		    (pvr2_stream_get_ready_count(stream) > 0) ||
+		    kthread_should_stop());
+		if (ret < 0) break;
+	}
+
+	/* If we get here and ret is < 0, then an error has occurred.
+	   Probably would be a good idea to communicate that to DVB core... */
+
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "dvb feed thread stopped");
+
+	return 0;
+}
+
+static int pvr2_dvb_feed_thread(void *data)
+{
+	int stat = pvr2_dvb_feed_func(data);
+	/* from videobuf-dvb.c: */
+	while (!kthread_should_stop()) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule();
+	}
+	return stat;
+}
+
+static void pvr2_dvb_notify(struct pvr2_dvb_adapter *adap)
+{
+	wake_up(&adap->buffer_wait_data);
+}
+
+static void pvr2_dvb_stream_end(struct pvr2_dvb_adapter *adap)
+{
+	unsigned int idx;
+	struct pvr2_stream *stream;
+
+	if (adap->thread) {
+		kthread_stop(adap->thread);
+		adap->thread = NULL;
+	}
+
+	if (adap->channel.stream) {
+		stream = adap->channel.stream->stream;
+	} else {
+		stream = NULL;
+	}
+	if (stream) {
+		pvr2_hdw_set_streaming(adap->channel.hdw, 0);
+		pvr2_stream_set_callback(stream, NULL, NULL);
+		pvr2_stream_kill(stream);
+		pvr2_stream_set_buffer_count(stream, 0);
+		pvr2_channel_claim_stream(&adap->channel, NULL);
+	}
+
+	if (adap->stream_run) {
+		for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) {
+			if (!(adap->buffer_storage[idx])) continue;
+			kfree(adap->buffer_storage[idx]);
+			adap->buffer_storage[idx] = NULL;
+		}
+		adap->stream_run = 0;
+	}
+}
+
+static int pvr2_dvb_stream_do_start(struct pvr2_dvb_adapter *adap)
+{
+	struct pvr2_context *pvr = adap->channel.mc_head;
+	unsigned int idx;
+	int ret;
+	struct pvr2_buffer *bp;
+	struct pvr2_stream *stream = NULL;
+
+	if (adap->stream_run) return -EIO;
+
+	ret = pvr2_channel_claim_stream(&adap->channel, &pvr->video_stream);
+	/* somebody else already has the stream */
+	if (ret < 0) return ret;
+
+	stream = adap->channel.stream->stream;
+
+	for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) {
+		adap->buffer_storage[idx] = kmalloc(PVR2_DVB_BUFFER_SIZE,
+						    GFP_KERNEL);
+		if (!(adap->buffer_storage[idx])) return -ENOMEM;
+	}
+
+	pvr2_stream_set_callback(pvr->video_stream.stream,
+				 (pvr2_stream_callback) pvr2_dvb_notify, adap);
+
+	ret = pvr2_stream_set_buffer_count(stream, PVR2_DVB_BUFFER_COUNT);
+	if (ret < 0) return ret;
+
+	for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) {
+		bp = pvr2_stream_get_buffer(stream, idx);
+		pvr2_buffer_set_buffer(bp,
+				       adap->buffer_storage[idx],
+				       PVR2_DVB_BUFFER_SIZE);
+	}
+
+	ret = pvr2_hdw_set_streaming(adap->channel.hdw, 1);
+	if (ret < 0) return ret;
+
+	while ((bp = pvr2_stream_get_idle_buffer(stream)) != NULL) {
+		ret = pvr2_buffer_queue(bp);
+		if (ret < 0) return ret;
+	}
+
+	adap->thread = kthread_run(pvr2_dvb_feed_thread, adap, "pvrusb2-dvb");
+
+	if (IS_ERR(adap->thread)) {
+		ret = PTR_ERR(adap->thread);
+		adap->thread = NULL;
+		return ret;
+	}
+
+	adap->stream_run = !0;
+
+	return 0;
+}
+
+static int pvr2_dvb_stream_start(struct pvr2_dvb_adapter *adap)
+{
+	int ret = pvr2_dvb_stream_do_start(adap);
+	if (ret < 0) pvr2_dvb_stream_end(adap);
+	return ret;
+}
+
+static int pvr2_dvb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
+{
+	struct pvr2_dvb_adapter *adap = dvbdmxfeed->demux->priv;
+	int ret = 0;
+
+	if (adap == NULL) return -ENODEV;
+
+	mutex_lock(&adap->lock);
+	do {
+		if (onoff) {
+			if (!adap->feedcount) {
+				pvr2_trace(PVR2_TRACE_DVB_FEED,
+					   "start feeding demux");
+				ret = pvr2_dvb_stream_start(adap);
+				if (ret < 0) break;
+			}
+			(adap->feedcount)++;
+		} else if (adap->feedcount > 0) {
+			(adap->feedcount)--;
+			if (!adap->feedcount) {
+				pvr2_trace(PVR2_TRACE_DVB_FEED,
+					   "stop feeding demux");
+				pvr2_dvb_stream_end(adap);
+			}
+		}
+	} while (0);
+	mutex_unlock(&adap->lock);
+
+	return ret;
+}
+
+static int pvr2_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "start pid: 0x%04x", dvbdmxfeed->pid);
+	return pvr2_dvb_ctrl_feed(dvbdmxfeed, 1);
+}
+
+static int pvr2_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "stop pid: 0x%04x", dvbdmxfeed->pid);
+	return pvr2_dvb_ctrl_feed(dvbdmxfeed, 0);
+}
+
+static int pvr2_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+	struct pvr2_dvb_adapter *adap = fe->dvb->priv;
+	return pvr2_channel_limit_inputs(
+	    &adap->channel,
+	    (acquire ? (1 << PVR2_CVAL_INPUT_DTV) : 0));
+}
+
+static int pvr2_dvb_adapter_init(struct pvr2_dvb_adapter *adap)
+{
+	int ret;
+
+	ret = dvb_register_adapter(&adap->dvb_adap, "pvrusb2-dvb",
+				   THIS_MODULE/*&hdw->usb_dev->owner*/,
+				   &adap->channel.hdw->usb_dev->dev,
+				   adapter_nr);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "dvb_register_adapter failed: error %d", ret);
+		goto err;
+	}
+	adap->dvb_adap.priv = adap;
+
+	adap->demux.dmx.capabilities = DMX_TS_FILTERING |
+				       DMX_SECTION_FILTERING |
+				       DMX_MEMORY_BASED_FILTERING;
+	adap->demux.priv             = adap;
+	adap->demux.filternum        = 256;
+	adap->demux.feednum          = 256;
+	adap->demux.start_feed       = pvr2_dvb_start_feed;
+	adap->demux.stop_feed        = pvr2_dvb_stop_feed;
+	adap->demux.write_to_decoder = NULL;
+
+	ret = dvb_dmx_init(&adap->demux);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "dvb_dmx_init failed: error %d", ret);
+		goto err_dmx;
+	}
+
+	adap->dmxdev.filternum       = adap->demux.filternum;
+	adap->dmxdev.demux           = &adap->demux.dmx;
+	adap->dmxdev.capabilities    = 0;
+
+	ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "dvb_dmxdev_init failed: error %d", ret);
+		goto err_dmx_dev;
+	}
+
+	dvb_net_init(&adap->dvb_adap, &adap->dvb_net, &adap->demux.dmx);
+
+	return 0;
+
+err_dmx_dev:
+	dvb_dmx_release(&adap->demux);
+err_dmx:
+	dvb_unregister_adapter(&adap->dvb_adap);
+err:
+	return ret;
+}
+
+static int pvr2_dvb_adapter_exit(struct pvr2_dvb_adapter *adap)
+{
+	pvr2_trace(PVR2_TRACE_INFO, "unregistering DVB devices");
+	dvb_net_release(&adap->dvb_net);
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb_adap);
+	return 0;
+}
+
+static int pvr2_dvb_frontend_init(struct pvr2_dvb_adapter *adap)
+{
+	struct pvr2_hdw *hdw = adap->channel.hdw;
+	const struct pvr2_dvb_props *dvb_props = hdw->hdw_desc->dvb_props;
+	int ret = 0;
+
+	if (dvb_props == NULL) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS, "fe_props not defined!");
+		return -EINVAL;
+	}
+
+	ret = pvr2_channel_limit_inputs(
+	    &adap->channel,
+	    (1 << PVR2_CVAL_INPUT_DTV));
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "failed to grab control of dtv input (code=%d)",
+		    ret);
+		return ret;
+	}
+
+	if (dvb_props->frontend_attach == NULL) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "frontend_attach not defined!");
+		ret = -EINVAL;
+		goto done;
+	}
+
+	if ((dvb_props->frontend_attach(adap) == 0) && (adap->fe)) {
+
+		if (dvb_register_frontend(&adap->dvb_adap, adap->fe)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "frontend registration failed!");
+			dvb_frontend_detach(adap->fe);
+			adap->fe = NULL;
+			ret = -ENODEV;
+			goto done;
+		}
+
+		if (dvb_props->tuner_attach)
+			dvb_props->tuner_attach(adap);
+
+		if (adap->fe->ops.analog_ops.standby)
+			adap->fe->ops.analog_ops.standby(adap->fe);
+
+		/* Ensure all frontends negotiate bus access */
+		adap->fe->ops.ts_bus_ctrl = pvr2_dvb_bus_ctrl;
+
+	} else {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "no frontend was attached!");
+		ret = -ENODEV;
+		return ret;
+	}
+
+ done:
+	pvr2_channel_limit_inputs(&adap->channel, 0);
+	return ret;
+}
+
+static int pvr2_dvb_frontend_exit(struct pvr2_dvb_adapter *adap)
+{
+	if (adap->fe != NULL) {
+		dvb_unregister_frontend(adap->fe);
+		dvb_frontend_detach(adap->fe);
+	}
+	return 0;
+}
+
+static void pvr2_dvb_destroy(struct pvr2_dvb_adapter *adap)
+{
+	pvr2_dvb_stream_end(adap);
+	pvr2_dvb_frontend_exit(adap);
+	pvr2_dvb_adapter_exit(adap);
+	pvr2_channel_done(&adap->channel);
+	kfree(adap);
+}
+
+static void pvr2_dvb_internal_check(struct pvr2_channel *chp)
+{
+	struct pvr2_dvb_adapter *adap;
+	adap = container_of(chp, struct pvr2_dvb_adapter, channel);
+	if (!adap->channel.mc_head->disconnect_flag) return;
+	pvr2_dvb_destroy(adap);
+}
+
+struct pvr2_dvb_adapter *pvr2_dvb_create(struct pvr2_context *pvr)
+{
+	int ret = 0;
+	struct pvr2_dvb_adapter *adap;
+	if (!pvr->hdw->hdw_desc->dvb_props) {
+		/* Device lacks a digital interface so don't set up
+		   the DVB side of the driver either.  For now. */
+		return NULL;
+	}
+	adap = kzalloc(sizeof(*adap), GFP_KERNEL);
+	if (!adap) return adap;
+	pvr2_channel_init(&adap->channel, pvr);
+	adap->channel.check_func = pvr2_dvb_internal_check;
+	init_waitqueue_head(&adap->buffer_wait_data);
+	mutex_init(&adap->lock);
+	ret = pvr2_dvb_adapter_init(adap);
+	if (ret < 0) goto fail1;
+	ret = pvr2_dvb_frontend_init(adap);
+	if (ret < 0) goto fail2;
+	return adap;
+
+fail2:
+	pvr2_dvb_adapter_exit(adap);
+fail1:
+	pvr2_channel_done(&adap->channel);
+	return NULL;
+}
+
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-dvb.h b/drivers/media/usb/pvrusb2/pvrusb2-dvb.h
new file mode 100644
index 0000000..e7f71fb
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-dvb.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PVRUSB2_DVB_H__
+#define __PVRUSB2_DVB_H__
+
+#include <media/dvb_frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_net.h>
+#include <media/dmxdev.h>
+#include "pvrusb2-context.h"
+
+#define PVR2_DVB_BUFFER_COUNT 32
+#define PVR2_DVB_BUFFER_SIZE PAGE_ALIGN(0x4000)
+
+struct pvr2_dvb_adapter {
+	struct pvr2_channel	channel;
+
+	struct dvb_adapter	dvb_adap;
+	struct dmxdev		dmxdev;
+	struct dvb_demux	demux;
+	struct dvb_net		dvb_net;
+	struct dvb_frontend	*fe;
+
+	int			feedcount;
+	int			max_feed_count;
+
+	struct task_struct	*thread;
+	struct mutex		lock;
+
+	unsigned int		stream_run:1;
+
+	wait_queue_head_t	buffer_wait_data;
+	char			*buffer_storage[PVR2_DVB_BUFFER_COUNT];
+};
+
+struct pvr2_dvb_props {
+	int (*frontend_attach) (struct pvr2_dvb_adapter *);
+	int (*tuner_attach) (struct pvr2_dvb_adapter *);
+};
+
+struct pvr2_dvb_adapter *pvr2_dvb_create(struct pvr2_context *pvr);
+
+#endif /* __PVRUSB2_DVB_H__ */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c
new file mode 100644
index 0000000..8b643d5
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c
@@ -0,0 +1,144 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#include <linux/slab.h>
+#include "pvrusb2-eeprom.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+
+#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__)
+
+
+
+/*
+
+   Read and analyze data in the eeprom.  Use tveeprom to figure out
+   the packet structure, since this is another Hauppauge device and
+   internally it has a family resemblance to ivtv-type devices
+
+*/
+
+#include <media/tveeprom.h>
+
+/* We seem to only be interested in the last 128 bytes of the EEPROM */
+#define EEPROM_SIZE 128
+
+/* Grab EEPROM contents, needed for direct method. */
+static u8 *pvr2_eeprom_fetch(struct pvr2_hdw *hdw)
+{
+	struct i2c_msg msg[2];
+	u8 *eeprom;
+	u8 iadd[2];
+	u8 addr;
+	u16 eepromSize;
+	unsigned int offs;
+	int ret;
+	int mode16 = 0;
+	unsigned pcnt,tcnt;
+	eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL);
+	if (!eeprom) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to allocate memory required to read eeprom");
+		return NULL;
+	}
+
+	trace_eeprom("Value for eeprom addr from controller was 0x%x",
+		     hdw->eeprom_addr);
+	addr = hdw->eeprom_addr;
+	/* Seems that if the high bit is set, then the *real* eeprom
+	   address is shifted right now bit position (noticed this in
+	   newer PVR USB2 hardware) */
+	if (addr & 0x80) addr >>= 1;
+
+	/* FX2 documentation states that a 16bit-addressed eeprom is
+	   expected if the I2C address is an odd number (yeah, this is
+	   strange but it's what they do) */
+	mode16 = (addr & 1);
+	eepromSize = (mode16 ? 4096 : 256);
+	trace_eeprom("Examining %d byte eeprom at location 0x%x using %d bit addressing",
+		     eepromSize, addr,
+		     mode16 ? 16 : 8);
+
+	msg[0].addr = addr;
+	msg[0].flags = 0;
+	msg[0].len = mode16 ? 2 : 1;
+	msg[0].buf = iadd;
+	msg[1].addr = addr;
+	msg[1].flags = I2C_M_RD;
+
+	/* We have to do the actual eeprom data fetch ourselves, because
+	   (1) we're only fetching part of the eeprom, and (2) if we were
+	   getting the whole thing our I2C driver can't grab it in one
+	   pass - which is what tveeprom is otherwise going to attempt */
+	memset(eeprom,0,EEPROM_SIZE);
+	for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) {
+		pcnt = 16;
+		if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt;
+		offs = tcnt + (eepromSize - EEPROM_SIZE);
+		if (mode16) {
+			iadd[0] = offs >> 8;
+			iadd[1] = offs;
+		} else {
+			iadd[0] = offs;
+		}
+		msg[1].len = pcnt;
+		msg[1].buf = eeprom+tcnt;
+		if ((ret = i2c_transfer(&hdw->i2c_adap,
+					msg,ARRAY_SIZE(msg))) != 2) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "eeprom fetch set offs err=%d",ret);
+			kfree(eeprom);
+			return NULL;
+		}
+	}
+	return eeprom;
+}
+
+
+/* Directly call eeprom analysis function within tveeprom. */
+int pvr2_eeprom_analyze(struct pvr2_hdw *hdw)
+{
+	u8 *eeprom;
+	struct tveeprom tvdata;
+
+	memset(&tvdata,0,sizeof(tvdata));
+
+	eeprom = pvr2_eeprom_fetch(hdw);
+	if (!eeprom)
+		return -EINVAL;
+
+	tveeprom_hauppauge_analog(&tvdata, eeprom);
+
+	trace_eeprom("eeprom assumed v4l tveeprom module");
+	trace_eeprom("eeprom direct call results:");
+	trace_eeprom("has_radio=%d",tvdata.has_radio);
+	trace_eeprom("tuner_type=%d",tvdata.tuner_type);
+	trace_eeprom("tuner_formats=0x%x",tvdata.tuner_formats);
+	trace_eeprom("audio_processor=%d",tvdata.audio_processor);
+	trace_eeprom("model=%d",tvdata.model);
+	trace_eeprom("revision=%d",tvdata.revision);
+	trace_eeprom("serial_number=%d",tvdata.serial_number);
+	trace_eeprom("rev_str=%s",tvdata.rev_str);
+	hdw->tuner_type = tvdata.tuner_type;
+	hdw->tuner_updated = !0;
+	hdw->serial_number = tvdata.serial_number;
+	hdw->std_mask_eeprom = tvdata.tuner_formats;
+
+	kfree(eeprom);
+
+	return 0;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h
new file mode 100644
index 0000000..1d81cac
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_EEPROM_H
+#define __PVRUSB2_EEPROM_H
+
+struct pvr2_hdw;
+
+int pvr2_eeprom_analyze(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_EEPROM_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-encoder.c b/drivers/media/usb/pvrusb2/pvrusb2-encoder.c
new file mode 100644
index 0000000..43e4340
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-encoder.c
@@ -0,0 +1,523 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#include <linux/device.h>   // for linux/firmware.h
+#include <linux/firmware.h>
+#include "pvrusb2-util.h"
+#include "pvrusb2-encoder.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-fx2-cmd.h"
+
+
+
+/* Firmware mailbox flags - definitions found from ivtv */
+#define IVTV_MBOX_FIRMWARE_DONE 0x00000004
+#define IVTV_MBOX_DRIVER_DONE 0x00000002
+#define IVTV_MBOX_DRIVER_BUSY 0x00000001
+
+#define MBOX_BASE 0x44
+
+
+static int pvr2_encoder_write_words(struct pvr2_hdw *hdw,
+				    unsigned int offs,
+				    const u32 *data, unsigned int dlen)
+{
+	unsigned int idx,addr;
+	unsigned int bAddr;
+	int ret;
+	unsigned int chunkCnt;
+
+	/*
+
+	Format: First byte must be 0x01.  Remaining 32 bit words are
+	spread out into chunks of 7 bytes each, with the first 4 bytes
+	being the data word (little endian), and the next 3 bytes
+	being the address where that data word is to be written (big
+	endian).  Repeat request for additional words, with offset
+	adjusted accordingly.
+
+	*/
+	while (dlen) {
+		chunkCnt = 8;
+		if (chunkCnt > dlen) chunkCnt = dlen;
+		memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer));
+		bAddr = 0;
+		hdw->cmd_buffer[bAddr++] = FX2CMD_MEM_WRITE_DWORD;
+		for (idx = 0; idx < chunkCnt; idx++) {
+			addr = idx + offs;
+			hdw->cmd_buffer[bAddr+6] = (addr & 0xffu);
+			hdw->cmd_buffer[bAddr+5] = ((addr>>8) & 0xffu);
+			hdw->cmd_buffer[bAddr+4] = ((addr>>16) & 0xffu);
+			PVR2_DECOMPOSE_LE(hdw->cmd_buffer, bAddr,data[idx]);
+			bAddr += 7;
+		}
+		ret = pvr2_send_request(hdw,
+					hdw->cmd_buffer,1+(chunkCnt*7),
+					NULL,0);
+		if (ret) return ret;
+		data += chunkCnt;
+		dlen -= chunkCnt;
+		offs += chunkCnt;
+	}
+
+	return 0;
+}
+
+
+static int pvr2_encoder_read_words(struct pvr2_hdw *hdw,
+				   unsigned int offs,
+				   u32 *data, unsigned int dlen)
+{
+	unsigned int idx;
+	int ret;
+	unsigned int chunkCnt;
+
+	/*
+
+	Format: First byte must be 0x02 (status check) or 0x28 (read
+	back block of 32 bit words).  Next 6 bytes must be zero,
+	followed by a single byte of MBOX_BASE+offset for portion to
+	be read.  Returned data is packed set of 32 bits words that
+	were read.
+
+	*/
+
+	while (dlen) {
+		chunkCnt = 16;
+		if (chunkCnt > dlen) chunkCnt = dlen;
+		if (chunkCnt < 16) chunkCnt = 1;
+		hdw->cmd_buffer[0] =
+			((chunkCnt == 1) ?
+			 FX2CMD_MEM_READ_DWORD : FX2CMD_MEM_READ_64BYTES);
+		hdw->cmd_buffer[1] = 0;
+		hdw->cmd_buffer[2] = 0;
+		hdw->cmd_buffer[3] = 0;
+		hdw->cmd_buffer[4] = 0;
+		hdw->cmd_buffer[5] = ((offs>>16) & 0xffu);
+		hdw->cmd_buffer[6] = ((offs>>8) & 0xffu);
+		hdw->cmd_buffer[7] = (offs & 0xffu);
+		ret = pvr2_send_request(hdw,
+					hdw->cmd_buffer,8,
+					hdw->cmd_buffer,
+					(chunkCnt == 1 ? 4 : 16 * 4));
+		if (ret) return ret;
+
+		for (idx = 0; idx < chunkCnt; idx++) {
+			data[idx] = PVR2_COMPOSE_LE(hdw->cmd_buffer,idx*4);
+		}
+		data += chunkCnt;
+		dlen -= chunkCnt;
+		offs += chunkCnt;
+	}
+
+	return 0;
+}
+
+
+/* This prototype is set up to be compatible with the
+   cx2341x_mbox_func prototype in cx2341x.h, which should be in
+   kernels 2.6.18 or later.  We do this so that we can enable
+   cx2341x.ko to write to our encoder (by handing it a pointer to this
+   function).  For earlier kernels this doesn't really matter. */
+static int pvr2_encoder_cmd(void *ctxt,
+			    u32 cmd,
+			    int arg_cnt_send,
+			    int arg_cnt_recv,
+			    u32 *argp)
+{
+	unsigned int poll_count;
+	unsigned int try_count = 0;
+	int retry_flag;
+	int ret = 0;
+	unsigned int idx;
+	/* These sizes look to be limited by the FX2 firmware implementation */
+	u32 wrData[16];
+	u32 rdData[16];
+	struct pvr2_hdw *hdw = (struct pvr2_hdw *)ctxt;
+
+
+	/*
+
+	The encoder seems to speak entirely using blocks 32 bit words.
+	In ivtv driver terms, this is a mailbox at MBOX_BASE which we
+	populate with data and watch what the hardware does with it.
+	The first word is a set of flags used to control the
+	transaction, the second word is the command to execute, the
+	third byte is zero (ivtv driver suggests that this is some
+	kind of return value), and the fourth byte is a specified
+	timeout (windows driver always uses 0x00060000 except for one
+	case when it is zero).  All successive words are the argument
+	words for the command.
+
+	First, write out the entire set of words, with the first word
+	being zero.
+
+	Next, write out just the first word again, but set it to
+	IVTV_MBOX_DRIVER_DONE | IVTV_DRIVER_BUSY this time (which
+	probably means "go").
+
+	Next, read back the return count words.  Check the first word,
+	which should have IVTV_MBOX_FIRMWARE_DONE set.  If however
+	that bit is not set, then the command isn't done so repeat the
+	read until it is set.
+
+	Finally, write out just the first word again, but set it to
+	0x0 this time (which probably means "idle").
+
+	*/
+
+	if (arg_cnt_send > (ARRAY_SIZE(wrData) - 4)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Failed to write cx23416 command - too many input arguments (was given %u limit %lu)",
+			arg_cnt_send, (long unsigned) ARRAY_SIZE(wrData) - 4);
+		return -EINVAL;
+	}
+
+	if (arg_cnt_recv > (ARRAY_SIZE(rdData) - 4)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Failed to write cx23416 command - too many return arguments (was given %u limit %lu)",
+			arg_cnt_recv, (long unsigned) ARRAY_SIZE(rdData) - 4);
+		return -EINVAL;
+	}
+
+
+	LOCK_TAKE(hdw->ctl_lock); while (1) {
+
+		if (!hdw->state_encoder_ok) {
+			ret = -EIO;
+			break;
+		}
+
+		retry_flag = 0;
+		try_count++;
+		ret = 0;
+		wrData[0] = 0;
+		wrData[1] = cmd;
+		wrData[2] = 0;
+		wrData[3] = 0x00060000;
+		for (idx = 0; idx < arg_cnt_send; idx++) {
+			wrData[idx+4] = argp[idx];
+		}
+		for (; idx < ARRAY_SIZE(wrData) - 4; idx++) {
+			wrData[idx+4] = 0;
+		}
+
+		ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,idx);
+		if (ret) break;
+		wrData[0] = IVTV_MBOX_DRIVER_DONE|IVTV_MBOX_DRIVER_BUSY;
+		ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,1);
+		if (ret) break;
+		poll_count = 0;
+		while (1) {
+			poll_count++;
+			ret = pvr2_encoder_read_words(hdw,MBOX_BASE,rdData,
+						      arg_cnt_recv+4);
+			if (ret) {
+				break;
+			}
+			if (rdData[0] & IVTV_MBOX_FIRMWARE_DONE) {
+				break;
+			}
+			if (rdData[0] && (poll_count < 1000)) continue;
+			if (!rdData[0]) {
+				retry_flag = !0;
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"Encoder timed out waiting for us; arranging to retry");
+			} else {
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"***WARNING*** device's encoder appears to be stuck (status=0x%08x)",
+rdData[0]);
+			}
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Encoder command: 0x%02x",cmd);
+			for (idx = 4; idx < arg_cnt_send; idx++) {
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"Encoder arg%d: 0x%08x",
+					idx-3,wrData[idx]);
+			}
+			ret = -EBUSY;
+			break;
+		}
+		if (retry_flag) {
+			if (try_count < 20) continue;
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Too many retries...");
+			ret = -EBUSY;
+		}
+		if (ret) {
+			del_timer_sync(&hdw->encoder_run_timer);
+			hdw->state_encoder_ok = 0;
+			pvr2_trace(PVR2_TRACE_STBITS,
+				   "State bit %s <-- %s",
+				   "state_encoder_ok",
+				   (hdw->state_encoder_ok ? "true" : "false"));
+			if (hdw->state_encoder_runok) {
+				hdw->state_encoder_runok = 0;
+				pvr2_trace(PVR2_TRACE_STBITS,
+				   "State bit %s <-- %s",
+					   "state_encoder_runok",
+					   (hdw->state_encoder_runok ?
+					    "true" : "false"));
+			}
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Giving up on command.  This is normally recovered via a firmware reload and re-initialization; concern is only warranted if this happens repeatedly and rapidly.");
+			break;
+		}
+		wrData[0] = 0x7;
+		for (idx = 0; idx < arg_cnt_recv; idx++) {
+			argp[idx] = rdData[idx+4];
+		}
+
+		wrData[0] = 0x0;
+		ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,1);
+		break;
+
+	}; LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+
+static int pvr2_encoder_vcmd(struct pvr2_hdw *hdw, int cmd,
+			     int args, ...)
+{
+	va_list vl;
+	unsigned int idx;
+	u32 data[12];
+
+	if (args > ARRAY_SIZE(data)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Failed to write cx23416 command - too many arguments (was given %u limit %lu)",
+			args, (long unsigned) ARRAY_SIZE(data));
+		return -EINVAL;
+	}
+
+	va_start(vl, args);
+	for (idx = 0; idx < args; idx++) {
+		data[idx] = va_arg(vl, u32);
+	}
+	va_end(vl);
+
+	return pvr2_encoder_cmd(hdw,cmd,args,0,data);
+}
+
+
+/* This implements some extra setup for the encoder that seems to be
+   specific to the PVR USB2 hardware. */
+static int pvr2_encoder_prep_config(struct pvr2_hdw *hdw)
+{
+	int ret = 0;
+	int encMisc3Arg = 0;
+
+#if 0
+	/* This inexplicable bit happens in the Hauppauge windows
+	   driver (for both 24xxx and 29xxx devices).  However I
+	   currently see no difference in behavior with or without
+	   this stuff.  Leave this here as a note of its existence,
+	   but don't use it. */
+	LOCK_TAKE(hdw->ctl_lock); do {
+		u32 dat[1];
+		dat[0] = 0x80000640;
+		pvr2_encoder_write_words(hdw,0x01fe,dat,1);
+		pvr2_encoder_write_words(hdw,0x023e,dat,1);
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+#endif
+
+	/* Mike Isely <isely@pobox.com> 26-Jan-2006 The windows driver
+	   sends the following list of ENC_MISC commands (for both
+	   24xxx and 29xxx devices).  Meanings are not entirely clear,
+	   however without the ENC_MISC(3,1) command then we risk
+	   random perpetual video corruption whenever the video input
+	   breaks up for a moment (like when switching channels). */
+
+
+#if 0
+	/* This ENC_MISC(5,0) command seems to hurt 29xxx sync
+	   performance on channel changes, but is not a problem on
+	   24xxx devices. */
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 5,0,0,0);
+#endif
+
+	/* This ENC_MISC(3,encMisc3Arg) command is critical - without
+	   it there will eventually be video corruption.  Also, the
+	   saa7115 case is strange - the Windows driver is passing 1
+	   regardless of device type but if we have 1 for saa7115
+	   devices the video turns sluggish.  */
+	if (hdw->hdw_desc->flag_has_cx25840) {
+		encMisc3Arg = 1;
+	} else {
+		encMisc3Arg = 0;
+	}
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 3,
+				 encMisc3Arg,0,0);
+
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 8,0,0,0);
+
+#if 0
+	/* This ENC_MISC(4,1) command is poisonous, so it is commented
+	   out.  But I'm leaving it here anyway to document its
+	   existence in the Windows driver.  The effect of this
+	   command is that apps displaying the stream become sluggish
+	   with stuttering video. */
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 4,1,0,0);
+#endif
+
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 0,3,0,0);
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4,15,0,0,0);
+
+	/* prevent the PTSs from slowly drifting away in the generated
+	   MPEG stream */
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC, 2, 4, 1);
+
+	return ret;
+}
+
+int pvr2_encoder_adjust(struct pvr2_hdw *hdw)
+{
+	int ret;
+	ret = cx2341x_update(hdw,pvr2_encoder_cmd,
+			     (hdw->enc_cur_valid ? &hdw->enc_cur_state : NULL),
+			     &hdw->enc_ctl_state);
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Error from cx2341x module code=%d",ret);
+	} else {
+		hdw->enc_cur_state = hdw->enc_ctl_state;
+		hdw->enc_cur_valid = !0;
+	}
+	return ret;
+}
+
+
+int pvr2_encoder_configure(struct pvr2_hdw *hdw)
+{
+	int ret;
+	int val;
+	pvr2_trace(PVR2_TRACE_ENCODER, "pvr2_encoder_configure (cx2341x module)");
+	hdw->enc_ctl_state.port = CX2341X_PORT_STREAMING;
+	hdw->enc_ctl_state.width = hdw->res_hor_val;
+	hdw->enc_ctl_state.height = hdw->res_ver_val;
+	hdw->enc_ctl_state.is_50hz = ((hdw->std_mask_cur & V4L2_STD_525_60) ?
+				      0 : 1);
+
+	ret = 0;
+
+	ret |= pvr2_encoder_prep_config(hdw);
+
+	/* saa7115: 0xf0 */
+	val = 0xf0;
+	if (hdw->hdw_desc->flag_has_cx25840) {
+		/* ivtv cx25840: 0x140 */
+		val = 0x140;
+	}
+
+	if (!ret) ret = pvr2_encoder_vcmd(
+		hdw,CX2341X_ENC_SET_NUM_VSYNC_LINES, 2,
+		val, val);
+
+	/* setup firmware to notify us about some events (don't know why...) */
+	if (!ret) ret = pvr2_encoder_vcmd(
+		hdw,CX2341X_ENC_SET_EVENT_NOTIFICATION, 4,
+		0, 0, 0x10000000, 0xffffffff);
+
+	if (!ret) ret = pvr2_encoder_vcmd(
+		hdw,CX2341X_ENC_SET_VBI_LINE, 5,
+		0xffffffff,0,0,0,0);
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to configure cx23416");
+		return ret;
+	}
+
+	ret = pvr2_encoder_adjust(hdw);
+	if (ret) return ret;
+
+	ret = pvr2_encoder_vcmd(
+		hdw, CX2341X_ENC_INITIALIZE_INPUT, 0);
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to initialize cx23416 video input");
+		return ret;
+	}
+
+	return 0;
+}
+
+
+int pvr2_encoder_start(struct pvr2_hdw *hdw)
+{
+	int status;
+
+	/* unmask some interrupts */
+	pvr2_write_register(hdw, 0x0048, 0xbfffffff);
+
+	pvr2_encoder_vcmd(hdw,CX2341X_ENC_MUTE_VIDEO,1,
+			  hdw->input_val == PVR2_CVAL_INPUT_RADIO ? 1 : 0);
+
+	switch (hdw->active_stream_type) {
+	case pvr2_config_vbi:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+					   0x01,0x14);
+		break;
+	case pvr2_config_mpeg:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+					   0,0x13);
+		break;
+	default: /* Unhandled cases for now */
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+					   0,0x13);
+		break;
+	}
+	return status;
+}
+
+int pvr2_encoder_stop(struct pvr2_hdw *hdw)
+{
+	int status;
+
+	/* mask all interrupts */
+	pvr2_write_register(hdw, 0x0048, 0xffffffff);
+
+	switch (hdw->active_stream_type) {
+	case pvr2_config_vbi:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+					   0x01,0x01,0x14);
+		break;
+	case pvr2_config_mpeg:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+					   0x01,0,0x13);
+		break;
+	default: /* Unhandled cases for now */
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+					   0x01,0,0x13);
+		break;
+	}
+
+	return status;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-encoder.h b/drivers/media/usb/pvrusb2/pvrusb2-encoder.h
new file mode 100644
index 0000000..10d7f0b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-encoder.h
@@ -0,0 +1,28 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_ENCODER_H
+#define __PVRUSB2_ENCODER_H
+
+struct pvr2_hdw;
+
+int pvr2_encoder_adjust(struct pvr2_hdw *);
+int pvr2_encoder_configure(struct pvr2_hdw *);
+int pvr2_encoder_start(struct pvr2_hdw *);
+int pvr2_encoder_stop(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_ENCODER_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h b/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h
new file mode 100644
index 0000000..0a01de4
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h
@@ -0,0 +1,58 @@
+/*
+ *
+ *
+ *  Copyright (C) 2007 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#ifndef _PVRUSB2_FX2_CMD_H_
+#define _PVRUSB2_FX2_CMD_H_
+
+#define FX2CMD_MEM_WRITE_DWORD  0x01u
+#define FX2CMD_MEM_READ_DWORD   0x02u
+
+#define FX2CMD_HCW_ZILOG_RESET  0x10u /* 1=reset 0=release */
+
+#define FX2CMD_MEM_READ_64BYTES 0x28u
+
+#define FX2CMD_REG_WRITE        0x04u
+#define FX2CMD_REG_READ         0x05u
+#define FX2CMD_MEMSEL           0x06u
+
+#define FX2CMD_I2C_WRITE        0x08u
+#define FX2CMD_I2C_READ         0x09u
+
+#define FX2CMD_GET_USB_SPEED    0x0bu
+
+#define FX2CMD_STREAMING_ON     0x36u
+#define FX2CMD_STREAMING_OFF    0x37u
+
+#define FX2CMD_FWPOST1          0x52u
+
+#define FX2CMD_POWER_OFF        0xdcu
+#define FX2CMD_POWER_ON         0xdeu
+
+#define FX2CMD_DEEP_RESET       0xddu
+
+#define FX2CMD_GET_EEPROM_ADDR  0xebu
+#define FX2CMD_GET_IR_CODE      0xecu
+
+#define FX2CMD_HCW_DEMOD_RESETIN       0xf0u
+#define FX2CMD_HCW_DTV_STREAMING_ON    0xf1u
+#define FX2CMD_HCW_DTV_STREAMING_OFF   0xf2u
+
+#define FX2CMD_ONAIR_DTV_STREAMING_ON  0xa0u
+#define FX2CMD_ONAIR_DTV_STREAMING_OFF 0xa1u
+#define FX2CMD_ONAIR_DTV_POWER_ON      0xa2u
+#define FX2CMD_ONAIR_DTV_POWER_OFF     0xa3u
+
+#endif /* _PVRUSB2_FX2_CMD_H_ */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h
new file mode 100644
index 0000000..7a82419
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h
@@ -0,0 +1,391 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_HDW_INTERNAL_H
+#define __PVRUSB2_HDW_INTERNAL_H
+
+/*
+
+  This header sets up all the internal structures and definitions needed to
+  track and coordinate the driver's interaction with the hardware.  ONLY
+  source files which actually implement part of that whole circus should be
+  including this header.  Higher levels, like the external layers to the
+  various public APIs (V4L, sysfs, etc) should NOT ever include this
+  private, internal header.  This means that pvrusb2-hdw, pvrusb2-encoder,
+  etc will include this, but pvrusb2-v4l should not.
+
+*/
+
+#include <linux/videodev2.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-io.h"
+#include <media/v4l2-device.h>
+#include <media/drv-intf/cx2341x.h>
+#include <media/i2c/ir-kbd-i2c.h>
+#include "pvrusb2-devattr.h"
+
+/* Legal values for PVR2_CID_HSM */
+#define PVR2_CVAL_HSM_FAIL 0
+#define PVR2_CVAL_HSM_FULL 1
+#define PVR2_CVAL_HSM_HIGH 2
+
+#define PVR2_VID_ENDPOINT        0x84
+#define PVR2_UNK_ENDPOINT        0x86    /* maybe raw yuv ? */
+#define PVR2_VBI_ENDPOINT        0x88
+
+#define PVR2_CTL_BUFFSIZE        64
+
+#define FREQTABLE_SIZE 500
+
+#define LOCK_TAKE(x) do { mutex_lock(&x##_mutex); x##_held = !0; } while (0)
+#define LOCK_GIVE(x) do { x##_held = 0; mutex_unlock(&x##_mutex); } while (0)
+
+typedef int (*pvr2_ctlf_is_dirty)(struct pvr2_ctrl *);
+typedef void (*pvr2_ctlf_clear_dirty)(struct pvr2_ctrl *);
+typedef int (*pvr2_ctlf_check_value)(struct pvr2_ctrl *,int);
+typedef int (*pvr2_ctlf_get_value)(struct pvr2_ctrl *,int *);
+typedef int (*pvr2_ctlf_set_value)(struct pvr2_ctrl *,int msk,int val);
+typedef int (*pvr2_ctlf_val_to_sym)(struct pvr2_ctrl *,int msk,int val,
+				    char *,unsigned int,unsigned int *);
+typedef int (*pvr2_ctlf_sym_to_val)(struct pvr2_ctrl *,
+				    const char *,unsigned int,
+				    int *mskp,int *valp);
+typedef unsigned int (*pvr2_ctlf_get_v4lflags)(struct pvr2_ctrl *);
+
+/* This structure describes a specific control.  A table of these is set up
+   in pvrusb2-hdw.c. */
+struct pvr2_ctl_info {
+	/* Control's name suitable for use as an identifier */
+	const char *name;
+
+	/* Short description of control */
+	const char *desc;
+
+	/* Control's implementation */
+	pvr2_ctlf_get_value get_value;      /* Get its value */
+	pvr2_ctlf_get_value get_def_value;  /* Get its default value */
+	pvr2_ctlf_get_value get_min_value;  /* Get minimum allowed value */
+	pvr2_ctlf_get_value get_max_value;  /* Get maximum allowed value */
+	pvr2_ctlf_set_value set_value;      /* Set its value */
+	pvr2_ctlf_check_value check_value;  /* Check that value is valid */
+	pvr2_ctlf_val_to_sym val_to_sym;    /* Custom convert value->symbol */
+	pvr2_ctlf_sym_to_val sym_to_val;    /* Custom convert symbol->value */
+	pvr2_ctlf_is_dirty is_dirty;        /* Return true if dirty */
+	pvr2_ctlf_clear_dirty clear_dirty;  /* Clear dirty state */
+	pvr2_ctlf_get_v4lflags get_v4lflags;/* Retrieve v4l flags */
+
+	/* Control's type (int, enum, bitmask) */
+	enum pvr2_ctl_type type;
+
+	/* Associated V4L control ID, if any */
+	int v4l_id;
+
+	/* Associated driver internal ID, if any */
+	int internal_id;
+
+	/* Don't implicitly initialize this control's value */
+	int skip_init;
+
+	/* Starting value for this control */
+	int default_value;
+
+	/* Type-specific control information */
+	union {
+		struct { /* Integer control */
+			long min_value; /* lower limit */
+			long max_value; /* upper limit */
+		} type_int;
+		struct { /* enumerated control */
+			unsigned int count;       /* enum value count */
+			const char * const *value_names; /* symbol names */
+		} type_enum;
+		struct { /* bitmask control */
+			unsigned int valid_bits; /* bits in use */
+			const char **bit_names;  /* symbol name/bit */
+		} type_bitmask;
+	} def;
+};
+
+
+/* Same as pvr2_ctl_info, but includes storage for the control description */
+#define PVR2_CTLD_INFO_DESC_SIZE 32
+struct pvr2_ctld_info {
+	struct pvr2_ctl_info info;
+	char desc[PVR2_CTLD_INFO_DESC_SIZE];
+};
+
+struct pvr2_ctrl {
+	const struct pvr2_ctl_info *info;
+	struct pvr2_hdw *hdw;
+};
+
+
+
+/* Disposition of firmware1 loading situation */
+#define FW1_STATE_UNKNOWN 0
+#define FW1_STATE_MISSING 1
+#define FW1_STATE_FAILED 2
+#define FW1_STATE_RELOAD 3
+#define FW1_STATE_OK 4
+
+/* What state the device is in if it is a hybrid */
+#define PVR2_PATHWAY_UNKNOWN 0
+#define PVR2_PATHWAY_ANALOG 1
+#define PVR2_PATHWAY_DIGITAL 2
+
+typedef int (*pvr2_i2c_func)(struct pvr2_hdw *,u8,u8 *,u16,u8 *, u16);
+#define PVR2_I2C_FUNC_CNT 128
+
+/* This structure contains all state data directly needed to
+   manipulate the hardware (as opposed to complying with a kernel
+   interface) */
+struct pvr2_hdw {
+	/* Underlying USB device handle */
+	struct usb_device *usb_dev;
+	struct usb_interface *usb_intf;
+
+	/* Our handle into the v4l2 sub-device architecture */
+	struct v4l2_device v4l2_dev;
+	/* Device description, anything that must adjust behavior based on
+	   device specific info will use information held here. */
+	const struct pvr2_device_desc *hdw_desc;
+
+	/* Kernel worker thread handling */
+	struct work_struct workpoll;     /* Update driver state */
+
+	/* Video spigot */
+	struct pvr2_stream *vid_stream;
+
+	/* Mutex for all hardware state control */
+	struct mutex big_lock_mutex;
+	int big_lock_held;  /* For debugging */
+
+	/* This is a simple string which identifies the instance of this
+	   driver.  It is unique within the set of existing devices, but
+	   there is no attempt to keep the name consistent with the same
+	   physical device each time. */
+	char name[32];
+
+	/* This is a simple string which identifies the physical device
+	   instance itself - if possible.  (If not possible, then it is
+	   based on the specific driver instance, similar to name above.)
+	   The idea here is that userspace might hopefully be able to use
+	   this recognize specific tuners.  It will encode a serial number,
+	   if available. */
+	char identifier[32];
+
+	/* I2C stuff */
+	struct i2c_adapter i2c_adap;
+	struct i2c_algorithm i2c_algo;
+	pvr2_i2c_func i2c_func[PVR2_I2C_FUNC_CNT];
+	int i2c_cx25840_hack_state;
+	int i2c_linked;
+
+	/* IR related */
+	unsigned int ir_scheme_active; /* IR scheme as seen from the outside */
+	struct IR_i2c_init_data ir_init_data; /* params passed to IR modules */
+
+	/* Frequency table */
+	unsigned int freqTable[FREQTABLE_SIZE];
+	unsigned int freqProgSlot;
+
+	/* Stuff for handling low level control interaction with device */
+	struct mutex ctl_lock_mutex;
+	int ctl_lock_held;  /* For debugging */
+	struct urb *ctl_write_urb;
+	struct urb *ctl_read_urb;
+	unsigned char *ctl_write_buffer;
+	unsigned char *ctl_read_buffer;
+	int ctl_write_pend_flag;
+	int ctl_read_pend_flag;
+	int ctl_timeout_flag;
+	struct completion ctl_done;
+	unsigned char cmd_buffer[PVR2_CTL_BUFFSIZE];
+	int cmd_debug_state;               // Low level command debugging info
+	unsigned char cmd_debug_code;      //
+	unsigned int cmd_debug_write_len;  //
+	unsigned int cmd_debug_read_len;   //
+
+	/* Bits of state that describe what is going on with various parts
+	   of the driver. */
+	int state_pathway_ok;         /* Pathway config is ok */
+	int state_encoder_ok;         /* Encoder is operational */
+	int state_encoder_run;        /* Encoder is running */
+	int state_encoder_config;     /* Encoder is configured */
+	int state_encoder_waitok;     /* Encoder pre-wait done */
+	int state_encoder_runok;      /* Encoder has run for >= .25 sec */
+	int state_decoder_run;        /* Decoder is running */
+	int state_decoder_ready;      /* Decoder is stabilized & streamable */
+	int state_usbstream_run;      /* FX2 is streaming */
+	int state_decoder_quiescent;  /* Decoder idle for minimal interval */
+	int state_pipeline_config;    /* Pipeline is configured */
+	int state_pipeline_req;       /* Somebody wants to stream */
+	int state_pipeline_pause;     /* Pipeline must be paused */
+	int state_pipeline_idle;      /* Pipeline not running */
+
+	/* This is the master state of the driver.  It is the combined
+	   result of other bits of state.  Examining this will indicate the
+	   overall state of the driver.  Values here are one of
+	   PVR2_STATE_xxxx */
+	unsigned int master_state;
+
+	/* True if device led is currently on */
+	int led_on;
+
+	/* True if states must be re-evaluated */
+	int state_stale;
+
+	void (*state_func)(void *);
+	void *state_data;
+
+	/* Timer for measuring required decoder settling time before we're
+	   allowed to fire it up again. */
+	struct timer_list quiescent_timer;
+
+	/* Timer for measuring decoder stabilization time, which is the
+	   amount of time we need to let the decoder run before we can
+	   trust its output (otherwise the encoder might see garbage and
+	   then fail to start correctly). */
+	struct timer_list decoder_stabilization_timer;
+
+	/* Timer for measuring encoder pre-wait time */
+	struct timer_list encoder_wait_timer;
+
+	/* Timer for measuring encoder minimum run time */
+	struct timer_list encoder_run_timer;
+
+	/* Place to block while waiting for state changes */
+	wait_queue_head_t state_wait_data;
+
+
+	int force_dirty;        /* consider all controls dirty if true */
+	int flag_ok;            /* device in known good state */
+	int flag_modulefail;    /* true if at least one module failed to load */
+	int flag_disconnected;  /* flag_ok == 0 due to disconnect */
+	int flag_init_ok;       /* true if structure is fully initialized */
+	int fw1_state;          /* current situation with fw1 */
+	int pathway_state;      /* one of PVR2_PATHWAY_xxx */
+	int flag_decoder_missed;/* We've noticed missing decoder */
+	int flag_tripped;       /* Indicates overall failure to start */
+
+	unsigned int decoder_client_id;
+
+	// CPU firmware info (used to help find / save firmware data)
+	char *fw_buffer;
+	unsigned int fw_size;
+	int fw_cpu_flag; /* True if we are dealing with the CPU */
+
+	/* Tuner / frequency control stuff */
+	unsigned int tuner_type;
+	int tuner_updated;
+	unsigned int freqValTelevision;  /* Current freq for tv mode */
+	unsigned int freqValRadio;       /* Current freq for radio mode */
+	unsigned int freqSlotTelevision; /* Current slot for tv mode */
+	unsigned int freqSlotRadio;      /* Current slot for radio mode */
+	unsigned int freqSelector;       /* 0=radio 1=television */
+	int freqDirty;
+
+	/* Current tuner info - this information is polled from the I2C bus */
+	struct v4l2_tuner tuner_signal_info;
+	int tuner_signal_stale;
+
+	/* Cropping capability info */
+	struct v4l2_cropcap cropcap_info;
+	int cropcap_stale;
+
+	/* Video standard handling */
+	v4l2_std_id std_mask_eeprom; // Hardware supported selections
+	v4l2_std_id std_mask_avail;  // Which standards we may select from
+	v4l2_std_id std_mask_cur;    // Currently selected standard(s)
+	int std_enum_cur;            // selected standard enumeration value
+	int std_dirty;               // True if std_mask_cur has changed
+	struct pvr2_ctl_info std_info_enum;
+	struct pvr2_ctl_info std_info_avail;
+	struct pvr2_ctl_info std_info_cur;
+	struct pvr2_ctl_info std_info_detect;
+
+	// Generated string names, one per actual V4L2 standard
+	const char *std_mask_ptrs[32];
+	char std_mask_names[32][16];
+
+	int unit_number;             /* ID for driver instance */
+	unsigned long serial_number; /* ID for hardware itself */
+
+	char bus_info[32]; /* Bus location info */
+
+	/* Minor numbers used by v4l logic (yes, this is a hack, as there
+	   should be no v4l junk here).  Probably a better way to do this. */
+	int v4l_minor_number_video;
+	int v4l_minor_number_vbi;
+	int v4l_minor_number_radio;
+
+	/* Bit mask of PVR2_CVAL_INPUT choices which are valid for the hardware */
+	unsigned int input_avail_mask;
+	/* Bit mask of PVR2_CVAL_INPUT choices which are currently allowed */
+	unsigned int input_allowed_mask;
+
+	/* Location of eeprom or a negative number if none */
+	int eeprom_addr;
+
+	enum pvr2_config active_stream_type;
+	enum pvr2_config desired_stream_type;
+
+	/* Control state needed for cx2341x module */
+	struct cx2341x_mpeg_params enc_cur_state;
+	struct cx2341x_mpeg_params enc_ctl_state;
+	/* True if an encoder attribute has changed */
+	int enc_stale;
+	/* True if an unsafe encoder attribute has changed */
+	int enc_unsafe_stale;
+	/* True if enc_cur_state is valid */
+	int enc_cur_valid;
+
+	/* Control state */
+#define VCREATE_DATA(lab) int lab##_val; int lab##_dirty
+	VCREATE_DATA(brightness);
+	VCREATE_DATA(contrast);
+	VCREATE_DATA(saturation);
+	VCREATE_DATA(hue);
+	VCREATE_DATA(volume);
+	VCREATE_DATA(balance);
+	VCREATE_DATA(bass);
+	VCREATE_DATA(treble);
+	VCREATE_DATA(mute);
+	VCREATE_DATA(cropl);
+	VCREATE_DATA(cropt);
+	VCREATE_DATA(cropw);
+	VCREATE_DATA(croph);
+	VCREATE_DATA(input);
+	VCREATE_DATA(audiomode);
+	VCREATE_DATA(res_hor);
+	VCREATE_DATA(res_ver);
+	VCREATE_DATA(srate);
+#undef VCREATE_DATA
+
+	struct pvr2_ctld_info *mpeg_ctrl_info;
+
+	struct pvr2_ctrl *controls;
+	unsigned int control_cnt;
+};
+
+/* This function gets the current frequency */
+unsigned long pvr2_hdw_get_cur_freq(struct pvr2_hdw *);
+
+void pvr2_hdw_status_poll(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c
new file mode 100644
index 0000000..a8519da
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c
@@ -0,0 +1,5114 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+#include "pvrusb2.h"
+#include "pvrusb2-std.h"
+#include "pvrusb2-util.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-i2c-core.h"
+#include "pvrusb2-eeprom.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-encoder.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-fx2-cmd.h"
+#include "pvrusb2-wm8775.h"
+#include "pvrusb2-video-v4l.h"
+#include "pvrusb2-cx2584x-v4l.h"
+#include "pvrusb2-cs53l32a.h"
+#include "pvrusb2-audio.h"
+
+#define TV_MIN_FREQ     55250000L
+#define TV_MAX_FREQ    850000000L
+
+/* This defines a minimum interval that the decoder must remain quiet
+   before we are allowed to start it running. */
+#define TIME_MSEC_DECODER_WAIT 50
+
+/* This defines a minimum interval that the decoder must be allowed to run
+   before we can safely begin using its streaming output. */
+#define TIME_MSEC_DECODER_STABILIZATION_WAIT 300
+
+/* This defines a minimum interval that the encoder must remain quiet
+   before we are allowed to configure it. */
+#define TIME_MSEC_ENCODER_WAIT 50
+
+/* This defines the minimum interval that the encoder must successfully run
+   before we consider that the encoder has run at least once since its
+   firmware has been loaded.  This measurement is in important for cases
+   where we can't do something until we know that the encoder has been run
+   at least once. */
+#define TIME_MSEC_ENCODER_OK 250
+
+static struct pvr2_hdw *unit_pointers[PVR_NUM] = {[ 0 ... PVR_NUM-1 ] = NULL};
+static DEFINE_MUTEX(pvr2_unit_mtx);
+
+static int ctlchg;
+static int procreload;
+static int tuner[PVR_NUM] = { [0 ... PVR_NUM-1] = -1 };
+static int tolerance[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 };
+static int video_std[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 };
+static int init_pause_msec;
+
+module_param(ctlchg, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(ctlchg, "0=optimize ctl change 1=always accept new ctl value");
+module_param(init_pause_msec, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(init_pause_msec, "hardware initialization settling delay");
+module_param(procreload, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(procreload,
+		 "Attempt init failure recovery with firmware reload");
+module_param_array(tuner,    int, NULL, 0444);
+MODULE_PARM_DESC(tuner,"specify installed tuner type");
+module_param_array(video_std,    int, NULL, 0444);
+MODULE_PARM_DESC(video_std,"specify initial video standard");
+module_param_array(tolerance,    int, NULL, 0444);
+MODULE_PARM_DESC(tolerance,"specify stream error tolerance");
+
+/* US Broadcast channel 3 (61.25 MHz), to help with testing */
+static int default_tv_freq    = 61250000L;
+/* 104.3 MHz, a usable FM station for my area */
+static int default_radio_freq = 104300000L;
+
+module_param_named(tv_freq, default_tv_freq, int, 0444);
+MODULE_PARM_DESC(tv_freq, "specify initial television frequency");
+module_param_named(radio_freq, default_radio_freq, int, 0444);
+MODULE_PARM_DESC(radio_freq, "specify initial radio frequency");
+
+#define PVR2_CTL_WRITE_ENDPOINT  0x01
+#define PVR2_CTL_READ_ENDPOINT   0x81
+
+#define PVR2_GPIO_IN 0x9008
+#define PVR2_GPIO_OUT 0x900c
+#define PVR2_GPIO_DIR 0x9020
+
+#define trace_firmware(...) pvr2_trace(PVR2_TRACE_FIRMWARE,__VA_ARGS__)
+
+#define PVR2_FIRMWARE_ENDPOINT   0x02
+
+/* size of a firmware chunk */
+#define FIRMWARE_CHUNK_SIZE 0x2000
+
+typedef void (*pvr2_subdev_update_func)(struct pvr2_hdw *,
+					struct v4l2_subdev *);
+
+static const pvr2_subdev_update_func pvr2_module_update_functions[] = {
+	[PVR2_CLIENT_ID_WM8775] = pvr2_wm8775_subdev_update,
+	[PVR2_CLIENT_ID_SAA7115] = pvr2_saa7115_subdev_update,
+	[PVR2_CLIENT_ID_MSP3400] = pvr2_msp3400_subdev_update,
+	[PVR2_CLIENT_ID_CX25840] = pvr2_cx25840_subdev_update,
+	[PVR2_CLIENT_ID_CS53L32A] = pvr2_cs53l32a_subdev_update,
+};
+
+static const char *module_names[] = {
+	[PVR2_CLIENT_ID_MSP3400] = "msp3400",
+	[PVR2_CLIENT_ID_CX25840] = "cx25840",
+	[PVR2_CLIENT_ID_SAA7115] = "saa7115",
+	[PVR2_CLIENT_ID_TUNER] = "tuner",
+	[PVR2_CLIENT_ID_DEMOD] = "tuner",
+	[PVR2_CLIENT_ID_CS53L32A] = "cs53l32a",
+	[PVR2_CLIENT_ID_WM8775] = "wm8775",
+};
+
+
+static const unsigned char *module_i2c_addresses[] = {
+	[PVR2_CLIENT_ID_TUNER] = "\x60\x61\x62\x63",
+	[PVR2_CLIENT_ID_DEMOD] = "\x43",
+	[PVR2_CLIENT_ID_MSP3400] = "\x40",
+	[PVR2_CLIENT_ID_SAA7115] = "\x21",
+	[PVR2_CLIENT_ID_WM8775] = "\x1b",
+	[PVR2_CLIENT_ID_CX25840] = "\x44",
+	[PVR2_CLIENT_ID_CS53L32A] = "\x11",
+};
+
+
+static const char *ir_scheme_names[] = {
+	[PVR2_IR_SCHEME_NONE] = "none",
+	[PVR2_IR_SCHEME_29XXX] = "29xxx",
+	[PVR2_IR_SCHEME_24XXX] = "24xxx (29xxx emulation)",
+	[PVR2_IR_SCHEME_24XXX_MCE] = "24xxx (MCE device)",
+	[PVR2_IR_SCHEME_ZILOG] = "Zilog",
+};
+
+
+/* Define the list of additional controls we'll dynamically construct based
+   on query of the cx2341x module. */
+struct pvr2_mpeg_ids {
+	const char *strid;
+	int id;
+};
+static const struct pvr2_mpeg_ids mpeg_ids[] = {
+	{
+		.strid = "audio_layer",
+		.id = V4L2_CID_MPEG_AUDIO_ENCODING,
+	},{
+		.strid = "audio_bitrate",
+		.id = V4L2_CID_MPEG_AUDIO_L2_BITRATE,
+	},{
+		/* Already using audio_mode elsewhere :-( */
+		.strid = "mpeg_audio_mode",
+		.id = V4L2_CID_MPEG_AUDIO_MODE,
+	},{
+		.strid = "mpeg_audio_mode_extension",
+		.id = V4L2_CID_MPEG_AUDIO_MODE_EXTENSION,
+	},{
+		.strid = "audio_emphasis",
+		.id = V4L2_CID_MPEG_AUDIO_EMPHASIS,
+	},{
+		.strid = "audio_crc",
+		.id = V4L2_CID_MPEG_AUDIO_CRC,
+	},{
+		.strid = "video_aspect",
+		.id = V4L2_CID_MPEG_VIDEO_ASPECT,
+	},{
+		.strid = "video_b_frames",
+		.id = V4L2_CID_MPEG_VIDEO_B_FRAMES,
+	},{
+		.strid = "video_gop_size",
+		.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+	},{
+		.strid = "video_gop_closure",
+		.id = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE,
+	},{
+		.strid = "video_bitrate_mode",
+		.id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+	},{
+		.strid = "video_bitrate",
+		.id = V4L2_CID_MPEG_VIDEO_BITRATE,
+	},{
+		.strid = "video_bitrate_peak",
+		.id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+	},{
+		.strid = "video_temporal_decimation",
+		.id = V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION,
+	},{
+		.strid = "stream_type",
+		.id = V4L2_CID_MPEG_STREAM_TYPE,
+	},{
+		.strid = "video_spatial_filter_mode",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE,
+	},{
+		.strid = "video_spatial_filter",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER,
+	},{
+		.strid = "video_luma_spatial_filter_type",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE,
+	},{
+		.strid = "video_chroma_spatial_filter_type",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE,
+	},{
+		.strid = "video_temporal_filter_mode",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE,
+	},{
+		.strid = "video_temporal_filter",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER,
+	},{
+		.strid = "video_median_filter_type",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE,
+	},{
+		.strid = "video_luma_median_filter_top",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP,
+	},{
+		.strid = "video_luma_median_filter_bottom",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM,
+	},{
+		.strid = "video_chroma_median_filter_top",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP,
+	},{
+		.strid = "video_chroma_median_filter_bottom",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM,
+	}
+};
+#define MPEGDEF_COUNT ARRAY_SIZE(mpeg_ids)
+
+
+static const char *control_values_srate[] = {
+	[V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100]   = "44.1 kHz",
+	[V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000]   = "48 kHz",
+	[V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000]   = "32 kHz",
+};
+
+
+
+static const char *control_values_input[] = {
+	[PVR2_CVAL_INPUT_TV]        = "television",  /*xawtv needs this name*/
+	[PVR2_CVAL_INPUT_DTV]       = "dtv",
+	[PVR2_CVAL_INPUT_RADIO]     = "radio",
+	[PVR2_CVAL_INPUT_SVIDEO]    = "s-video",
+	[PVR2_CVAL_INPUT_COMPOSITE] = "composite",
+};
+
+
+static const char *control_values_audiomode[] = {
+	[V4L2_TUNER_MODE_MONO]   = "Mono",
+	[V4L2_TUNER_MODE_STEREO] = "Stereo",
+	[V4L2_TUNER_MODE_LANG1]  = "Lang1",
+	[V4L2_TUNER_MODE_LANG2]  = "Lang2",
+	[V4L2_TUNER_MODE_LANG1_LANG2] = "Lang1+Lang2",
+};
+
+
+static const char *control_values_hsm[] = {
+	[PVR2_CVAL_HSM_FAIL] = "Fail",
+	[PVR2_CVAL_HSM_HIGH] = "High",
+	[PVR2_CVAL_HSM_FULL] = "Full",
+};
+
+
+static const char *pvr2_state_names[] = {
+	[PVR2_STATE_NONE] =    "none",
+	[PVR2_STATE_DEAD] =    "dead",
+	[PVR2_STATE_COLD] =    "cold",
+	[PVR2_STATE_WARM] =    "warm",
+	[PVR2_STATE_ERROR] =   "error",
+	[PVR2_STATE_READY] =   "ready",
+	[PVR2_STATE_RUN] =     "run",
+};
+
+
+struct pvr2_fx2cmd_descdef {
+	unsigned char id;
+	unsigned char *desc;
+};
+
+static const struct pvr2_fx2cmd_descdef pvr2_fx2cmd_desc[] = {
+	{FX2CMD_MEM_WRITE_DWORD, "write encoder dword"},
+	{FX2CMD_MEM_READ_DWORD, "read encoder dword"},
+	{FX2CMD_HCW_ZILOG_RESET, "zilog IR reset control"},
+	{FX2CMD_MEM_READ_64BYTES, "read encoder 64bytes"},
+	{FX2CMD_REG_WRITE, "write encoder register"},
+	{FX2CMD_REG_READ, "read encoder register"},
+	{FX2CMD_MEMSEL, "encoder memsel"},
+	{FX2CMD_I2C_WRITE, "i2c write"},
+	{FX2CMD_I2C_READ, "i2c read"},
+	{FX2CMD_GET_USB_SPEED, "get USB speed"},
+	{FX2CMD_STREAMING_ON, "stream on"},
+	{FX2CMD_STREAMING_OFF, "stream off"},
+	{FX2CMD_FWPOST1, "fwpost1"},
+	{FX2CMD_POWER_OFF, "power off"},
+	{FX2CMD_POWER_ON, "power on"},
+	{FX2CMD_DEEP_RESET, "deep reset"},
+	{FX2CMD_GET_EEPROM_ADDR, "get rom addr"},
+	{FX2CMD_GET_IR_CODE, "get IR code"},
+	{FX2CMD_HCW_DEMOD_RESETIN, "hcw demod resetin"},
+	{FX2CMD_HCW_DTV_STREAMING_ON, "hcw dtv stream on"},
+	{FX2CMD_HCW_DTV_STREAMING_OFF, "hcw dtv stream off"},
+	{FX2CMD_ONAIR_DTV_STREAMING_ON, "onair dtv stream on"},
+	{FX2CMD_ONAIR_DTV_STREAMING_OFF, "onair dtv stream off"},
+	{FX2CMD_ONAIR_DTV_POWER_ON, "onair dtv power on"},
+	{FX2CMD_ONAIR_DTV_POWER_OFF, "onair dtv power off"},
+};
+
+
+static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v);
+static void pvr2_hdw_state_sched(struct pvr2_hdw *);
+static int pvr2_hdw_state_eval(struct pvr2_hdw *);
+static void pvr2_hdw_set_cur_freq(struct pvr2_hdw *,unsigned long);
+static void pvr2_hdw_worker_poll(struct work_struct *work);
+static int pvr2_hdw_wait(struct pvr2_hdw *,int state);
+static int pvr2_hdw_untrip_unlocked(struct pvr2_hdw *);
+static void pvr2_hdw_state_log_state(struct pvr2_hdw *);
+static int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl);
+static int pvr2_hdw_commit_setup(struct pvr2_hdw *hdw);
+static int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw);
+static void pvr2_hdw_quiescent_timeout(struct timer_list *);
+static void pvr2_hdw_decoder_stabilization_timeout(struct timer_list *);
+static void pvr2_hdw_encoder_wait_timeout(struct timer_list *);
+static void pvr2_hdw_encoder_run_timeout(struct timer_list *);
+static int pvr2_issue_simple_cmd(struct pvr2_hdw *,u32);
+static int pvr2_send_request_ex(struct pvr2_hdw *hdw,
+				unsigned int timeout,int probe_fl,
+				void *write_data,unsigned int write_len,
+				void *read_data,unsigned int read_len);
+static int pvr2_hdw_check_cropcap(struct pvr2_hdw *hdw);
+static v4l2_std_id pvr2_hdw_get_detected_std(struct pvr2_hdw *hdw);
+
+static void trace_stbit(const char *name,int val)
+{
+	pvr2_trace(PVR2_TRACE_STBITS,
+		   "State bit %s <-- %s",
+		   name,(val ? "true" : "false"));
+}
+
+static int ctrl_channelfreq_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if ((hdw->freqProgSlot > 0) && (hdw->freqProgSlot <= FREQTABLE_SIZE)) {
+		*vp = hdw->freqTable[hdw->freqProgSlot-1];
+	} else {
+		*vp = 0;
+	}
+	return 0;
+}
+
+static int ctrl_channelfreq_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	unsigned int slotId = hdw->freqProgSlot;
+	if ((slotId > 0) && (slotId <= FREQTABLE_SIZE)) {
+		hdw->freqTable[slotId-1] = v;
+		/* Handle side effects correctly - if we're tuned to this
+		   slot, then forgot the slot id relation since the stored
+		   frequency has been changed. */
+		if (hdw->freqSelector) {
+			if (hdw->freqSlotRadio == slotId) {
+				hdw->freqSlotRadio = 0;
+			}
+		} else {
+			if (hdw->freqSlotTelevision == slotId) {
+				hdw->freqSlotTelevision = 0;
+			}
+		}
+	}
+	return 0;
+}
+
+static int ctrl_channelprog_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->freqProgSlot;
+	return 0;
+}
+
+static int ctrl_channelprog_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if ((v >= 0) && (v <= FREQTABLE_SIZE)) {
+		hdw->freqProgSlot = v;
+	}
+	return 0;
+}
+
+static int ctrl_channel_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	*vp = hdw->freqSelector ? hdw->freqSlotRadio : hdw->freqSlotTelevision;
+	return 0;
+}
+
+static int ctrl_channel_set(struct pvr2_ctrl *cptr,int m,int slotId)
+{
+	unsigned freq = 0;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if ((slotId < 0) || (slotId > FREQTABLE_SIZE)) return 0;
+	if (slotId > 0) {
+		freq = hdw->freqTable[slotId-1];
+		if (!freq) return 0;
+		pvr2_hdw_set_cur_freq(hdw,freq);
+	}
+	if (hdw->freqSelector) {
+		hdw->freqSlotRadio = slotId;
+	} else {
+		hdw->freqSlotTelevision = slotId;
+	}
+	return 0;
+}
+
+static int ctrl_freq_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = pvr2_hdw_get_cur_freq(cptr->hdw);
+	return 0;
+}
+
+static int ctrl_freq_is_dirty(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->freqDirty != 0;
+}
+
+static void ctrl_freq_clear_dirty(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->freqDirty = 0;
+}
+
+static int ctrl_freq_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	pvr2_hdw_set_cur_freq(cptr->hdw,v);
+	return 0;
+}
+
+static int ctrl_cropl_min_get(struct pvr2_ctrl *cptr, int *left)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*left = cap->bounds.left;
+	return 0;
+}
+
+static int ctrl_cropl_max_get(struct pvr2_ctrl *cptr, int *left)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*left = cap->bounds.left;
+	if (cap->bounds.width > cptr->hdw->cropw_val) {
+		*left += cap->bounds.width - cptr->hdw->cropw_val;
+	}
+	return 0;
+}
+
+static int ctrl_cropt_min_get(struct pvr2_ctrl *cptr, int *top)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*top = cap->bounds.top;
+	return 0;
+}
+
+static int ctrl_cropt_max_get(struct pvr2_ctrl *cptr, int *top)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*top = cap->bounds.top;
+	if (cap->bounds.height > cptr->hdw->croph_val) {
+		*top += cap->bounds.height - cptr->hdw->croph_val;
+	}
+	return 0;
+}
+
+static int ctrl_cropw_max_get(struct pvr2_ctrl *cptr, int *width)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat, bleftend, cleft;
+
+	stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	bleftend = cap->bounds.left+cap->bounds.width;
+	cleft = cptr->hdw->cropl_val;
+
+	*width = cleft < bleftend ? bleftend-cleft : 0;
+	return 0;
+}
+
+static int ctrl_croph_max_get(struct pvr2_ctrl *cptr, int *height)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat, btopend, ctop;
+
+	stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	btopend = cap->bounds.top+cap->bounds.height;
+	ctop = cptr->hdw->cropt_val;
+
+	*height = ctop < btopend ? btopend-ctop : 0;
+	return 0;
+}
+
+static int ctrl_get_cropcapbl(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.left;
+	return 0;
+}
+
+static int ctrl_get_cropcapbt(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.top;
+	return 0;
+}
+
+static int ctrl_get_cropcapbw(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.width;
+	return 0;
+}
+
+static int ctrl_get_cropcapbh(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.height;
+	return 0;
+}
+
+static int ctrl_get_cropcapdl(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.left;
+	return 0;
+}
+
+static int ctrl_get_cropcapdt(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.top;
+	return 0;
+}
+
+static int ctrl_get_cropcapdw(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.width;
+	return 0;
+}
+
+static int ctrl_get_cropcapdh(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.height;
+	return 0;
+}
+
+static int ctrl_get_cropcappan(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->pixelaspect.numerator;
+	return 0;
+}
+
+static int ctrl_get_cropcappad(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->pixelaspect.denominator;
+	return 0;
+}
+
+static int ctrl_vres_max_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	/* Actual maximum depends on the video standard in effect. */
+	if (cptr->hdw->std_mask_cur & V4L2_STD_525_60) {
+		*vp = 480;
+	} else {
+		*vp = 576;
+	}
+	return 0;
+}
+
+static int ctrl_vres_min_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	/* Actual minimum depends on device digitizer type. */
+	if (cptr->hdw->hdw_desc->flag_has_cx25840) {
+		*vp = 75;
+	} else {
+		*vp = 17;
+	}
+	return 0;
+}
+
+static int ctrl_get_input(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->input_val;
+	return 0;
+}
+
+static int ctrl_check_input(struct pvr2_ctrl *cptr,int v)
+{
+	return ((1 << v) & cptr->hdw->input_allowed_mask) != 0;
+}
+
+static int ctrl_set_input(struct pvr2_ctrl *cptr,int m,int v)
+{
+	return pvr2_hdw_set_input(cptr->hdw,v);
+}
+
+static int ctrl_isdirty_input(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->input_dirty != 0;
+}
+
+static void ctrl_cleardirty_input(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->input_dirty = 0;
+}
+
+
+static int ctrl_freq_max_get(struct pvr2_ctrl *cptr, int *vp)
+{
+	unsigned long fv;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if (hdw->tuner_signal_stale) {
+		pvr2_hdw_status_poll(hdw);
+	}
+	fv = hdw->tuner_signal_info.rangehigh;
+	if (!fv) {
+		/* Safety fallback */
+		*vp = TV_MAX_FREQ;
+		return 0;
+	}
+	if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) {
+		fv = (fv * 125) / 2;
+	} else {
+		fv = fv * 62500;
+	}
+	*vp = fv;
+	return 0;
+}
+
+static int ctrl_freq_min_get(struct pvr2_ctrl *cptr, int *vp)
+{
+	unsigned long fv;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if (hdw->tuner_signal_stale) {
+		pvr2_hdw_status_poll(hdw);
+	}
+	fv = hdw->tuner_signal_info.rangelow;
+	if (!fv) {
+		/* Safety fallback */
+		*vp = TV_MIN_FREQ;
+		return 0;
+	}
+	if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) {
+		fv = (fv * 125) / 2;
+	} else {
+		fv = fv * 62500;
+	}
+	*vp = fv;
+	return 0;
+}
+
+static int ctrl_cx2341x_is_dirty(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->enc_stale != 0;
+}
+
+static void ctrl_cx2341x_clear_dirty(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->enc_stale = 0;
+	cptr->hdw->enc_unsafe_stale = 0;
+}
+
+static int ctrl_cx2341x_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	int ret;
+	struct v4l2_ext_controls cs;
+	struct v4l2_ext_control c1;
+	memset(&cs,0,sizeof(cs));
+	memset(&c1,0,sizeof(c1));
+	cs.controls = &c1;
+	cs.count = 1;
+	c1.id = cptr->info->v4l_id;
+	ret = cx2341x_ext_ctrls(&cptr->hdw->enc_ctl_state, 0, &cs,
+				VIDIOC_G_EXT_CTRLS);
+	if (ret) return ret;
+	*vp = c1.value;
+	return 0;
+}
+
+static int ctrl_cx2341x_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	int ret;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	struct v4l2_ext_controls cs;
+	struct v4l2_ext_control c1;
+	memset(&cs,0,sizeof(cs));
+	memset(&c1,0,sizeof(c1));
+	cs.controls = &c1;
+	cs.count = 1;
+	c1.id = cptr->info->v4l_id;
+	c1.value = v;
+	ret = cx2341x_ext_ctrls(&hdw->enc_ctl_state,
+				hdw->state_encoder_run, &cs,
+				VIDIOC_S_EXT_CTRLS);
+	if (ret == -EBUSY) {
+		/* Oops.  cx2341x is telling us it's not safe to change
+		   this control while we're capturing.  Make a note of this
+		   fact so that the pipeline will be stopped the next time
+		   controls are committed.  Then go on ahead and store this
+		   change anyway. */
+		ret = cx2341x_ext_ctrls(&hdw->enc_ctl_state,
+					0, &cs,
+					VIDIOC_S_EXT_CTRLS);
+		if (!ret) hdw->enc_unsafe_stale = !0;
+	}
+	if (ret) return ret;
+	hdw->enc_stale = !0;
+	return 0;
+}
+
+static unsigned int ctrl_cx2341x_getv4lflags(struct pvr2_ctrl *cptr)
+{
+	struct v4l2_queryctrl qctrl;
+	struct pvr2_ctl_info *info;
+	qctrl.id = cptr->info->v4l_id;
+	cx2341x_ctrl_query(&cptr->hdw->enc_ctl_state,&qctrl);
+	/* Strip out the const so we can adjust a function pointer.  It's
+	   OK to do this here because we know this is a dynamically created
+	   control, so the underlying storage for the info pointer is (a)
+	   private to us, and (b) not in read-only storage.  Either we do
+	   this or we significantly complicate the underlying control
+	   implementation. */
+	info = (struct pvr2_ctl_info *)(cptr->info);
+	if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) {
+		if (info->set_value) {
+			info->set_value = NULL;
+		}
+	} else {
+		if (!(info->set_value)) {
+			info->set_value = ctrl_cx2341x_set;
+		}
+	}
+	return qctrl.flags;
+}
+
+static int ctrl_streamingenabled_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->state_pipeline_req;
+	return 0;
+}
+
+static int ctrl_masterstate_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->master_state;
+	return 0;
+}
+
+static int ctrl_hsm_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	int result = pvr2_hdw_is_hsm(cptr->hdw);
+	*vp = PVR2_CVAL_HSM_FULL;
+	if (result < 0) *vp = PVR2_CVAL_HSM_FAIL;
+	if (result) *vp = PVR2_CVAL_HSM_HIGH;
+	return 0;
+}
+
+static int ctrl_stddetect_get(struct pvr2_ctrl *cptr, int *vp)
+{
+	*vp = pvr2_hdw_get_detected_std(cptr->hdw);
+	return 0;
+}
+
+static int ctrl_stdavail_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->std_mask_avail;
+	return 0;
+}
+
+static int ctrl_stdavail_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	v4l2_std_id ns;
+	ns = hdw->std_mask_avail;
+	ns = (ns & ~m) | (v & m);
+	if (ns == hdw->std_mask_avail) return 0;
+	hdw->std_mask_avail = ns;
+	hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail;
+	return 0;
+}
+
+static int ctrl_std_val_to_sym(struct pvr2_ctrl *cptr,int msk,int val,
+			       char *bufPtr,unsigned int bufSize,
+			       unsigned int *len)
+{
+	*len = pvr2_std_id_to_str(bufPtr,bufSize,msk & val);
+	return 0;
+}
+
+static int ctrl_std_sym_to_val(struct pvr2_ctrl *cptr,
+			       const char *bufPtr,unsigned int bufSize,
+			       int *mskp,int *valp)
+{
+	int ret;
+	v4l2_std_id id;
+	ret = pvr2_std_str_to_id(&id,bufPtr,bufSize);
+	if (ret < 0) return ret;
+	if (mskp) *mskp = id;
+	if (valp) *valp = id;
+	return 0;
+}
+
+static int ctrl_stdcur_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->std_mask_cur;
+	return 0;
+}
+
+static int ctrl_stdcur_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	v4l2_std_id ns;
+	ns = hdw->std_mask_cur;
+	ns = (ns & ~m) | (v & m);
+	if (ns == hdw->std_mask_cur) return 0;
+	hdw->std_mask_cur = ns;
+	hdw->std_dirty = !0;
+	return 0;
+}
+
+static int ctrl_stdcur_is_dirty(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->std_dirty != 0;
+}
+
+static void ctrl_stdcur_clear_dirty(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->std_dirty = 0;
+}
+
+static int ctrl_signal_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	pvr2_hdw_status_poll(hdw);
+	*vp = hdw->tuner_signal_info.signal;
+	return 0;
+}
+
+static int ctrl_audio_modes_present_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	int val = 0;
+	unsigned int subchan;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	pvr2_hdw_status_poll(hdw);
+	subchan = hdw->tuner_signal_info.rxsubchans;
+	if (subchan & V4L2_TUNER_SUB_MONO) {
+		val |= (1 << V4L2_TUNER_MODE_MONO);
+	}
+	if (subchan & V4L2_TUNER_SUB_STEREO) {
+		val |= (1 << V4L2_TUNER_MODE_STEREO);
+	}
+	if (subchan & V4L2_TUNER_SUB_LANG1) {
+		val |= (1 << V4L2_TUNER_MODE_LANG1);
+	}
+	if (subchan & V4L2_TUNER_SUB_LANG2) {
+		val |= (1 << V4L2_TUNER_MODE_LANG2);
+	}
+	*vp = val;
+	return 0;
+}
+
+
+#define DEFINT(vmin,vmax) \
+	.type = pvr2_ctl_int, \
+	.def.type_int.min_value = vmin, \
+	.def.type_int.max_value = vmax
+
+#define DEFENUM(tab) \
+	.type = pvr2_ctl_enum, \
+	.def.type_enum.count = ARRAY_SIZE(tab), \
+	.def.type_enum.value_names = tab
+
+#define DEFBOOL \
+	.type = pvr2_ctl_bool
+
+#define DEFMASK(msk,tab) \
+	.type = pvr2_ctl_bitmask, \
+	.def.type_bitmask.valid_bits = msk, \
+	.def.type_bitmask.bit_names = tab
+
+#define DEFREF(vname) \
+	.set_value = ctrl_set_##vname, \
+	.get_value = ctrl_get_##vname, \
+	.is_dirty = ctrl_isdirty_##vname, \
+	.clear_dirty = ctrl_cleardirty_##vname
+
+
+#define VCREATE_FUNCS(vname) \
+static int ctrl_get_##vname(struct pvr2_ctrl *cptr,int *vp) \
+{*vp = cptr->hdw->vname##_val; return 0;} \
+static int ctrl_set_##vname(struct pvr2_ctrl *cptr,int m,int v) \
+{cptr->hdw->vname##_val = v; cptr->hdw->vname##_dirty = !0; return 0;} \
+static int ctrl_isdirty_##vname(struct pvr2_ctrl *cptr) \
+{return cptr->hdw->vname##_dirty != 0;} \
+static void ctrl_cleardirty_##vname(struct pvr2_ctrl *cptr) \
+{cptr->hdw->vname##_dirty = 0;}
+
+VCREATE_FUNCS(brightness)
+VCREATE_FUNCS(contrast)
+VCREATE_FUNCS(saturation)
+VCREATE_FUNCS(hue)
+VCREATE_FUNCS(volume)
+VCREATE_FUNCS(balance)
+VCREATE_FUNCS(bass)
+VCREATE_FUNCS(treble)
+VCREATE_FUNCS(mute)
+VCREATE_FUNCS(cropl)
+VCREATE_FUNCS(cropt)
+VCREATE_FUNCS(cropw)
+VCREATE_FUNCS(croph)
+VCREATE_FUNCS(audiomode)
+VCREATE_FUNCS(res_hor)
+VCREATE_FUNCS(res_ver)
+VCREATE_FUNCS(srate)
+
+/* Table definition of all controls which can be manipulated */
+static const struct pvr2_ctl_info control_defs[] = {
+	{
+		.v4l_id = V4L2_CID_BRIGHTNESS,
+		.desc = "Brightness",
+		.name = "brightness",
+		.default_value = 128,
+		DEFREF(brightness),
+		DEFINT(0,255),
+	},{
+		.v4l_id = V4L2_CID_CONTRAST,
+		.desc = "Contrast",
+		.name = "contrast",
+		.default_value = 68,
+		DEFREF(contrast),
+		DEFINT(0,127),
+	},{
+		.v4l_id = V4L2_CID_SATURATION,
+		.desc = "Saturation",
+		.name = "saturation",
+		.default_value = 64,
+		DEFREF(saturation),
+		DEFINT(0,127),
+	},{
+		.v4l_id = V4L2_CID_HUE,
+		.desc = "Hue",
+		.name = "hue",
+		.default_value = 0,
+		DEFREF(hue),
+		DEFINT(-128,127),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_VOLUME,
+		.desc = "Volume",
+		.name = "volume",
+		.default_value = 62000,
+		DEFREF(volume),
+		DEFINT(0,65535),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_BALANCE,
+		.desc = "Balance",
+		.name = "balance",
+		.default_value = 0,
+		DEFREF(balance),
+		DEFINT(-32768,32767),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_BASS,
+		.desc = "Bass",
+		.name = "bass",
+		.default_value = 0,
+		DEFREF(bass),
+		DEFINT(-32768,32767),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_TREBLE,
+		.desc = "Treble",
+		.name = "treble",
+		.default_value = 0,
+		DEFREF(treble),
+		DEFINT(-32768,32767),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_MUTE,
+		.desc = "Mute",
+		.name = "mute",
+		.default_value = 0,
+		DEFREF(mute),
+		DEFBOOL,
+	}, {
+		.desc = "Capture crop left margin",
+		.name = "crop_left",
+		.internal_id = PVR2_CID_CROPL,
+		.default_value = 0,
+		DEFREF(cropl),
+		DEFINT(-129, 340),
+		.get_min_value = ctrl_cropl_min_get,
+		.get_max_value = ctrl_cropl_max_get,
+		.get_def_value = ctrl_get_cropcapdl,
+	}, {
+		.desc = "Capture crop top margin",
+		.name = "crop_top",
+		.internal_id = PVR2_CID_CROPT,
+		.default_value = 0,
+		DEFREF(cropt),
+		DEFINT(-35, 544),
+		.get_min_value = ctrl_cropt_min_get,
+		.get_max_value = ctrl_cropt_max_get,
+		.get_def_value = ctrl_get_cropcapdt,
+	}, {
+		.desc = "Capture crop width",
+		.name = "crop_width",
+		.internal_id = PVR2_CID_CROPW,
+		.default_value = 720,
+		DEFREF(cropw),
+		DEFINT(0, 864),
+		.get_max_value = ctrl_cropw_max_get,
+		.get_def_value = ctrl_get_cropcapdw,
+	}, {
+		.desc = "Capture crop height",
+		.name = "crop_height",
+		.internal_id = PVR2_CID_CROPH,
+		.default_value = 480,
+		DEFREF(croph),
+		DEFINT(0, 576),
+		.get_max_value = ctrl_croph_max_get,
+		.get_def_value = ctrl_get_cropcapdh,
+	}, {
+		.desc = "Capture capability pixel aspect numerator",
+		.name = "cropcap_pixel_numerator",
+		.internal_id = PVR2_CID_CROPCAPPAN,
+		.get_value = ctrl_get_cropcappan,
+	}, {
+		.desc = "Capture capability pixel aspect denominator",
+		.name = "cropcap_pixel_denominator",
+		.internal_id = PVR2_CID_CROPCAPPAD,
+		.get_value = ctrl_get_cropcappad,
+	}, {
+		.desc = "Capture capability bounds top",
+		.name = "cropcap_bounds_top",
+		.internal_id = PVR2_CID_CROPCAPBT,
+		.get_value = ctrl_get_cropcapbt,
+	}, {
+		.desc = "Capture capability bounds left",
+		.name = "cropcap_bounds_left",
+		.internal_id = PVR2_CID_CROPCAPBL,
+		.get_value = ctrl_get_cropcapbl,
+	}, {
+		.desc = "Capture capability bounds width",
+		.name = "cropcap_bounds_width",
+		.internal_id = PVR2_CID_CROPCAPBW,
+		.get_value = ctrl_get_cropcapbw,
+	}, {
+		.desc = "Capture capability bounds height",
+		.name = "cropcap_bounds_height",
+		.internal_id = PVR2_CID_CROPCAPBH,
+		.get_value = ctrl_get_cropcapbh,
+	},{
+		.desc = "Video Source",
+		.name = "input",
+		.internal_id = PVR2_CID_INPUT,
+		.default_value = PVR2_CVAL_INPUT_TV,
+		.check_value = ctrl_check_input,
+		DEFREF(input),
+		DEFENUM(control_values_input),
+	},{
+		.desc = "Audio Mode",
+		.name = "audio_mode",
+		.internal_id = PVR2_CID_AUDIOMODE,
+		.default_value = V4L2_TUNER_MODE_STEREO,
+		DEFREF(audiomode),
+		DEFENUM(control_values_audiomode),
+	},{
+		.desc = "Horizontal capture resolution",
+		.name = "resolution_hor",
+		.internal_id = PVR2_CID_HRES,
+		.default_value = 720,
+		DEFREF(res_hor),
+		DEFINT(19,720),
+	},{
+		.desc = "Vertical capture resolution",
+		.name = "resolution_ver",
+		.internal_id = PVR2_CID_VRES,
+		.default_value = 480,
+		DEFREF(res_ver),
+		DEFINT(17,576),
+		/* Hook in check for video standard and adjust maximum
+		   depending on the standard. */
+		.get_max_value = ctrl_vres_max_get,
+		.get_min_value = ctrl_vres_min_get,
+	},{
+		.v4l_id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ,
+		.default_value = V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000,
+		.desc = "Audio Sampling Frequency",
+		.name = "srate",
+		DEFREF(srate),
+		DEFENUM(control_values_srate),
+	},{
+		.desc = "Tuner Frequency (Hz)",
+		.name = "frequency",
+		.internal_id = PVR2_CID_FREQUENCY,
+		.default_value = 0,
+		.set_value = ctrl_freq_set,
+		.get_value = ctrl_freq_get,
+		.is_dirty = ctrl_freq_is_dirty,
+		.clear_dirty = ctrl_freq_clear_dirty,
+		DEFINT(0,0),
+		/* Hook in check for input value (tv/radio) and adjust
+		   max/min values accordingly */
+		.get_max_value = ctrl_freq_max_get,
+		.get_min_value = ctrl_freq_min_get,
+	},{
+		.desc = "Channel",
+		.name = "channel",
+		.set_value = ctrl_channel_set,
+		.get_value = ctrl_channel_get,
+		DEFINT(0,FREQTABLE_SIZE),
+	},{
+		.desc = "Channel Program Frequency",
+		.name = "freq_table_value",
+		.set_value = ctrl_channelfreq_set,
+		.get_value = ctrl_channelfreq_get,
+		DEFINT(0,0),
+		/* Hook in check for input value (tv/radio) and adjust
+		   max/min values accordingly */
+		.get_max_value = ctrl_freq_max_get,
+		.get_min_value = ctrl_freq_min_get,
+	},{
+		.desc = "Channel Program ID",
+		.name = "freq_table_channel",
+		.set_value = ctrl_channelprog_set,
+		.get_value = ctrl_channelprog_get,
+		DEFINT(0,FREQTABLE_SIZE),
+	},{
+		.desc = "Streaming Enabled",
+		.name = "streaming_enabled",
+		.get_value = ctrl_streamingenabled_get,
+		DEFBOOL,
+	},{
+		.desc = "USB Speed",
+		.name = "usb_speed",
+		.get_value = ctrl_hsm_get,
+		DEFENUM(control_values_hsm),
+	},{
+		.desc = "Master State",
+		.name = "master_state",
+		.get_value = ctrl_masterstate_get,
+		DEFENUM(pvr2_state_names),
+	},{
+		.desc = "Signal Present",
+		.name = "signal_present",
+		.get_value = ctrl_signal_get,
+		DEFINT(0,65535),
+	},{
+		.desc = "Audio Modes Present",
+		.name = "audio_modes_present",
+		.get_value = ctrl_audio_modes_present_get,
+		/* For this type we "borrow" the V4L2_TUNER_MODE enum from
+		   v4l.  Nothing outside of this module cares about this,
+		   but I reuse it in order to also reuse the
+		   control_values_audiomode string table. */
+		DEFMASK(((1 << V4L2_TUNER_MODE_MONO)|
+			 (1 << V4L2_TUNER_MODE_STEREO)|
+			 (1 << V4L2_TUNER_MODE_LANG1)|
+			 (1 << V4L2_TUNER_MODE_LANG2)),
+			control_values_audiomode),
+	},{
+		.desc = "Video Standards Available Mask",
+		.name = "video_standard_mask_available",
+		.internal_id = PVR2_CID_STDAVAIL,
+		.skip_init = !0,
+		.get_value = ctrl_stdavail_get,
+		.set_value = ctrl_stdavail_set,
+		.val_to_sym = ctrl_std_val_to_sym,
+		.sym_to_val = ctrl_std_sym_to_val,
+		.type = pvr2_ctl_bitmask,
+	},{
+		.desc = "Video Standards In Use Mask",
+		.name = "video_standard_mask_active",
+		.internal_id = PVR2_CID_STDCUR,
+		.skip_init = !0,
+		.get_value = ctrl_stdcur_get,
+		.set_value = ctrl_stdcur_set,
+		.is_dirty = ctrl_stdcur_is_dirty,
+		.clear_dirty = ctrl_stdcur_clear_dirty,
+		.val_to_sym = ctrl_std_val_to_sym,
+		.sym_to_val = ctrl_std_sym_to_val,
+		.type = pvr2_ctl_bitmask,
+	},{
+		.desc = "Video Standards Detected Mask",
+		.name = "video_standard_mask_detected",
+		.internal_id = PVR2_CID_STDDETECT,
+		.skip_init = !0,
+		.get_value = ctrl_stddetect_get,
+		.val_to_sym = ctrl_std_val_to_sym,
+		.sym_to_val = ctrl_std_sym_to_val,
+		.type = pvr2_ctl_bitmask,
+	}
+};
+
+#define CTRLDEF_COUNT ARRAY_SIZE(control_defs)
+
+
+const char *pvr2_config_get_name(enum pvr2_config cfg)
+{
+	switch (cfg) {
+	case pvr2_config_empty: return "empty";
+	case pvr2_config_mpeg: return "mpeg";
+	case pvr2_config_vbi: return "vbi";
+	case pvr2_config_pcm: return "pcm";
+	case pvr2_config_rawvideo: return "raw video";
+	}
+	return "<unknown>";
+}
+
+
+struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *hdw)
+{
+	return hdw->usb_dev;
+}
+
+
+unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *hdw)
+{
+	return hdw->serial_number;
+}
+
+
+const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *hdw)
+{
+	return hdw->bus_info;
+}
+
+
+const char *pvr2_hdw_get_device_identifier(struct pvr2_hdw *hdw)
+{
+	return hdw->identifier;
+}
+
+
+unsigned long pvr2_hdw_get_cur_freq(struct pvr2_hdw *hdw)
+{
+	return hdw->freqSelector ? hdw->freqValTelevision : hdw->freqValRadio;
+}
+
+/* Set the currently tuned frequency and account for all possible
+   driver-core side effects of this action. */
+static void pvr2_hdw_set_cur_freq(struct pvr2_hdw *hdw,unsigned long val)
+{
+	if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+		if (hdw->freqSelector) {
+			/* Swing over to radio frequency selection */
+			hdw->freqSelector = 0;
+			hdw->freqDirty = !0;
+		}
+		if (hdw->freqValRadio != val) {
+			hdw->freqValRadio = val;
+			hdw->freqSlotRadio = 0;
+			hdw->freqDirty = !0;
+		}
+	} else {
+		if (!(hdw->freqSelector)) {
+			/* Swing over to television frequency selection */
+			hdw->freqSelector = 1;
+			hdw->freqDirty = !0;
+		}
+		if (hdw->freqValTelevision != val) {
+			hdw->freqValTelevision = val;
+			hdw->freqSlotTelevision = 0;
+			hdw->freqDirty = !0;
+		}
+	}
+}
+
+int pvr2_hdw_get_unit_number(struct pvr2_hdw *hdw)
+{
+	return hdw->unit_number;
+}
+
+
+/* Attempt to locate one of the given set of files.  Messages are logged
+   appropriate to what has been found.  The return value will be 0 or
+   greater on success (it will be the index of the file name found) and
+   fw_entry will be filled in.  Otherwise a negative error is returned on
+   failure.  If the return value is -ENOENT then no viable firmware file
+   could be located. */
+static int pvr2_locate_firmware(struct pvr2_hdw *hdw,
+				const struct firmware **fw_entry,
+				const char *fwtypename,
+				unsigned int fwcount,
+				const char *fwnames[])
+{
+	unsigned int idx;
+	int ret = -EINVAL;
+	for (idx = 0; idx < fwcount; idx++) {
+		ret = request_firmware(fw_entry,
+				       fwnames[idx],
+				       &hdw->usb_dev->dev);
+		if (!ret) {
+			trace_firmware("Located %s firmware: %s; uploading...",
+				       fwtypename,
+				       fwnames[idx]);
+			return idx;
+		}
+		if (ret == -ENOENT) continue;
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "request_firmware fatal error with code=%d",ret);
+		return ret;
+	}
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "***WARNING*** Device %s firmware seems to be missing.",
+		   fwtypename);
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "Did you install the pvrusb2 firmware files in their proper location?");
+	if (fwcount == 1) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "request_firmware unable to locate %s file %s",
+			   fwtypename,fwnames[0]);
+	} else {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "request_firmware unable to locate one of the following %s files:",
+			   fwtypename);
+		for (idx = 0; idx < fwcount; idx++) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "request_firmware: Failed to find %s",
+				   fwnames[idx]);
+		}
+	}
+	return ret;
+}
+
+
+/*
+ * pvr2_upload_firmware1().
+ *
+ * Send the 8051 firmware to the device.  After the upload, arrange for
+ * device to re-enumerate.
+ *
+ * NOTE : the pointer to the firmware data given by request_firmware()
+ * is not suitable for an usb transaction.
+ *
+ */
+static int pvr2_upload_firmware1(struct pvr2_hdw *hdw)
+{
+	const struct firmware *fw_entry = NULL;
+	void  *fw_ptr;
+	unsigned int pipe;
+	unsigned int fwsize;
+	int ret;
+	u16 address;
+
+	if (!hdw->hdw_desc->fx2_firmware.cnt) {
+		hdw->fw1_state = FW1_STATE_OK;
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Connected device type defines no firmware to upload; ignoring firmware");
+		return -ENOTTY;
+	}
+
+	hdw->fw1_state = FW1_STATE_FAILED; // default result
+
+	trace_firmware("pvr2_upload_firmware1");
+
+	ret = pvr2_locate_firmware(hdw,&fw_entry,"fx2 controller",
+				   hdw->hdw_desc->fx2_firmware.cnt,
+				   hdw->hdw_desc->fx2_firmware.lst);
+	if (ret < 0) {
+		if (ret == -ENOENT) hdw->fw1_state = FW1_STATE_MISSING;
+		return ret;
+	}
+
+	usb_clear_halt(hdw->usb_dev, usb_sndbulkpipe(hdw->usb_dev, 0 & 0x7f));
+
+	pipe = usb_sndctrlpipe(hdw->usb_dev, 0);
+	fwsize = fw_entry->size;
+
+	if ((fwsize != 0x2000) &&
+	    (!(hdw->hdw_desc->flag_fx2_16kb && (fwsize == 0x4000)))) {
+		if (hdw->hdw_desc->flag_fx2_16kb) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Wrong fx2 firmware size (expected 8192 or 16384, got %u)",
+				   fwsize);
+		} else {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Wrong fx2 firmware size (expected 8192, got %u)",
+				   fwsize);
+		}
+		release_firmware(fw_entry);
+		return -ENOMEM;
+	}
+
+	fw_ptr = kmalloc(0x800, GFP_KERNEL);
+	if (fw_ptr == NULL){
+		release_firmware(fw_entry);
+		return -ENOMEM;
+	}
+
+	/* We have to hold the CPU during firmware upload. */
+	pvr2_hdw_cpureset_assert(hdw,1);
+
+	/* upload the firmware to address 0000-1fff in 2048 (=0x800) bytes
+	   chunk. */
+
+	ret = 0;
+	for (address = 0; address < fwsize; address += 0x800) {
+		memcpy(fw_ptr, fw_entry->data + address, 0x800);
+		ret += usb_control_msg(hdw->usb_dev, pipe, 0xa0, 0x40, address,
+				       0, fw_ptr, 0x800, HZ);
+	}
+
+	trace_firmware("Upload done, releasing device's CPU");
+
+	/* Now release the CPU.  It will disconnect and reconnect later. */
+	pvr2_hdw_cpureset_assert(hdw,0);
+
+	kfree(fw_ptr);
+	release_firmware(fw_entry);
+
+	trace_firmware("Upload done (%d bytes sent)",ret);
+
+	/* We should have written fwsize bytes */
+	if (ret == fwsize) {
+		hdw->fw1_state = FW1_STATE_RELOAD;
+		return 0;
+	}
+
+	return -EIO;
+}
+
+
+/*
+ * pvr2_upload_firmware2()
+ *
+ * This uploads encoder firmware on endpoint 2.
+ *
+ */
+
+int pvr2_upload_firmware2(struct pvr2_hdw *hdw)
+{
+	const struct firmware *fw_entry = NULL;
+	void  *fw_ptr;
+	unsigned int pipe, fw_len, fw_done, bcnt, icnt;
+	int actual_length;
+	int ret = 0;
+	int fwidx;
+	static const char *fw_files[] = {
+		CX2341X_FIRM_ENC_FILENAME,
+	};
+
+	if (hdw->hdw_desc->flag_skip_cx23416_firmware) {
+		return 0;
+	}
+
+	trace_firmware("pvr2_upload_firmware2");
+
+	ret = pvr2_locate_firmware(hdw,&fw_entry,"encoder",
+				   ARRAY_SIZE(fw_files), fw_files);
+	if (ret < 0) return ret;
+	fwidx = ret;
+	ret = 0;
+	/* Since we're about to completely reinitialize the encoder,
+	   invalidate our cached copy of its configuration state.  Next
+	   time we configure the encoder, then we'll fully configure it. */
+	hdw->enc_cur_valid = 0;
+
+	/* Encoder is about to be reset so note that as far as we're
+	   concerned now, the encoder has never been run. */
+	del_timer_sync(&hdw->encoder_run_timer);
+	if (hdw->state_encoder_runok) {
+		hdw->state_encoder_runok = 0;
+		trace_stbit("state_encoder_runok",hdw->state_encoder_runok);
+	}
+
+	/* First prepare firmware loading */
+	ret |= pvr2_write_register(hdw, 0x0048, 0xffffffff); /*interrupt mask*/
+	ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000088); /*gpio dir*/
+	ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/
+	ret |= pvr2_hdw_cmd_deep_reset(hdw);
+	ret |= pvr2_write_register(hdw, 0xa064, 0x00000000); /*APU command*/
+	ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000408); /*gpio dir*/
+	ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/
+	ret |= pvr2_write_register(hdw, 0x9058, 0xffffffed); /*VPU ctrl*/
+	ret |= pvr2_write_register(hdw, 0x9054, 0xfffffffd); /*reset hw blocks*/
+	ret |= pvr2_write_register(hdw, 0x07f8, 0x80000800); /*encoder SDRAM refresh*/
+	ret |= pvr2_write_register(hdw, 0x07fc, 0x0000001a); /*encoder SDRAM pre-charge*/
+	ret |= pvr2_write_register(hdw, 0x0700, 0x00000000); /*I2C clock*/
+	ret |= pvr2_write_register(hdw, 0xaa00, 0x00000000); /*unknown*/
+	ret |= pvr2_write_register(hdw, 0xaa04, 0x00057810); /*unknown*/
+	ret |= pvr2_write_register(hdw, 0xaa10, 0x00148500); /*unknown*/
+	ret |= pvr2_write_register(hdw, 0xaa18, 0x00840000); /*unknown*/
+	ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_FWPOST1);
+	ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_MEMSEL | (1 << 8) | (0 << 16));
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "firmware2 upload prep failed, ret=%d",ret);
+		release_firmware(fw_entry);
+		goto done;
+	}
+
+	/* Now send firmware */
+
+	fw_len = fw_entry->size;
+
+	if (fw_len % sizeof(u32)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "size of %s firmware must be a multiple of %zu bytes",
+			   fw_files[fwidx],sizeof(u32));
+		release_firmware(fw_entry);
+		ret = -EINVAL;
+		goto done;
+	}
+
+	fw_ptr = kmalloc(FIRMWARE_CHUNK_SIZE, GFP_KERNEL);
+	if (fw_ptr == NULL){
+		release_firmware(fw_entry);
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "failed to allocate memory for firmware2 upload");
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	pipe = usb_sndbulkpipe(hdw->usb_dev, PVR2_FIRMWARE_ENDPOINT);
+
+	fw_done = 0;
+	for (fw_done = 0; fw_done < fw_len;) {
+		bcnt = fw_len - fw_done;
+		if (bcnt > FIRMWARE_CHUNK_SIZE) bcnt = FIRMWARE_CHUNK_SIZE;
+		memcpy(fw_ptr, fw_entry->data + fw_done, bcnt);
+		/* Usbsnoop log shows that we must swap bytes... */
+		/* Some background info: The data being swapped here is a
+		   firmware image destined for the mpeg encoder chip that
+		   lives at the other end of a USB endpoint.  The encoder
+		   chip always talks in 32 bit chunks and its storage is
+		   organized into 32 bit words.  However from the file
+		   system to the encoder chip everything is purely a byte
+		   stream.  The firmware file's contents are always 32 bit
+		   swapped from what the encoder expects.  Thus the need
+		   always exists to swap the bytes regardless of the endian
+		   type of the host processor and therefore swab32() makes
+		   the most sense. */
+		for (icnt = 0; icnt < bcnt/4 ; icnt++)
+			((u32 *)fw_ptr)[icnt] = swab32(((u32 *)fw_ptr)[icnt]);
+
+		ret |= usb_bulk_msg(hdw->usb_dev, pipe, fw_ptr,bcnt,
+				    &actual_length, HZ);
+		ret |= (actual_length != bcnt);
+		if (ret) break;
+		fw_done += bcnt;
+	}
+
+	trace_firmware("upload of %s : %i / %i ",
+		       fw_files[fwidx],fw_done,fw_len);
+
+	kfree(fw_ptr);
+	release_firmware(fw_entry);
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "firmware2 upload transfer failure");
+		goto done;
+	}
+
+	/* Finish upload */
+
+	ret |= pvr2_write_register(hdw, 0x9054, 0xffffffff); /*reset hw blocks*/
+	ret |= pvr2_write_register(hdw, 0x9058, 0xffffffe8); /*VPU ctrl*/
+	ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_MEMSEL | (1 << 8) | (0 << 16));
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "firmware2 upload post-proc failure");
+	}
+
+ done:
+	if (hdw->hdw_desc->signal_routing_scheme ==
+	    PVR2_ROUTING_SCHEME_GOTVIEW) {
+		/* Ensure that GPIO 11 is set to output for GOTVIEW
+		   hardware. */
+		pvr2_hdw_gpio_chg_dir(hdw,(1 << 11),~0);
+	}
+	return ret;
+}
+
+
+static const char *pvr2_get_state_name(unsigned int st)
+{
+	if (st < ARRAY_SIZE(pvr2_state_names)) {
+		return pvr2_state_names[st];
+	}
+	return "???";
+}
+
+static int pvr2_decoder_enable(struct pvr2_hdw *hdw,int enablefl)
+{
+	/* Even though we really only care about the video decoder chip at
+	   this point, we'll broadcast stream on/off to all sub-devices
+	   anyway, just in case somebody else wants to hear the
+	   command... */
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 stream=%s",
+		   (enablefl ? "on" : "off"));
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, video, s_stream, enablefl);
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, audio, s_stream, enablefl);
+	if (hdw->decoder_client_id) {
+		/* We get here if the encoder has been noticed.  Otherwise
+		   we'll issue a warning to the user (which should
+		   normally never happen). */
+		return 0;
+	}
+	if (!hdw->flag_decoder_missed) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: No decoder present");
+		hdw->flag_decoder_missed = !0;
+		trace_stbit("flag_decoder_missed",
+			    hdw->flag_decoder_missed);
+	}
+	return -EIO;
+}
+
+
+int pvr2_hdw_get_state(struct pvr2_hdw *hdw)
+{
+	return hdw->master_state;
+}
+
+
+static int pvr2_hdw_untrip_unlocked(struct pvr2_hdw *hdw)
+{
+	if (!hdw->flag_tripped) return 0;
+	hdw->flag_tripped = 0;
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "Clearing driver error statuss");
+	return !0;
+}
+
+
+int pvr2_hdw_untrip(struct pvr2_hdw *hdw)
+{
+	int fl;
+	LOCK_TAKE(hdw->big_lock); do {
+		fl = pvr2_hdw_untrip_unlocked(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	if (fl) pvr2_hdw_state_sched(hdw);
+	return 0;
+}
+
+
+
+
+int pvr2_hdw_get_streaming(struct pvr2_hdw *hdw)
+{
+	return hdw->state_pipeline_req != 0;
+}
+
+
+int pvr2_hdw_set_streaming(struct pvr2_hdw *hdw,int enable_flag)
+{
+	int ret,st;
+	LOCK_TAKE(hdw->big_lock); do {
+		pvr2_hdw_untrip_unlocked(hdw);
+		if ((!enable_flag) != !(hdw->state_pipeline_req)) {
+			hdw->state_pipeline_req = enable_flag != 0;
+			pvr2_trace(PVR2_TRACE_START_STOP,
+				   "/*--TRACE_STREAM--*/ %s",
+				   enable_flag ? "enable" : "disable");
+		}
+		pvr2_hdw_state_sched(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	if ((ret = pvr2_hdw_wait(hdw,0)) < 0) return ret;
+	if (enable_flag) {
+		while ((st = hdw->master_state) != PVR2_STATE_RUN) {
+			if (st != PVR2_STATE_READY) return -EIO;
+			if ((ret = pvr2_hdw_wait(hdw,st)) < 0) return ret;
+		}
+	}
+	return 0;
+}
+
+
+int pvr2_hdw_set_stream_type(struct pvr2_hdw *hdw,enum pvr2_config config)
+{
+	int fl;
+	LOCK_TAKE(hdw->big_lock);
+	if ((fl = (hdw->desired_stream_type != config)) != 0) {
+		hdw->desired_stream_type = config;
+		hdw->state_pipeline_config = 0;
+		trace_stbit("state_pipeline_config",
+			    hdw->state_pipeline_config);
+		pvr2_hdw_state_sched(hdw);
+	}
+	LOCK_GIVE(hdw->big_lock);
+	if (fl) return 0;
+	return pvr2_hdw_wait(hdw,0);
+}
+
+
+static int get_default_tuner_type(struct pvr2_hdw *hdw)
+{
+	int unit_number = hdw->unit_number;
+	int tp = -1;
+	if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+		tp = tuner[unit_number];
+	}
+	if (tp < 0) return -EINVAL;
+	hdw->tuner_type = tp;
+	hdw->tuner_updated = !0;
+	return 0;
+}
+
+
+static v4l2_std_id get_default_standard(struct pvr2_hdw *hdw)
+{
+	int unit_number = hdw->unit_number;
+	int tp = 0;
+	if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+		tp = video_std[unit_number];
+		if (tp) return tp;
+	}
+	return 0;
+}
+
+
+static unsigned int get_default_error_tolerance(struct pvr2_hdw *hdw)
+{
+	int unit_number = hdw->unit_number;
+	int tp = 0;
+	if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+		tp = tolerance[unit_number];
+	}
+	return tp;
+}
+
+
+static int pvr2_hdw_check_firmware(struct pvr2_hdw *hdw)
+{
+	/* Try a harmless request to fetch the eeprom's address over
+	   endpoint 1.  See what happens.  Only the full FX2 image can
+	   respond to this.  If this probe fails then likely the FX2
+	   firmware needs be loaded. */
+	int result;
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_EEPROM_ADDR;
+		result = pvr2_send_request_ex(hdw,HZ*1,!0,
+					   hdw->cmd_buffer,1,
+					   hdw->cmd_buffer,1);
+		if (result < 0) break;
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+	if (result) {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Probe of device endpoint 1 result status %d",
+			   result);
+	} else {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Probe of device endpoint 1 succeeded");
+	}
+	return result == 0;
+}
+
+struct pvr2_std_hack {
+	v4l2_std_id pat;  /* Pattern to match */
+	v4l2_std_id msk;  /* Which bits we care about */
+	v4l2_std_id std;  /* What additional standards or default to set */
+};
+
+/* This data structure labels specific combinations of standards from
+   tveeprom that we'll try to recognize.  If we recognize one, then assume
+   a specified default standard to use.  This is here because tveeprom only
+   tells us about available standards not the intended default standard (if
+   any) for the device in question.  We guess the default based on what has
+   been reported as available.  Note that this is only for guessing a
+   default - which can always be overridden explicitly - and if the user
+   has otherwise named a default then that default will always be used in
+   place of this table. */
+static const struct pvr2_std_hack std_eeprom_maps[] = {
+	{	/* PAL(B/G) */
+		.pat = V4L2_STD_B|V4L2_STD_GH,
+		.std = V4L2_STD_PAL_B|V4L2_STD_PAL_B1|V4L2_STD_PAL_G,
+	},
+	{	/* NTSC(M) */
+		.pat = V4L2_STD_MN,
+		.std = V4L2_STD_NTSC_M,
+	},
+	{	/* PAL(I) */
+		.pat = V4L2_STD_PAL_I,
+		.std = V4L2_STD_PAL_I,
+	},
+	{	/* SECAM(L/L') */
+		.pat = V4L2_STD_SECAM_L|V4L2_STD_SECAM_LC,
+		.std = V4L2_STD_SECAM_L|V4L2_STD_SECAM_LC,
+	},
+	{	/* PAL(D/D1/K) */
+		.pat = V4L2_STD_DK,
+		.std = V4L2_STD_PAL_D|V4L2_STD_PAL_D1|V4L2_STD_PAL_K,
+	},
+};
+
+static void pvr2_hdw_setup_std(struct pvr2_hdw *hdw)
+{
+	char buf[40];
+	unsigned int bcnt;
+	v4l2_std_id std1,std2,std3;
+
+	std1 = get_default_standard(hdw);
+	std3 = std1 ? 0 : hdw->hdw_desc->default_std_mask;
+
+	bcnt = pvr2_std_id_to_str(buf,sizeof(buf),hdw->std_mask_eeprom);
+	pvr2_trace(PVR2_TRACE_STD,
+		   "Supported video standard(s) reported available in hardware: %.*s",
+		   bcnt,buf);
+
+	hdw->std_mask_avail = hdw->std_mask_eeprom;
+
+	std2 = (std1|std3) & ~hdw->std_mask_avail;
+	if (std2) {
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std2);
+		pvr2_trace(PVR2_TRACE_STD,
+			   "Expanding supported video standards to include: %.*s",
+			   bcnt,buf);
+		hdw->std_mask_avail |= std2;
+	}
+
+	hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail;
+
+	if (std1) {
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std1);
+		pvr2_trace(PVR2_TRACE_STD,
+			   "Initial video standard forced to %.*s",
+			   bcnt,buf);
+		hdw->std_mask_cur = std1;
+		hdw->std_dirty = !0;
+		return;
+	}
+	if (std3) {
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std3);
+		pvr2_trace(PVR2_TRACE_STD,
+			   "Initial video standard (determined by device type): %.*s",
+			   bcnt, buf);
+		hdw->std_mask_cur = std3;
+		hdw->std_dirty = !0;
+		return;
+	}
+
+	{
+		unsigned int idx;
+		for (idx = 0; idx < ARRAY_SIZE(std_eeprom_maps); idx++) {
+			if (std_eeprom_maps[idx].msk ?
+			    ((std_eeprom_maps[idx].pat ^
+			     hdw->std_mask_eeprom) &
+			     std_eeprom_maps[idx].msk) :
+			    (std_eeprom_maps[idx].pat !=
+			     hdw->std_mask_eeprom)) continue;
+			bcnt = pvr2_std_id_to_str(buf,sizeof(buf),
+						  std_eeprom_maps[idx].std);
+			pvr2_trace(PVR2_TRACE_STD,
+				   "Initial video standard guessed as %.*s",
+				   bcnt,buf);
+			hdw->std_mask_cur = std_eeprom_maps[idx].std;
+			hdw->std_dirty = !0;
+			return;
+		}
+	}
+
+}
+
+
+static unsigned int pvr2_copy_i2c_addr_list(
+	unsigned short *dst, const unsigned char *src,
+	unsigned int dst_max)
+{
+	unsigned int cnt = 0;
+	if (!src) return 0;
+	while (src[cnt] && (cnt + 1) < dst_max) {
+		dst[cnt] = src[cnt];
+		cnt++;
+	}
+	dst[cnt] = I2C_CLIENT_END;
+	return cnt;
+}
+
+
+static void pvr2_hdw_cx25840_vbi_hack(struct pvr2_hdw *hdw)
+{
+	/*
+	  Mike Isely <isely@pobox.com> 19-Nov-2006 - This bit of nuttiness
+	  for cx25840 causes that module to correctly set up its video
+	  scaling.  This is really a problem in the cx25840 module itself,
+	  but we work around it here.  The problem has not been seen in
+	  ivtv because there VBI is supported and set up.  We don't do VBI
+	  here (at least not yet) and thus we never attempted to even set
+	  it up.
+	*/
+	struct v4l2_format fmt;
+	if (hdw->decoder_client_id != PVR2_CLIENT_ID_CX25840) {
+		/* We're not using a cx25840 so don't enable the hack */
+		return;
+	}
+
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Module ID %u: Executing cx25840 VBI hack",
+		   hdw->decoder_client_id);
+	memset(&fmt, 0, sizeof(fmt));
+	fmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
+	fmt.fmt.sliced.service_lines[0][21] = V4L2_SLICED_CAPTION_525;
+	fmt.fmt.sliced.service_lines[1][21] = V4L2_SLICED_CAPTION_525;
+	v4l2_device_call_all(&hdw->v4l2_dev, hdw->decoder_client_id,
+			     vbi, s_sliced_fmt, &fmt.fmt.sliced);
+}
+
+
+static int pvr2_hdw_load_subdev(struct pvr2_hdw *hdw,
+				const struct pvr2_device_client_desc *cd)
+{
+	const char *fname;
+	unsigned char mid;
+	struct v4l2_subdev *sd;
+	unsigned int i2ccnt;
+	const unsigned char *p;
+	/* Arbitrary count - max # i2c addresses we will probe */
+	unsigned short i2caddr[25];
+
+	mid = cd->module_id;
+	fname = (mid < ARRAY_SIZE(module_names)) ? module_names[mid] : NULL;
+	if (!fname) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Module ID %u for device %s has no name?  The driver might have a configuration problem.",
+			   mid,
+			   hdw->hdw_desc->description);
+		return -EINVAL;
+	}
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Module ID %u (%s) for device %s being loaded...",
+		   mid, fname,
+		   hdw->hdw_desc->description);
+
+	i2ccnt = pvr2_copy_i2c_addr_list(i2caddr, cd->i2c_address_list,
+					 ARRAY_SIZE(i2caddr));
+	if (!i2ccnt && ((p = (mid < ARRAY_SIZE(module_i2c_addresses)) ?
+			 module_i2c_addresses[mid] : NULL) != NULL)) {
+		/* Second chance: Try default i2c address list */
+		i2ccnt = pvr2_copy_i2c_addr_list(i2caddr, p,
+						 ARRAY_SIZE(i2caddr));
+		if (i2ccnt) {
+			pvr2_trace(PVR2_TRACE_INIT,
+				   "Module ID %u: Using default i2c address list",
+				   mid);
+		}
+	}
+
+	if (!i2ccnt) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Module ID %u (%s) for device %s: No i2c addresses.	The driver might have a configuration problem.",
+			   mid, fname, hdw->hdw_desc->description);
+		return -EINVAL;
+	}
+
+	if (i2ccnt == 1) {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Module ID %u: Setting up with specified i2c address 0x%x",
+			   mid, i2caddr[0]);
+		sd = v4l2_i2c_new_subdev(&hdw->v4l2_dev, &hdw->i2c_adap,
+					 fname, i2caddr[0], NULL);
+	} else {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Module ID %u: Setting up with address probe list",
+			   mid);
+		sd = v4l2_i2c_new_subdev(&hdw->v4l2_dev, &hdw->i2c_adap,
+					 fname, 0, i2caddr);
+	}
+
+	if (!sd) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Module ID %u (%s) for device %s failed to load.  Possible missing sub-device kernel module or initialization failure within module.",
+			   mid, fname, hdw->hdw_desc->description);
+		return -EIO;
+	}
+
+	/* Tag this sub-device instance with the module ID we know about.
+	   In other places we'll use that tag to determine if the instance
+	   requires special handling. */
+	sd->grp_id = mid;
+
+	pvr2_trace(PVR2_TRACE_INFO, "Attached sub-driver %s", fname);
+
+
+	/* client-specific setup... */
+	switch (mid) {
+	case PVR2_CLIENT_ID_CX25840:
+	case PVR2_CLIENT_ID_SAA7115:
+		hdw->decoder_client_id = mid;
+		break;
+	default: break;
+	}
+
+	return 0;
+}
+
+
+static void pvr2_hdw_load_modules(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+	const struct pvr2_string_table *cm;
+	const struct pvr2_device_client_table *ct;
+	int okFl = !0;
+
+	cm = &hdw->hdw_desc->client_modules;
+	for (idx = 0; idx < cm->cnt; idx++) {
+		request_module(cm->lst[idx]);
+	}
+
+	ct = &hdw->hdw_desc->client_table;
+	for (idx = 0; idx < ct->cnt; idx++) {
+		if (pvr2_hdw_load_subdev(hdw, &ct->lst[idx]) < 0) okFl = 0;
+	}
+	if (!okFl) {
+		hdw->flag_modulefail = !0;
+		pvr2_hdw_render_useless(hdw);
+	}
+}
+
+
+static void pvr2_hdw_setup_low(struct pvr2_hdw *hdw)
+{
+	int ret;
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int reloadFl = 0;
+	if (hdw->hdw_desc->fx2_firmware.cnt) {
+		if (!reloadFl) {
+			reloadFl =
+				(hdw->usb_intf->cur_altsetting->desc.bNumEndpoints
+				 == 0);
+			if (reloadFl) {
+				pvr2_trace(PVR2_TRACE_INIT,
+					   "USB endpoint config looks strange; possibly firmware needs to be loaded");
+			}
+		}
+		if (!reloadFl) {
+			reloadFl = !pvr2_hdw_check_firmware(hdw);
+			if (reloadFl) {
+				pvr2_trace(PVR2_TRACE_INIT,
+					   "Check for FX2 firmware failed; possibly firmware needs to be loaded");
+			}
+		}
+		if (reloadFl) {
+			if (pvr2_upload_firmware1(hdw) != 0) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "Failure uploading firmware1");
+			}
+			return;
+		}
+	}
+	hdw->fw1_state = FW1_STATE_OK;
+
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	hdw->force_dirty = !0;
+
+	if (!hdw->hdw_desc->flag_no_powerup) {
+		pvr2_hdw_cmd_powerup(hdw);
+		if (!pvr2_hdw_dev_ok(hdw)) return;
+	}
+
+	/* Take the IR chip out of reset, if appropriate */
+	if (hdw->ir_scheme_active == PVR2_IR_SCHEME_ZILOG) {
+		pvr2_issue_simple_cmd(hdw,
+				      FX2CMD_HCW_ZILOG_RESET |
+				      (1 << 8) |
+				      ((0) << 16));
+	}
+
+	// This step MUST happen after the earlier powerup step.
+	pvr2_i2c_core_init(hdw);
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	pvr2_hdw_load_modules(hdw);
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, core, load_fw);
+
+	for (idx = 0; idx < CTRLDEF_COUNT; idx++) {
+		cptr = hdw->controls + idx;
+		if (cptr->info->skip_init) continue;
+		if (!cptr->info->set_value) continue;
+		cptr->info->set_value(cptr,~0,cptr->info->default_value);
+	}
+
+	pvr2_hdw_cx25840_vbi_hack(hdw);
+
+	/* Set up special default values for the television and radio
+	   frequencies here.  It's not really important what these defaults
+	   are, but I set them to something usable in the Chicago area just
+	   to make driver testing a little easier. */
+
+	hdw->freqValTelevision = default_tv_freq;
+	hdw->freqValRadio = default_radio_freq;
+
+	// Do not use pvr2_reset_ctl_endpoints() here.  It is not
+	// thread-safe against the normal pvr2_send_request() mechanism.
+	// (We should make it thread safe).
+
+	if (hdw->hdw_desc->flag_has_hauppauge_rom) {
+		ret = pvr2_hdw_get_eeprom_addr(hdw);
+		if (!pvr2_hdw_dev_ok(hdw)) return;
+		if (ret < 0) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Unable to determine location of eeprom, skipping");
+		} else {
+			hdw->eeprom_addr = ret;
+			pvr2_eeprom_analyze(hdw);
+			if (!pvr2_hdw_dev_ok(hdw)) return;
+		}
+	} else {
+		hdw->tuner_type = hdw->hdw_desc->default_tuner_type;
+		hdw->tuner_updated = !0;
+		hdw->std_mask_eeprom = V4L2_STD_ALL;
+	}
+
+	if (hdw->serial_number) {
+		idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1,
+				"sn-%lu", hdw->serial_number);
+	} else if (hdw->unit_number >= 0) {
+		idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1,
+				"unit-%c",
+				hdw->unit_number + 'a');
+	} else {
+		idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1,
+				"unit-??");
+	}
+	hdw->identifier[idx] = 0;
+
+	pvr2_hdw_setup_std(hdw);
+
+	if (!get_default_tuner_type(hdw)) {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "pvr2_hdw_setup: Tuner type overridden to %d",
+			   hdw->tuner_type);
+	}
+
+
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	if (hdw->hdw_desc->signal_routing_scheme ==
+	    PVR2_ROUTING_SCHEME_GOTVIEW) {
+		/* Ensure that GPIO 11 is set to output for GOTVIEW
+		   hardware. */
+		pvr2_hdw_gpio_chg_dir(hdw,(1 << 11),~0);
+	}
+
+	pvr2_hdw_commit_setup(hdw);
+
+	hdw->vid_stream = pvr2_stream_create();
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "pvr2_hdw_setup: video stream is %p",hdw->vid_stream);
+	if (hdw->vid_stream) {
+		idx = get_default_error_tolerance(hdw);
+		if (idx) {
+			pvr2_trace(PVR2_TRACE_INIT,
+				   "pvr2_hdw_setup: video stream %p setting tolerance %u",
+				   hdw->vid_stream,idx);
+		}
+		pvr2_stream_setup(hdw->vid_stream,hdw->usb_dev,
+				  PVR2_VID_ENDPOINT,idx);
+	}
+
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	hdw->flag_init_ok = !0;
+
+	pvr2_hdw_state_sched(hdw);
+}
+
+
+/* Set up the structure and attempt to put the device into a usable state.
+   This can be a time-consuming operation, which is why it is not done
+   internally as part of the create() step. */
+static void pvr2_hdw_setup(struct pvr2_hdw *hdw)
+{
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) begin",hdw);
+	do {
+		pvr2_hdw_setup_low(hdw);
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "pvr2_hdw_setup(hdw=%p) done, ok=%d init_ok=%d",
+			   hdw,pvr2_hdw_dev_ok(hdw),hdw->flag_init_ok);
+		if (pvr2_hdw_dev_ok(hdw)) {
+			if (hdw->flag_init_ok) {
+				pvr2_trace(
+					PVR2_TRACE_INFO,
+					"Device initialization completed successfully.");
+				break;
+			}
+			if (hdw->fw1_state == FW1_STATE_RELOAD) {
+				pvr2_trace(
+					PVR2_TRACE_INFO,
+					"Device microcontroller firmware (re)loaded; it should now reset and reconnect.");
+				break;
+			}
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Device initialization was not successful.");
+			if (hdw->fw1_state == FW1_STATE_MISSING) {
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"Giving up since device microcontroller firmware appears to be missing.");
+				break;
+			}
+		}
+		if (hdw->flag_modulefail) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"***WARNING*** pvrusb2 driver initialization failed due to the failure of one or more sub-device kernel modules.");
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"You need to resolve the failing condition before this driver can function.  There should be some earlier messages giving more information about the problem.");
+			break;
+		}
+		if (procreload) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Attempting pvrusb2 recovery by reloading primary firmware.");
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"If this works, device should disconnect and reconnect in a sane state.");
+			hdw->fw1_state = FW1_STATE_UNKNOWN;
+			pvr2_upload_firmware1(hdw);
+		} else {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"***WARNING*** pvrusb2 device hardware appears to be jammed and I can't clear it.");
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"You might need to power cycle the pvrusb2 device in order to recover.");
+		}
+	} while (0);
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) end",hdw);
+}
+
+
+/* Perform second stage initialization.  Set callback pointer first so that
+   we can avoid a possible initialization race (if the kernel thread runs
+   before the callback has been set). */
+int pvr2_hdw_initialize(struct pvr2_hdw *hdw,
+			void (*callback_func)(void *),
+			void *callback_data)
+{
+	LOCK_TAKE(hdw->big_lock); do {
+		if (hdw->flag_disconnected) {
+			/* Handle a race here: If we're already
+			   disconnected by this point, then give up.  If we
+			   get past this then we'll remain connected for
+			   the duration of initialization since the entire
+			   initialization sequence is now protected by the
+			   big_lock. */
+			break;
+		}
+		hdw->state_data = callback_data;
+		hdw->state_func = callback_func;
+		pvr2_hdw_setup(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	return hdw->flag_init_ok;
+}
+
+
+/* Create, set up, and return a structure for interacting with the
+   underlying hardware.  */
+struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf,
+				 const struct usb_device_id *devid)
+{
+	unsigned int idx,cnt1,cnt2,m;
+	struct pvr2_hdw *hdw = NULL;
+	int valid_std_mask;
+	struct pvr2_ctrl *cptr;
+	struct usb_device *usb_dev;
+	const struct pvr2_device_desc *hdw_desc;
+	__u8 ifnum;
+	struct v4l2_queryctrl qctrl;
+	struct pvr2_ctl_info *ciptr;
+
+	usb_dev = interface_to_usbdev(intf);
+
+	hdw_desc = (const struct pvr2_device_desc *)(devid->driver_info);
+
+	if (hdw_desc == NULL) {
+		pvr2_trace(PVR2_TRACE_INIT, "pvr2_hdw_create: No device description pointer, unable to continue.");
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "If you have a new device type, please contact Mike Isely <isely@pobox.com> to get it included in the driver");
+		goto fail;
+	}
+
+	hdw = kzalloc(sizeof(*hdw),GFP_KERNEL);
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_create: hdw=%p, type \"%s\"",
+		   hdw,hdw_desc->description);
+	pvr2_trace(PVR2_TRACE_INFO, "Hardware description: %s",
+		hdw_desc->description);
+	if (hdw_desc->flag_is_experimental) {
+		pvr2_trace(PVR2_TRACE_INFO, "**********");
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "WARNING: Support for this device (%s) is experimental.",
+							      hdw_desc->description);
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Important functionality might not be entirely working.");
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Please consider contacting the driver author to help with further stabilization of the driver.");
+		pvr2_trace(PVR2_TRACE_INFO, "**********");
+	}
+	if (!hdw) goto fail;
+
+	timer_setup(&hdw->quiescent_timer, pvr2_hdw_quiescent_timeout, 0);
+
+	timer_setup(&hdw->decoder_stabilization_timer,
+		    pvr2_hdw_decoder_stabilization_timeout, 0);
+
+	timer_setup(&hdw->encoder_wait_timer, pvr2_hdw_encoder_wait_timeout,
+		    0);
+
+	timer_setup(&hdw->encoder_run_timer, pvr2_hdw_encoder_run_timeout, 0);
+
+	hdw->master_state = PVR2_STATE_DEAD;
+
+	init_waitqueue_head(&hdw->state_wait_data);
+
+	hdw->tuner_signal_stale = !0;
+	cx2341x_fill_defaults(&hdw->enc_ctl_state);
+
+	/* Calculate which inputs are OK */
+	m = 0;
+	if (hdw_desc->flag_has_analogtuner) m |= 1 << PVR2_CVAL_INPUT_TV;
+	if (hdw_desc->digital_control_scheme != PVR2_DIGITAL_SCHEME_NONE) {
+		m |= 1 << PVR2_CVAL_INPUT_DTV;
+	}
+	if (hdw_desc->flag_has_svideo) m |= 1 << PVR2_CVAL_INPUT_SVIDEO;
+	if (hdw_desc->flag_has_composite) m |= 1 << PVR2_CVAL_INPUT_COMPOSITE;
+	if (hdw_desc->flag_has_fmradio) m |= 1 << PVR2_CVAL_INPUT_RADIO;
+	hdw->input_avail_mask = m;
+	hdw->input_allowed_mask = hdw->input_avail_mask;
+
+	/* If not a hybrid device, pathway_state never changes.  So
+	   initialize it here to what it should forever be. */
+	if (!(hdw->input_avail_mask & (1 << PVR2_CVAL_INPUT_DTV))) {
+		hdw->pathway_state = PVR2_PATHWAY_ANALOG;
+	} else if (!(hdw->input_avail_mask & (1 << PVR2_CVAL_INPUT_TV))) {
+		hdw->pathway_state = PVR2_PATHWAY_DIGITAL;
+	}
+
+	hdw->control_cnt = CTRLDEF_COUNT;
+	hdw->control_cnt += MPEGDEF_COUNT;
+	hdw->controls = kcalloc(hdw->control_cnt, sizeof(struct pvr2_ctrl),
+				GFP_KERNEL);
+	if (!hdw->controls) goto fail;
+	hdw->hdw_desc = hdw_desc;
+	hdw->ir_scheme_active = hdw->hdw_desc->ir_scheme;
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		cptr->hdw = hdw;
+	}
+	for (idx = 0; idx < 32; idx++) {
+		hdw->std_mask_ptrs[idx] = hdw->std_mask_names[idx];
+	}
+	for (idx = 0; idx < CTRLDEF_COUNT; idx++) {
+		cptr = hdw->controls + idx;
+		cptr->info = control_defs+idx;
+	}
+
+	/* Ensure that default input choice is a valid one. */
+	m = hdw->input_avail_mask;
+	if (m) for (idx = 0; idx < (sizeof(m) << 3); idx++) {
+		if (!((1 << idx) & m)) continue;
+		hdw->input_val = idx;
+		break;
+	}
+
+	/* Define and configure additional controls from cx2341x module. */
+	hdw->mpeg_ctrl_info = kcalloc(MPEGDEF_COUNT,
+				      sizeof(*(hdw->mpeg_ctrl_info)),
+				      GFP_KERNEL);
+	if (!hdw->mpeg_ctrl_info) goto fail;
+	for (idx = 0; idx < MPEGDEF_COUNT; idx++) {
+		cptr = hdw->controls + idx + CTRLDEF_COUNT;
+		ciptr = &(hdw->mpeg_ctrl_info[idx].info);
+		ciptr->desc = hdw->mpeg_ctrl_info[idx].desc;
+		ciptr->name = mpeg_ids[idx].strid;
+		ciptr->v4l_id = mpeg_ids[idx].id;
+		ciptr->skip_init = !0;
+		ciptr->get_value = ctrl_cx2341x_get;
+		ciptr->get_v4lflags = ctrl_cx2341x_getv4lflags;
+		ciptr->is_dirty = ctrl_cx2341x_is_dirty;
+		if (!idx) ciptr->clear_dirty = ctrl_cx2341x_clear_dirty;
+		qctrl.id = ciptr->v4l_id;
+		cx2341x_ctrl_query(&hdw->enc_ctl_state,&qctrl);
+		if (!(qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY)) {
+			ciptr->set_value = ctrl_cx2341x_set;
+		}
+		strncpy(hdw->mpeg_ctrl_info[idx].desc,qctrl.name,
+			PVR2_CTLD_INFO_DESC_SIZE);
+		hdw->mpeg_ctrl_info[idx].desc[PVR2_CTLD_INFO_DESC_SIZE-1] = 0;
+		ciptr->default_value = qctrl.default_value;
+		switch (qctrl.type) {
+		default:
+		case V4L2_CTRL_TYPE_INTEGER:
+			ciptr->type = pvr2_ctl_int;
+			ciptr->def.type_int.min_value = qctrl.minimum;
+			ciptr->def.type_int.max_value = qctrl.maximum;
+			break;
+		case V4L2_CTRL_TYPE_BOOLEAN:
+			ciptr->type = pvr2_ctl_bool;
+			break;
+		case V4L2_CTRL_TYPE_MENU:
+			ciptr->type = pvr2_ctl_enum;
+			ciptr->def.type_enum.value_names =
+				cx2341x_ctrl_get_menu(&hdw->enc_ctl_state,
+								ciptr->v4l_id);
+			for (cnt1 = 0;
+			     ciptr->def.type_enum.value_names[cnt1] != NULL;
+			     cnt1++) { }
+			ciptr->def.type_enum.count = cnt1;
+			break;
+		}
+		cptr->info = ciptr;
+	}
+
+	// Initialize control data regarding video standard masks
+	valid_std_mask = pvr2_std_get_usable();
+	for (idx = 0; idx < 32; idx++) {
+		if (!(valid_std_mask & (1 << idx))) continue;
+		cnt1 = pvr2_std_id_to_str(
+			hdw->std_mask_names[idx],
+			sizeof(hdw->std_mask_names[idx])-1,
+			1 << idx);
+		hdw->std_mask_names[idx][cnt1] = 0;
+	}
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDAVAIL);
+	if (cptr) {
+		memcpy(&hdw->std_info_avail,cptr->info,
+		       sizeof(hdw->std_info_avail));
+		cptr->info = &hdw->std_info_avail;
+		hdw->std_info_avail.def.type_bitmask.bit_names =
+			hdw->std_mask_ptrs;
+		hdw->std_info_avail.def.type_bitmask.valid_bits =
+			valid_std_mask;
+	}
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR);
+	if (cptr) {
+		memcpy(&hdw->std_info_cur,cptr->info,
+		       sizeof(hdw->std_info_cur));
+		cptr->info = &hdw->std_info_cur;
+		hdw->std_info_cur.def.type_bitmask.bit_names =
+			hdw->std_mask_ptrs;
+		hdw->std_info_cur.def.type_bitmask.valid_bits =
+			valid_std_mask;
+	}
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDDETECT);
+	if (cptr) {
+		memcpy(&hdw->std_info_detect,cptr->info,
+		       sizeof(hdw->std_info_detect));
+		cptr->info = &hdw->std_info_detect;
+		hdw->std_info_detect.def.type_bitmask.bit_names =
+			hdw->std_mask_ptrs;
+		hdw->std_info_detect.def.type_bitmask.valid_bits =
+			valid_std_mask;
+	}
+
+	hdw->cropcap_stale = !0;
+	hdw->eeprom_addr = -1;
+	hdw->unit_number = -1;
+	hdw->v4l_minor_number_video = -1;
+	hdw->v4l_minor_number_vbi = -1;
+	hdw->v4l_minor_number_radio = -1;
+	hdw->ctl_write_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL);
+	if (!hdw->ctl_write_buffer) goto fail;
+	hdw->ctl_read_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL);
+	if (!hdw->ctl_read_buffer) goto fail;
+	hdw->ctl_write_urb = usb_alloc_urb(0,GFP_KERNEL);
+	if (!hdw->ctl_write_urb) goto fail;
+	hdw->ctl_read_urb = usb_alloc_urb(0,GFP_KERNEL);
+	if (!hdw->ctl_read_urb) goto fail;
+
+	if (v4l2_device_register(&intf->dev, &hdw->v4l2_dev) != 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Error registering with v4l core, giving up");
+		goto fail;
+	}
+	mutex_lock(&pvr2_unit_mtx);
+	do {
+		for (idx = 0; idx < PVR_NUM; idx++) {
+			if (unit_pointers[idx]) continue;
+			hdw->unit_number = idx;
+			unit_pointers[idx] = hdw;
+			break;
+		}
+	} while (0);
+	mutex_unlock(&pvr2_unit_mtx);
+
+	cnt1 = 0;
+	cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"pvrusb2");
+	cnt1 += cnt2;
+	if (hdw->unit_number >= 0) {
+		cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"_%c",
+				 ('a' + hdw->unit_number));
+		cnt1 += cnt2;
+	}
+	if (cnt1 >= sizeof(hdw->name)) cnt1 = sizeof(hdw->name)-1;
+	hdw->name[cnt1] = 0;
+
+	INIT_WORK(&hdw->workpoll,pvr2_hdw_worker_poll);
+
+	pvr2_trace(PVR2_TRACE_INIT,"Driver unit number is %d, name is %s",
+		   hdw->unit_number,hdw->name);
+
+	hdw->tuner_type = -1;
+	hdw->flag_ok = !0;
+
+	hdw->usb_intf = intf;
+	hdw->usb_dev = usb_dev;
+
+	usb_make_path(hdw->usb_dev, hdw->bus_info, sizeof(hdw->bus_info));
+
+	ifnum = hdw->usb_intf->cur_altsetting->desc.bInterfaceNumber;
+	usb_set_interface(hdw->usb_dev,ifnum,0);
+
+	mutex_init(&hdw->ctl_lock_mutex);
+	mutex_init(&hdw->big_lock_mutex);
+
+	return hdw;
+ fail:
+	if (hdw) {
+		del_timer_sync(&hdw->quiescent_timer);
+		del_timer_sync(&hdw->decoder_stabilization_timer);
+		del_timer_sync(&hdw->encoder_run_timer);
+		del_timer_sync(&hdw->encoder_wait_timer);
+		flush_work(&hdw->workpoll);
+		usb_free_urb(hdw->ctl_read_urb);
+		usb_free_urb(hdw->ctl_write_urb);
+		kfree(hdw->ctl_read_buffer);
+		kfree(hdw->ctl_write_buffer);
+		kfree(hdw->controls);
+		kfree(hdw->mpeg_ctrl_info);
+		kfree(hdw);
+	}
+	return NULL;
+}
+
+
+/* Remove _all_ associations between this driver and the underlying USB
+   layer. */
+static void pvr2_hdw_remove_usb_stuff(struct pvr2_hdw *hdw)
+{
+	if (hdw->flag_disconnected) return;
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_remove_usb_stuff: hdw=%p",hdw);
+	if (hdw->ctl_read_urb) {
+		usb_kill_urb(hdw->ctl_read_urb);
+		usb_free_urb(hdw->ctl_read_urb);
+		hdw->ctl_read_urb = NULL;
+	}
+	if (hdw->ctl_write_urb) {
+		usb_kill_urb(hdw->ctl_write_urb);
+		usb_free_urb(hdw->ctl_write_urb);
+		hdw->ctl_write_urb = NULL;
+	}
+	if (hdw->ctl_read_buffer) {
+		kfree(hdw->ctl_read_buffer);
+		hdw->ctl_read_buffer = NULL;
+	}
+	if (hdw->ctl_write_buffer) {
+		kfree(hdw->ctl_write_buffer);
+		hdw->ctl_write_buffer = NULL;
+	}
+	hdw->flag_disconnected = !0;
+	/* If we don't do this, then there will be a dangling struct device
+	   reference to our disappearing device persisting inside the V4L
+	   core... */
+	v4l2_device_disconnect(&hdw->v4l2_dev);
+	hdw->usb_dev = NULL;
+	hdw->usb_intf = NULL;
+	pvr2_hdw_render_useless(hdw);
+}
+
+void pvr2_hdw_set_v4l2_dev(struct pvr2_hdw *hdw, struct video_device *vdev)
+{
+	vdev->v4l2_dev = &hdw->v4l2_dev;
+}
+
+/* Destroy hardware interaction structure */
+void pvr2_hdw_destroy(struct pvr2_hdw *hdw)
+{
+	if (!hdw) return;
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_destroy: hdw=%p",hdw);
+	flush_work(&hdw->workpoll);
+	del_timer_sync(&hdw->quiescent_timer);
+	del_timer_sync(&hdw->decoder_stabilization_timer);
+	del_timer_sync(&hdw->encoder_run_timer);
+	del_timer_sync(&hdw->encoder_wait_timer);
+	if (hdw->fw_buffer) {
+		kfree(hdw->fw_buffer);
+		hdw->fw_buffer = NULL;
+	}
+	if (hdw->vid_stream) {
+		pvr2_stream_destroy(hdw->vid_stream);
+		hdw->vid_stream = NULL;
+	}
+	pvr2_i2c_core_done(hdw);
+	v4l2_device_unregister(&hdw->v4l2_dev);
+	pvr2_hdw_remove_usb_stuff(hdw);
+	mutex_lock(&pvr2_unit_mtx);
+	do {
+		if ((hdw->unit_number >= 0) &&
+		    (hdw->unit_number < PVR_NUM) &&
+		    (unit_pointers[hdw->unit_number] == hdw)) {
+			unit_pointers[hdw->unit_number] = NULL;
+		}
+	} while (0);
+	mutex_unlock(&pvr2_unit_mtx);
+	kfree(hdw->controls);
+	kfree(hdw->mpeg_ctrl_info);
+	kfree(hdw);
+}
+
+
+int pvr2_hdw_dev_ok(struct pvr2_hdw *hdw)
+{
+	return (hdw && hdw->flag_ok);
+}
+
+
+/* Called when hardware has been unplugged */
+void pvr2_hdw_disconnect(struct pvr2_hdw *hdw)
+{
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_disconnect(hdw=%p)",hdw);
+	LOCK_TAKE(hdw->big_lock);
+	LOCK_TAKE(hdw->ctl_lock);
+	pvr2_hdw_remove_usb_stuff(hdw);
+	LOCK_GIVE(hdw->ctl_lock);
+	LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Get the number of defined controls */
+unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *hdw)
+{
+	return hdw->control_cnt;
+}
+
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *hdw,
+					     unsigned int idx)
+{
+	if (idx >= hdw->control_cnt) return NULL;
+	return hdw->controls + idx;
+}
+
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *hdw,
+					  unsigned int ctl_id)
+{
+	struct pvr2_ctrl *cptr;
+	unsigned int idx;
+	int i;
+
+	/* This could be made a lot more efficient, but for now... */
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		i = cptr->info->internal_id;
+		if (i && (i == ctl_id)) return cptr;
+	}
+	return NULL;
+}
+
+
+/* Given a V4L ID, retrieve the control structure associated with it. */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *hdw,unsigned int ctl_id)
+{
+	struct pvr2_ctrl *cptr;
+	unsigned int idx;
+	int i;
+
+	/* This could be made a lot more efficient, but for now... */
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		i = cptr->info->v4l_id;
+		if (i && (i == ctl_id)) return cptr;
+	}
+	return NULL;
+}
+
+
+/* Given a V4L ID for its immediate predecessor, retrieve the control
+   structure associated with it. */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *hdw,
+					    unsigned int ctl_id)
+{
+	struct pvr2_ctrl *cptr,*cp2;
+	unsigned int idx;
+	int i;
+
+	/* This could be made a lot more efficient, but for now... */
+	cp2 = NULL;
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		i = cptr->info->v4l_id;
+		if (!i) continue;
+		if (i <= ctl_id) continue;
+		if (cp2 && (cp2->info->v4l_id < i)) continue;
+		cp2 = cptr;
+	}
+	return cp2;
+	return NULL;
+}
+
+
+static const char *get_ctrl_typename(enum pvr2_ctl_type tp)
+{
+	switch (tp) {
+	case pvr2_ctl_int: return "integer";
+	case pvr2_ctl_enum: return "enum";
+	case pvr2_ctl_bool: return "boolean";
+	case pvr2_ctl_bitmask: return "bitmask";
+	}
+	return "";
+}
+
+
+static void pvr2_subdev_set_control(struct pvr2_hdw *hdw, int id,
+				    const char *name, int val)
+{
+	struct v4l2_control ctrl;
+	struct v4l2_subdev *sd;
+
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 %s=%d", name, val);
+	memset(&ctrl, 0, sizeof(ctrl));
+	ctrl.id = id;
+	ctrl.value = val;
+
+	v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev)
+		v4l2_s_ctrl(NULL, sd->ctrl_handler, &ctrl);
+}
+
+#define PVR2_SUBDEV_SET_CONTROL(hdw, id, lab) \
+	if ((hdw)->lab##_dirty || (hdw)->force_dirty) {		\
+		pvr2_subdev_set_control(hdw, id, #lab, (hdw)->lab##_val); \
+	}
+
+static v4l2_std_id pvr2_hdw_get_detected_std(struct pvr2_hdw *hdw)
+{
+	v4l2_std_id std;
+	std = (v4l2_std_id)hdw->std_mask_avail;
+	v4l2_device_call_all(&hdw->v4l2_dev, 0,
+			     video, querystd, &std);
+	return std;
+}
+
+/* Execute whatever commands are required to update the state of all the
+   sub-devices so that they match our current control values. */
+static void pvr2_subdev_update(struct pvr2_hdw *hdw)
+{
+	struct v4l2_subdev *sd;
+	unsigned int id;
+	pvr2_subdev_update_func fp;
+
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev update...");
+
+	if (hdw->tuner_updated || hdw->force_dirty) {
+		struct tuner_setup setup;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev tuner set_type(%d)",
+			   hdw->tuner_type);
+		if (((int)(hdw->tuner_type)) >= 0) {
+			memset(&setup, 0, sizeof(setup));
+			setup.addr = ADDR_UNSET;
+			setup.type = hdw->tuner_type;
+			setup.mode_mask = T_RADIO | T_ANALOG_TV;
+			v4l2_device_call_all(&hdw->v4l2_dev, 0,
+					     tuner, s_type_addr, &setup);
+		}
+	}
+
+	if (hdw->input_dirty || hdw->std_dirty || hdw->force_dirty) {
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_standard");
+		if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+			v4l2_device_call_all(&hdw->v4l2_dev, 0,
+					     tuner, s_radio);
+		} else {
+			v4l2_std_id vs;
+			vs = hdw->std_mask_cur;
+			v4l2_device_call_all(&hdw->v4l2_dev, 0,
+					     video, s_std, vs);
+			pvr2_hdw_cx25840_vbi_hack(hdw);
+		}
+		hdw->tuner_signal_stale = !0;
+		hdw->cropcap_stale = !0;
+	}
+
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_BRIGHTNESS, brightness);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_CONTRAST, contrast);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_SATURATION, saturation);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_HUE, hue);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_MUTE, mute);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_VOLUME, volume);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_BALANCE, balance);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_BASS, bass);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_TREBLE, treble);
+
+	if (hdw->input_dirty || hdw->audiomode_dirty || hdw->force_dirty) {
+		struct v4l2_tuner vt;
+		memset(&vt, 0, sizeof(vt));
+		vt.type = (hdw->input_val == PVR2_CVAL_INPUT_RADIO) ?
+			V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+		vt.audmode = hdw->audiomode_val;
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, s_tuner, &vt);
+	}
+
+	if (hdw->freqDirty || hdw->force_dirty) {
+		unsigned long fv;
+		struct v4l2_frequency freq;
+		fv = pvr2_hdw_get_cur_freq(hdw);
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_freq(%lu)", fv);
+		if (hdw->tuner_signal_stale) pvr2_hdw_status_poll(hdw);
+		memset(&freq, 0, sizeof(freq));
+		if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) {
+			/* ((fv * 1000) / 62500) */
+			freq.frequency = (fv * 2) / 125;
+		} else {
+			freq.frequency = fv / 62500;
+		}
+		/* tuner-core currently doesn't seem to care about this, but
+		   let's set it anyway for completeness. */
+		if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+			freq.type = V4L2_TUNER_RADIO;
+		} else {
+			freq.type = V4L2_TUNER_ANALOG_TV;
+		}
+		freq.tuner = 0;
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner,
+				     s_frequency, &freq);
+	}
+
+	if (hdw->res_hor_dirty || hdw->res_ver_dirty || hdw->force_dirty) {
+		struct v4l2_subdev_format format = {
+			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		};
+
+		format.format.width = hdw->res_hor_val;
+		format.format.height = hdw->res_ver_val;
+		format.format.code = MEDIA_BUS_FMT_FIXED;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_size(%dx%d)",
+			   format.format.width, format.format.height);
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, pad, set_fmt,
+				     NULL, &format);
+	}
+
+	if (hdw->srate_dirty || hdw->force_dirty) {
+		u32 val;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_audio %d",
+			   hdw->srate_val);
+		switch (hdw->srate_val) {
+		default:
+		case V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000:
+			val = 48000;
+			break;
+		case V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100:
+			val = 44100;
+			break;
+		case V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000:
+			val = 32000;
+			break;
+		}
+		v4l2_device_call_all(&hdw->v4l2_dev, 0,
+				     audio, s_clock_freq, val);
+	}
+
+	/* Unable to set crop parameters; there is apparently no equivalent
+	   for VIDIOC_S_CROP */
+
+	v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev) {
+		id = sd->grp_id;
+		if (id >= ARRAY_SIZE(pvr2_module_update_functions)) continue;
+		fp = pvr2_module_update_functions[id];
+		if (!fp) continue;
+		(*fp)(hdw, sd);
+	}
+
+	if (hdw->tuner_signal_stale || hdw->cropcap_stale) {
+		pvr2_hdw_status_poll(hdw);
+	}
+}
+
+
+/* Figure out if we need to commit control changes.  If so, mark internal
+   state flags to indicate this fact and return true.  Otherwise do nothing
+   else and return false. */
+static int pvr2_hdw_commit_setup(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int value;
+	int commit_flag = hdw->force_dirty;
+	char buf[100];
+	unsigned int bcnt,ccnt;
+
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		if (!cptr->info->is_dirty) continue;
+		if (!cptr->info->is_dirty(cptr)) continue;
+		commit_flag = !0;
+
+		if (!(pvrusb2_debug & PVR2_TRACE_CTL)) continue;
+		bcnt = scnprintf(buf,sizeof(buf),"\"%s\" <-- ",
+				 cptr->info->name);
+		value = 0;
+		cptr->info->get_value(cptr,&value);
+		pvr2_ctrl_value_to_sym_internal(cptr,~0,value,
+						buf+bcnt,
+						sizeof(buf)-bcnt,&ccnt);
+		bcnt += ccnt;
+		bcnt += scnprintf(buf+bcnt,sizeof(buf)-bcnt," <%s>",
+				  get_ctrl_typename(cptr->info->type));
+		pvr2_trace(PVR2_TRACE_CTL,
+			   "/*--TRACE_COMMIT--*/ %.*s",
+			   bcnt,buf);
+	}
+
+	if (!commit_flag) {
+		/* Nothing has changed */
+		return 0;
+	}
+
+	hdw->state_pipeline_config = 0;
+	trace_stbit("state_pipeline_config",hdw->state_pipeline_config);
+	pvr2_hdw_state_sched(hdw);
+
+	return !0;
+}
+
+
+/* Perform all operations needed to commit all control changes.  This must
+   be performed in synchronization with the pipeline state and is thus
+   expected to be called as part of the driver's worker thread.  Return
+   true if commit successful, otherwise return false to indicate that
+   commit isn't possible at this time. */
+static int pvr2_hdw_commit_execute(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int disruptive_change;
+
+	if (hdw->input_dirty && hdw->state_pathway_ok &&
+	    (((hdw->input_val == PVR2_CVAL_INPUT_DTV) ?
+	      PVR2_PATHWAY_DIGITAL : PVR2_PATHWAY_ANALOG) !=
+	     hdw->pathway_state)) {
+		/* Change of mode being asked for... */
+		hdw->state_pathway_ok = 0;
+		trace_stbit("state_pathway_ok", hdw->state_pathway_ok);
+	}
+	if (!hdw->state_pathway_ok) {
+		/* Can't commit anything until pathway is ok. */
+		return 0;
+	}
+
+	/* Handle some required side effects when the video standard is
+	   changed.... */
+	if (hdw->std_dirty) {
+		int nvres;
+		int gop_size;
+		if (hdw->std_mask_cur & V4L2_STD_525_60) {
+			nvres = 480;
+			gop_size = 15;
+		} else {
+			nvres = 576;
+			gop_size = 12;
+		}
+		/* Rewrite the vertical resolution to be appropriate to the
+		   video standard that has been selected. */
+		if (nvres != hdw->res_ver_val) {
+			hdw->res_ver_val = nvres;
+			hdw->res_ver_dirty = !0;
+		}
+		/* Rewrite the GOP size to be appropriate to the video
+		   standard that has been selected. */
+		if (gop_size != hdw->enc_ctl_state.video_gop_size) {
+			struct v4l2_ext_controls cs;
+			struct v4l2_ext_control c1;
+			memset(&cs, 0, sizeof(cs));
+			memset(&c1, 0, sizeof(c1));
+			cs.controls = &c1;
+			cs.count = 1;
+			c1.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE;
+			c1.value = gop_size;
+			cx2341x_ext_ctrls(&hdw->enc_ctl_state, 0, &cs,
+					  VIDIOC_S_EXT_CTRLS);
+		}
+	}
+
+	/* The broadcast decoder can only scale down, so if
+	 * res_*_dirty && crop window < output format ==> enlarge crop.
+	 *
+	 * The mpeg encoder receives fields of res_hor_val dots and
+	 * res_ver_val halflines.  Limits: hor<=720, ver<=576.
+	 */
+	if (hdw->res_hor_dirty && hdw->cropw_val < hdw->res_hor_val) {
+		hdw->cropw_val = hdw->res_hor_val;
+		hdw->cropw_dirty = !0;
+	} else if (hdw->cropw_dirty) {
+		hdw->res_hor_dirty = !0;           /* must rescale */
+		hdw->res_hor_val = min(720, hdw->cropw_val);
+	}
+	if (hdw->res_ver_dirty && hdw->croph_val < hdw->res_ver_val) {
+		hdw->croph_val = hdw->res_ver_val;
+		hdw->croph_dirty = !0;
+	} else if (hdw->croph_dirty) {
+		int nvres = hdw->std_mask_cur & V4L2_STD_525_60 ? 480 : 576;
+		hdw->res_ver_dirty = !0;
+		hdw->res_ver_val = min(nvres, hdw->croph_val);
+	}
+
+	/* If any of the below has changed, then we can't do the update
+	   while the pipeline is running.  Pipeline must be paused first
+	   and decoder -> encoder connection be made quiescent before we
+	   can proceed. */
+	disruptive_change =
+		(hdw->std_dirty ||
+		 hdw->enc_unsafe_stale ||
+		 hdw->srate_dirty ||
+		 hdw->res_ver_dirty ||
+		 hdw->res_hor_dirty ||
+		 hdw->cropw_dirty ||
+		 hdw->croph_dirty ||
+		 hdw->input_dirty ||
+		 (hdw->active_stream_type != hdw->desired_stream_type));
+	if (disruptive_change && !hdw->state_pipeline_idle) {
+		/* Pipeline is not idle; we can't proceed.  Arrange to
+		   cause pipeline to stop so that we can try this again
+		   later.... */
+		hdw->state_pipeline_pause = !0;
+		return 0;
+	}
+
+	if (hdw->srate_dirty) {
+		/* Write new sample rate into control structure since
+		 * the master copy is stale.  We must track srate
+		 * separate from the mpeg control structure because
+		 * other logic also uses this value. */
+		struct v4l2_ext_controls cs;
+		struct v4l2_ext_control c1;
+		memset(&cs,0,sizeof(cs));
+		memset(&c1,0,sizeof(c1));
+		cs.controls = &c1;
+		cs.count = 1;
+		c1.id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ;
+		c1.value = hdw->srate_val;
+		cx2341x_ext_ctrls(&hdw->enc_ctl_state, 0, &cs,VIDIOC_S_EXT_CTRLS);
+	}
+
+	if (hdw->active_stream_type != hdw->desired_stream_type) {
+		/* Handle any side effects of stream config here */
+		hdw->active_stream_type = hdw->desired_stream_type;
+	}
+
+	if (hdw->hdw_desc->signal_routing_scheme ==
+	    PVR2_ROUTING_SCHEME_GOTVIEW) {
+		u32 b;
+		/* Handle GOTVIEW audio switching */
+		pvr2_hdw_gpio_get_out(hdw,&b);
+		if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+			/* Set GPIO 11 */
+			pvr2_hdw_gpio_chg_out(hdw,(1 << 11),~0);
+		} else {
+			/* Clear GPIO 11 */
+			pvr2_hdw_gpio_chg_out(hdw,(1 << 11),0);
+		}
+	}
+
+	/* Check and update state for all sub-devices. */
+	pvr2_subdev_update(hdw);
+
+	hdw->tuner_updated = 0;
+	hdw->force_dirty = 0;
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		if (!cptr->info->clear_dirty) continue;
+		cptr->info->clear_dirty(cptr);
+	}
+
+	if ((hdw->pathway_state == PVR2_PATHWAY_ANALOG) &&
+	    hdw->state_encoder_run) {
+		/* If encoder isn't running or it can't be touched, then
+		   this will get worked out later when we start the
+		   encoder. */
+		if (pvr2_encoder_adjust(hdw) < 0) return !0;
+	}
+
+	hdw->state_pipeline_config = !0;
+	/* Hardware state may have changed in a way to cause the cropping
+	   capabilities to have changed.  So mark it stale, which will
+	   cause a later re-fetch. */
+	trace_stbit("state_pipeline_config",hdw->state_pipeline_config);
+	return !0;
+}
+
+
+int pvr2_hdw_commit_ctl(struct pvr2_hdw *hdw)
+{
+	int fl;
+	LOCK_TAKE(hdw->big_lock);
+	fl = pvr2_hdw_commit_setup(hdw);
+	LOCK_GIVE(hdw->big_lock);
+	if (!fl) return 0;
+	return pvr2_hdw_wait(hdw,0);
+}
+
+
+static void pvr2_hdw_worker_poll(struct work_struct *work)
+{
+	int fl = 0;
+	struct pvr2_hdw *hdw = container_of(work,struct pvr2_hdw,workpoll);
+	LOCK_TAKE(hdw->big_lock); do {
+		fl = pvr2_hdw_state_eval(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	if (fl && hdw->state_func) {
+		hdw->state_func(hdw->state_data);
+	}
+}
+
+
+static int pvr2_hdw_wait(struct pvr2_hdw *hdw,int state)
+{
+	return wait_event_interruptible(
+		hdw->state_wait_data,
+		(hdw->state_stale == 0) &&
+		(!state || (hdw->master_state != state)));
+}
+
+
+/* Return name for this driver instance */
+const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *hdw)
+{
+	return hdw->name;
+}
+
+
+const char *pvr2_hdw_get_desc(struct pvr2_hdw *hdw)
+{
+	return hdw->hdw_desc->description;
+}
+
+
+const char *pvr2_hdw_get_type(struct pvr2_hdw *hdw)
+{
+	return hdw->hdw_desc->shortname;
+}
+
+
+int pvr2_hdw_is_hsm(struct pvr2_hdw *hdw)
+{
+	int result;
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_USB_SPEED;
+		result = pvr2_send_request(hdw,
+					   hdw->cmd_buffer,1,
+					   hdw->cmd_buffer,1);
+		if (result < 0) break;
+		result = (hdw->cmd_buffer[0] != 0);
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+	return result;
+}
+
+
+/* Execute poll of tuner status */
+void pvr2_hdw_execute_tuner_poll(struct pvr2_hdw *hdw)
+{
+	LOCK_TAKE(hdw->big_lock); do {
+		pvr2_hdw_status_poll(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+static int pvr2_hdw_check_cropcap(struct pvr2_hdw *hdw)
+{
+	if (!hdw->cropcap_stale) {
+		return 0;
+	}
+	pvr2_hdw_status_poll(hdw);
+	if (hdw->cropcap_stale) {
+		return -EIO;
+	}
+	return 0;
+}
+
+
+/* Return information about cropping capabilities */
+int pvr2_hdw_get_cropcap(struct pvr2_hdw *hdw, struct v4l2_cropcap *pp)
+{
+	int stat = 0;
+	LOCK_TAKE(hdw->big_lock);
+	stat = pvr2_hdw_check_cropcap(hdw);
+	if (!stat) {
+		memcpy(pp, &hdw->cropcap_info, sizeof(hdw->cropcap_info));
+	}
+	LOCK_GIVE(hdw->big_lock);
+	return stat;
+}
+
+
+/* Return information about the tuner */
+int pvr2_hdw_get_tuner_status(struct pvr2_hdw *hdw,struct v4l2_tuner *vtp)
+{
+	LOCK_TAKE(hdw->big_lock); do {
+		if (hdw->tuner_signal_stale) {
+			pvr2_hdw_status_poll(hdw);
+		}
+		memcpy(vtp,&hdw->tuner_signal_info,sizeof(struct v4l2_tuner));
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	return 0;
+}
+
+
+/* Get handle to video output stream */
+struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *hp)
+{
+	return hp->vid_stream;
+}
+
+
+void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw)
+{
+	int nr = pvr2_hdw_get_unit_number(hdw);
+	LOCK_TAKE(hdw->big_lock);
+	do {
+		printk(KERN_INFO "pvrusb2: =================  START STATUS CARD #%d  =================\n", nr);
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, core, log_status);
+		pvr2_trace(PVR2_TRACE_INFO,"cx2341x config:");
+		cx2341x_log_status(&hdw->enc_ctl_state, "pvrusb2");
+		pvr2_hdw_state_log_state(hdw);
+		printk(KERN_INFO "pvrusb2: ==================  END STATUS CARD #%d  ==================\n", nr);
+	} while (0);
+	LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Grab EEPROM contents, needed for direct method. */
+#define EEPROM_SIZE 8192
+#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__)
+static u8 *pvr2_full_eeprom_fetch(struct pvr2_hdw *hdw)
+{
+	struct i2c_msg msg[2];
+	u8 *eeprom;
+	u8 iadd[2];
+	u8 addr;
+	u16 eepromSize;
+	unsigned int offs;
+	int ret;
+	int mode16 = 0;
+	unsigned pcnt,tcnt;
+	eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL);
+	if (!eeprom) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to allocate memory required to read eeprom");
+		return NULL;
+	}
+
+	trace_eeprom("Value for eeprom addr from controller was 0x%x",
+		     hdw->eeprom_addr);
+	addr = hdw->eeprom_addr;
+	/* Seems that if the high bit is set, then the *real* eeprom
+	   address is shifted right now bit position (noticed this in
+	   newer PVR USB2 hardware) */
+	if (addr & 0x80) addr >>= 1;
+
+	/* FX2 documentation states that a 16bit-addressed eeprom is
+	   expected if the I2C address is an odd number (yeah, this is
+	   strange but it's what they do) */
+	mode16 = (addr & 1);
+	eepromSize = (mode16 ? EEPROM_SIZE : 256);
+	trace_eeprom("Examining %d byte eeprom at location 0x%x using %d bit addressing",
+		     eepromSize, addr,
+		     mode16 ? 16 : 8);
+
+	msg[0].addr = addr;
+	msg[0].flags = 0;
+	msg[0].len = mode16 ? 2 : 1;
+	msg[0].buf = iadd;
+	msg[1].addr = addr;
+	msg[1].flags = I2C_M_RD;
+
+	/* We have to do the actual eeprom data fetch ourselves, because
+	   (1) we're only fetching part of the eeprom, and (2) if we were
+	   getting the whole thing our I2C driver can't grab it in one
+	   pass - which is what tveeprom is otherwise going to attempt */
+	memset(eeprom,0,EEPROM_SIZE);
+	for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) {
+		pcnt = 16;
+		if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt;
+		offs = tcnt + (eepromSize - EEPROM_SIZE);
+		if (mode16) {
+			iadd[0] = offs >> 8;
+			iadd[1] = offs;
+		} else {
+			iadd[0] = offs;
+		}
+		msg[1].len = pcnt;
+		msg[1].buf = eeprom+tcnt;
+		if ((ret = i2c_transfer(&hdw->i2c_adap,
+					msg,ARRAY_SIZE(msg))) != 2) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "eeprom fetch set offs err=%d",ret);
+			kfree(eeprom);
+			return NULL;
+		}
+	}
+	return eeprom;
+}
+
+
+void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *hdw,
+				int mode,
+				int enable_flag)
+{
+	int ret;
+	u16 address;
+	unsigned int pipe;
+	LOCK_TAKE(hdw->big_lock); do {
+		if ((hdw->fw_buffer == NULL) == !enable_flag) break;
+
+		if (!enable_flag) {
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Cleaning up after CPU firmware fetch");
+			kfree(hdw->fw_buffer);
+			hdw->fw_buffer = NULL;
+			hdw->fw_size = 0;
+			if (hdw->fw_cpu_flag) {
+				/* Now release the CPU.  It will disconnect
+				   and reconnect later. */
+				pvr2_hdw_cpureset_assert(hdw,0);
+			}
+			break;
+		}
+
+		hdw->fw_cpu_flag = (mode != 2);
+		if (hdw->fw_cpu_flag) {
+			hdw->fw_size = (mode == 1) ? 0x4000 : 0x2000;
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Preparing to suck out CPU firmware (size=%u)",
+				   hdw->fw_size);
+			hdw->fw_buffer = kzalloc(hdw->fw_size,GFP_KERNEL);
+			if (!hdw->fw_buffer) {
+				hdw->fw_size = 0;
+				break;
+			}
+
+			/* We have to hold the CPU during firmware upload. */
+			pvr2_hdw_cpureset_assert(hdw,1);
+
+			/* download the firmware from address 0000-1fff in 2048
+			   (=0x800) bytes chunk. */
+
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Grabbing CPU firmware");
+			pipe = usb_rcvctrlpipe(hdw->usb_dev, 0);
+			for(address = 0; address < hdw->fw_size;
+			    address += 0x800) {
+				ret = usb_control_msg(hdw->usb_dev,pipe,
+						      0xa0,0xc0,
+						      address,0,
+						      hdw->fw_buffer+address,
+						      0x800,HZ);
+				if (ret < 0) break;
+			}
+
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Done grabbing CPU firmware");
+		} else {
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Sucking down EEPROM contents");
+			hdw->fw_buffer = pvr2_full_eeprom_fetch(hdw);
+			if (!hdw->fw_buffer) {
+				pvr2_trace(PVR2_TRACE_FIRMWARE,
+					   "EEPROM content suck failed.");
+				break;
+			}
+			hdw->fw_size = EEPROM_SIZE;
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Done sucking down EEPROM contents");
+		}
+
+	} while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Return true if we're in a mode for retrieval CPU firmware */
+int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *hdw)
+{
+	return hdw->fw_buffer != NULL;
+}
+
+
+int pvr2_hdw_cpufw_get(struct pvr2_hdw *hdw,unsigned int offs,
+		       char *buf,unsigned int cnt)
+{
+	int ret = -EINVAL;
+	LOCK_TAKE(hdw->big_lock); do {
+		if (!buf) break;
+		if (!cnt) break;
+
+		if (!hdw->fw_buffer) {
+			ret = -EIO;
+			break;
+		}
+
+		if (offs >= hdw->fw_size) {
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Read firmware data offs=%d EOF",
+				   offs);
+			ret = 0;
+			break;
+		}
+
+		if (offs + cnt > hdw->fw_size) cnt = hdw->fw_size - offs;
+
+		memcpy(buf,hdw->fw_buffer+offs,cnt);
+
+		pvr2_trace(PVR2_TRACE_FIRMWARE,
+			   "Read firmware data offs=%d cnt=%d",
+			   offs,cnt);
+		ret = cnt;
+	} while (0); LOCK_GIVE(hdw->big_lock);
+
+	return ret;
+}
+
+
+int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *hdw,
+				  enum pvr2_v4l_type index)
+{
+	switch (index) {
+	case pvr2_v4l_type_video: return hdw->v4l_minor_number_video;
+	case pvr2_v4l_type_vbi: return hdw->v4l_minor_number_vbi;
+	case pvr2_v4l_type_radio: return hdw->v4l_minor_number_radio;
+	default: return -1;
+	}
+}
+
+
+/* Store a v4l minor device number */
+void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *hdw,
+				     enum pvr2_v4l_type index,int v)
+{
+	switch (index) {
+	case pvr2_v4l_type_video: hdw->v4l_minor_number_video = v;break;
+	case pvr2_v4l_type_vbi: hdw->v4l_minor_number_vbi = v;break;
+	case pvr2_v4l_type_radio: hdw->v4l_minor_number_radio = v;break;
+	default: break;
+	}
+}
+
+
+static void pvr2_ctl_write_complete(struct urb *urb)
+{
+	struct pvr2_hdw *hdw = urb->context;
+	hdw->ctl_write_pend_flag = 0;
+	if (hdw->ctl_read_pend_flag) return;
+	complete(&hdw->ctl_done);
+}
+
+
+static void pvr2_ctl_read_complete(struct urb *urb)
+{
+	struct pvr2_hdw *hdw = urb->context;
+	hdw->ctl_read_pend_flag = 0;
+	if (hdw->ctl_write_pend_flag) return;
+	complete(&hdw->ctl_done);
+}
+
+struct hdw_timer {
+	struct timer_list timer;
+	struct pvr2_hdw *hdw;
+};
+
+static void pvr2_ctl_timeout(struct timer_list *t)
+{
+	struct hdw_timer *timer = from_timer(timer, t, timer);
+	struct pvr2_hdw *hdw = timer->hdw;
+
+	if (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) {
+		hdw->ctl_timeout_flag = !0;
+		if (hdw->ctl_write_pend_flag)
+			usb_unlink_urb(hdw->ctl_write_urb);
+		if (hdw->ctl_read_pend_flag)
+			usb_unlink_urb(hdw->ctl_read_urb);
+	}
+}
+
+
+/* Issue a command and get a response from the device.  This extended
+   version includes a probe flag (which if set means that device errors
+   should not be logged or treated as fatal) and a timeout in jiffies.
+   This can be used to non-lethally probe the health of endpoint 1. */
+static int pvr2_send_request_ex(struct pvr2_hdw *hdw,
+				unsigned int timeout,int probe_fl,
+				void *write_data,unsigned int write_len,
+				void *read_data,unsigned int read_len)
+{
+	unsigned int idx;
+	int status = 0;
+	struct hdw_timer timer = {
+		.hdw = hdw,
+	};
+
+	if (!hdw->ctl_lock_held) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Attempted to execute control transfer without lock!!");
+		return -EDEADLK;
+	}
+	if (!hdw->flag_ok && !probe_fl) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Attempted to execute control transfer when device not ok");
+		return -EIO;
+	}
+	if (!(hdw->ctl_read_urb && hdw->ctl_write_urb)) {
+		if (!probe_fl) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Attempted to execute control transfer when USB is disconnected");
+		}
+		return -ENOTTY;
+	}
+
+	/* Ensure that we have sane parameters */
+	if (!write_data) write_len = 0;
+	if (!read_data) read_len = 0;
+	if (write_len > PVR2_CTL_BUFFSIZE) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Attempted to execute %d byte control-write transfer (limit=%d)",
+			write_len,PVR2_CTL_BUFFSIZE);
+		return -EINVAL;
+	}
+	if (read_len > PVR2_CTL_BUFFSIZE) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Attempted to execute %d byte control-read transfer (limit=%d)",
+			write_len,PVR2_CTL_BUFFSIZE);
+		return -EINVAL;
+	}
+	if ((!write_len) && (!read_len)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Attempted to execute null control transfer?");
+		return -EINVAL;
+	}
+
+
+	hdw->cmd_debug_state = 1;
+	if (write_len && write_data)
+		hdw->cmd_debug_code = ((unsigned char *)write_data)[0];
+	else
+		hdw->cmd_debug_code = 0;
+	hdw->cmd_debug_write_len = write_len;
+	hdw->cmd_debug_read_len = read_len;
+
+	/* Initialize common stuff */
+	init_completion(&hdw->ctl_done);
+	hdw->ctl_timeout_flag = 0;
+	hdw->ctl_write_pend_flag = 0;
+	hdw->ctl_read_pend_flag = 0;
+	timer_setup_on_stack(&timer.timer, pvr2_ctl_timeout, 0);
+	timer.timer.expires = jiffies + timeout;
+
+	if (write_len && write_data) {
+		hdw->cmd_debug_state = 2;
+		/* Transfer write data to internal buffer */
+		for (idx = 0; idx < write_len; idx++) {
+			hdw->ctl_write_buffer[idx] =
+				((unsigned char *)write_data)[idx];
+		}
+		/* Initiate a write request */
+		usb_fill_bulk_urb(hdw->ctl_write_urb,
+				  hdw->usb_dev,
+				  usb_sndbulkpipe(hdw->usb_dev,
+						  PVR2_CTL_WRITE_ENDPOINT),
+				  hdw->ctl_write_buffer,
+				  write_len,
+				  pvr2_ctl_write_complete,
+				  hdw);
+		hdw->ctl_write_urb->actual_length = 0;
+		hdw->ctl_write_pend_flag = !0;
+		if (usb_urb_ep_type_check(hdw->ctl_write_urb)) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Invalid write control endpoint");
+			return -EINVAL;
+		}
+		status = usb_submit_urb(hdw->ctl_write_urb,GFP_KERNEL);
+		if (status < 0) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Failed to submit write-control URB status=%d",
+status);
+			hdw->ctl_write_pend_flag = 0;
+			goto done;
+		}
+	}
+
+	if (read_len) {
+		hdw->cmd_debug_state = 3;
+		memset(hdw->ctl_read_buffer,0x43,read_len);
+		/* Initiate a read request */
+		usb_fill_bulk_urb(hdw->ctl_read_urb,
+				  hdw->usb_dev,
+				  usb_rcvbulkpipe(hdw->usb_dev,
+						  PVR2_CTL_READ_ENDPOINT),
+				  hdw->ctl_read_buffer,
+				  read_len,
+				  pvr2_ctl_read_complete,
+				  hdw);
+		hdw->ctl_read_urb->actual_length = 0;
+		hdw->ctl_read_pend_flag = !0;
+		if (usb_urb_ep_type_check(hdw->ctl_read_urb)) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Invalid read control endpoint");
+			return -EINVAL;
+		}
+		status = usb_submit_urb(hdw->ctl_read_urb,GFP_KERNEL);
+		if (status < 0) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Failed to submit read-control URB status=%d",
+status);
+			hdw->ctl_read_pend_flag = 0;
+			goto done;
+		}
+	}
+
+	/* Start timer */
+	add_timer(&timer.timer);
+
+	/* Now wait for all I/O to complete */
+	hdw->cmd_debug_state = 4;
+	while (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) {
+		wait_for_completion(&hdw->ctl_done);
+	}
+	hdw->cmd_debug_state = 5;
+
+	/* Stop timer */
+	del_timer_sync(&timer.timer);
+
+	hdw->cmd_debug_state = 6;
+	status = 0;
+
+	if (hdw->ctl_timeout_flag) {
+		status = -ETIMEDOUT;
+		if (!probe_fl) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Timed out control-write");
+		}
+		goto done;
+	}
+
+	if (write_len) {
+		/* Validate results of write request */
+		if ((hdw->ctl_write_urb->status != 0) &&
+		    (hdw->ctl_write_urb->status != -ENOENT) &&
+		    (hdw->ctl_write_urb->status != -ESHUTDOWN) &&
+		    (hdw->ctl_write_urb->status != -ECONNRESET)) {
+			/* USB subsystem is reporting some kind of failure
+			   on the write */
+			status = hdw->ctl_write_urb->status;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-write URB failure, status=%d",
+					   status);
+			}
+			goto done;
+		}
+		if (hdw->ctl_write_urb->actual_length < write_len) {
+			/* Failed to write enough data */
+			status = -EIO;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-write URB short, expected=%d got=%d",
+					   write_len,
+					   hdw->ctl_write_urb->actual_length);
+			}
+			goto done;
+		}
+	}
+	if (read_len && read_data) {
+		/* Validate results of read request */
+		if ((hdw->ctl_read_urb->status != 0) &&
+		    (hdw->ctl_read_urb->status != -ENOENT) &&
+		    (hdw->ctl_read_urb->status != -ESHUTDOWN) &&
+		    (hdw->ctl_read_urb->status != -ECONNRESET)) {
+			/* USB subsystem is reporting some kind of failure
+			   on the read */
+			status = hdw->ctl_read_urb->status;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-read URB failure, status=%d",
+					   status);
+			}
+			goto done;
+		}
+		if (hdw->ctl_read_urb->actual_length < read_len) {
+			/* Failed to read enough data */
+			status = -EIO;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-read URB short, expected=%d got=%d",
+					   read_len,
+					   hdw->ctl_read_urb->actual_length);
+			}
+			goto done;
+		}
+		/* Transfer retrieved data out from internal buffer */
+		for (idx = 0; idx < read_len; idx++) {
+			((unsigned char *)read_data)[idx] =
+				hdw->ctl_read_buffer[idx];
+		}
+	}
+
+ done:
+
+	hdw->cmd_debug_state = 0;
+	if ((status < 0) && (!probe_fl)) {
+		pvr2_hdw_render_useless(hdw);
+	}
+	destroy_timer_on_stack(&timer.timer);
+
+	return status;
+}
+
+
+int pvr2_send_request(struct pvr2_hdw *hdw,
+		      void *write_data,unsigned int write_len,
+		      void *read_data,unsigned int read_len)
+{
+	return pvr2_send_request_ex(hdw,HZ*4,0,
+				    write_data,write_len,
+				    read_data,read_len);
+}
+
+
+static int pvr2_issue_simple_cmd(struct pvr2_hdw *hdw,u32 cmdcode)
+{
+	int ret;
+	unsigned int cnt = 1;
+	unsigned int args = 0;
+	LOCK_TAKE(hdw->ctl_lock);
+	hdw->cmd_buffer[0] = cmdcode & 0xffu;
+	args = (cmdcode >> 8) & 0xffu;
+	args = (args > 2) ? 2 : args;
+	if (args) {
+		cnt += args;
+		hdw->cmd_buffer[1] = (cmdcode >> 16) & 0xffu;
+		if (args > 1) {
+			hdw->cmd_buffer[2] = (cmdcode >> 24) & 0xffu;
+		}
+	}
+	if (pvrusb2_debug & PVR2_TRACE_INIT) {
+		unsigned int idx;
+		unsigned int ccnt,bcnt;
+		char tbuf[50];
+		cmdcode &= 0xffu;
+		bcnt = 0;
+		ccnt = scnprintf(tbuf+bcnt,
+				 sizeof(tbuf)-bcnt,
+				 "Sending FX2 command 0x%x",cmdcode);
+		bcnt += ccnt;
+		for (idx = 0; idx < ARRAY_SIZE(pvr2_fx2cmd_desc); idx++) {
+			if (pvr2_fx2cmd_desc[idx].id == cmdcode) {
+				ccnt = scnprintf(tbuf+bcnt,
+						 sizeof(tbuf)-bcnt,
+						 " \"%s\"",
+						 pvr2_fx2cmd_desc[idx].desc);
+				bcnt += ccnt;
+				break;
+			}
+		}
+		if (args) {
+			ccnt = scnprintf(tbuf+bcnt,
+					 sizeof(tbuf)-bcnt,
+					 " (%u",hdw->cmd_buffer[1]);
+			bcnt += ccnt;
+			if (args > 1) {
+				ccnt = scnprintf(tbuf+bcnt,
+						 sizeof(tbuf)-bcnt,
+						 ",%u",hdw->cmd_buffer[2]);
+				bcnt += ccnt;
+			}
+			ccnt = scnprintf(tbuf+bcnt,
+					 sizeof(tbuf)-bcnt,
+					 ")");
+			bcnt += ccnt;
+		}
+		pvr2_trace(PVR2_TRACE_INIT,"%.*s",bcnt,tbuf);
+	}
+	ret = pvr2_send_request(hdw,hdw->cmd_buffer,cnt,NULL,0);
+	LOCK_GIVE(hdw->ctl_lock);
+	return ret;
+}
+
+
+int pvr2_write_register(struct pvr2_hdw *hdw, u16 reg, u32 data)
+{
+	int ret;
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	hdw->cmd_buffer[0] = FX2CMD_REG_WRITE;  /* write register prefix */
+	PVR2_DECOMPOSE_LE(hdw->cmd_buffer,1,data);
+	hdw->cmd_buffer[5] = 0;
+	hdw->cmd_buffer[6] = (reg >> 8) & 0xff;
+	hdw->cmd_buffer[7] = reg & 0xff;
+
+
+	ret = pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 0);
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+
+static int pvr2_read_register(struct pvr2_hdw *hdw, u16 reg, u32 *data)
+{
+	int ret = 0;
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	hdw->cmd_buffer[0] = FX2CMD_REG_READ;  /* read register prefix */
+	hdw->cmd_buffer[1] = 0;
+	hdw->cmd_buffer[2] = 0;
+	hdw->cmd_buffer[3] = 0;
+	hdw->cmd_buffer[4] = 0;
+	hdw->cmd_buffer[5] = 0;
+	hdw->cmd_buffer[6] = (reg >> 8) & 0xff;
+	hdw->cmd_buffer[7] = reg & 0xff;
+
+	ret |= pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 4);
+	*data = PVR2_COMPOSE_LE(hdw->cmd_buffer,0);
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+
+void pvr2_hdw_render_useless(struct pvr2_hdw *hdw)
+{
+	if (!hdw->flag_ok) return;
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "Device being rendered inoperable");
+	if (hdw->vid_stream) {
+		pvr2_stream_setup(hdw->vid_stream,NULL,0,0);
+	}
+	hdw->flag_ok = 0;
+	trace_stbit("flag_ok",hdw->flag_ok);
+	pvr2_hdw_state_sched(hdw);
+}
+
+
+void pvr2_hdw_device_reset(struct pvr2_hdw *hdw)
+{
+	int ret;
+	pvr2_trace(PVR2_TRACE_INIT,"Performing a device reset...");
+	ret = usb_lock_device_for_reset(hdw->usb_dev,NULL);
+	if (ret == 0) {
+		ret = usb_reset_device(hdw->usb_dev);
+		usb_unlock_device(hdw->usb_dev);
+	} else {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to lock USB device ret=%d",ret);
+	}
+	if (init_pause_msec) {
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Waiting %u msec for hardware to settle",
+			   init_pause_msec);
+		msleep(init_pause_msec);
+	}
+
+}
+
+
+void pvr2_hdw_cpureset_assert(struct pvr2_hdw *hdw,int val)
+{
+	char *da;
+	unsigned int pipe;
+	int ret;
+
+	if (!hdw->usb_dev) return;
+
+	da = kmalloc(16, GFP_KERNEL);
+
+	if (da == NULL) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Unable to allocate memory to control CPU reset");
+		return;
+	}
+
+	pvr2_trace(PVR2_TRACE_INIT,"cpureset_assert(%d)",val);
+
+	da[0] = val ? 0x01 : 0x00;
+
+	/* Write the CPUCS register on the 8051.  The lsb of the register
+	   is the reset bit; a 1 asserts reset while a 0 clears it. */
+	pipe = usb_sndctrlpipe(hdw->usb_dev, 0);
+	ret = usb_control_msg(hdw->usb_dev,pipe,0xa0,0x40,0xe600,0,da,1,HZ);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "cpureset_assert(%d) error=%d",val,ret);
+		pvr2_hdw_render_useless(hdw);
+	}
+
+	kfree(da);
+}
+
+
+int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *hdw)
+{
+	return pvr2_issue_simple_cmd(hdw,FX2CMD_DEEP_RESET);
+}
+
+
+int pvr2_hdw_cmd_powerup(struct pvr2_hdw *hdw)
+{
+	return pvr2_issue_simple_cmd(hdw,FX2CMD_POWER_ON);
+}
+
+
+
+int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *hdw)
+{
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Requesting decoder reset");
+	if (hdw->decoder_client_id) {
+		v4l2_device_call_all(&hdw->v4l2_dev, hdw->decoder_client_id,
+				     core, reset, 0);
+		pvr2_hdw_cx25840_vbi_hack(hdw);
+		return 0;
+	}
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Unable to reset decoder: nothing attached");
+	return -ENOTTY;
+}
+
+
+static int pvr2_hdw_cmd_hcw_demod_reset(struct pvr2_hdw *hdw, int onoff)
+{
+	hdw->flag_ok = !0;
+	return pvr2_issue_simple_cmd(hdw,
+				     FX2CMD_HCW_DEMOD_RESETIN |
+				     (1 << 8) |
+				     ((onoff ? 1 : 0) << 16));
+}
+
+
+static int pvr2_hdw_cmd_onair_fe_power_ctrl(struct pvr2_hdw *hdw, int onoff)
+{
+	hdw->flag_ok = !0;
+	return pvr2_issue_simple_cmd(hdw,(onoff ?
+					  FX2CMD_ONAIR_DTV_POWER_ON :
+					  FX2CMD_ONAIR_DTV_POWER_OFF));
+}
+
+
+static int pvr2_hdw_cmd_onair_digital_path_ctrl(struct pvr2_hdw *hdw,
+						int onoff)
+{
+	return pvr2_issue_simple_cmd(hdw,(onoff ?
+					  FX2CMD_ONAIR_DTV_STREAMING_ON :
+					  FX2CMD_ONAIR_DTV_STREAMING_OFF));
+}
+
+
+static void pvr2_hdw_cmd_modeswitch(struct pvr2_hdw *hdw,int digitalFl)
+{
+	int cmode;
+	/* Compare digital/analog desired setting with current setting.  If
+	   they don't match, fix it... */
+	cmode = (digitalFl ? PVR2_PATHWAY_DIGITAL : PVR2_PATHWAY_ANALOG);
+	if (cmode == hdw->pathway_state) {
+		/* They match; nothing to do */
+		return;
+	}
+
+	switch (hdw->hdw_desc->digital_control_scheme) {
+	case PVR2_DIGITAL_SCHEME_HAUPPAUGE:
+		pvr2_hdw_cmd_hcw_demod_reset(hdw,digitalFl);
+		if (cmode == PVR2_PATHWAY_ANALOG) {
+			/* If moving to analog mode, also force the decoder
+			   to reset.  If no decoder is attached, then it's
+			   ok to ignore this because if/when the decoder
+			   attaches, it will reset itself at that time. */
+			pvr2_hdw_cmd_decoder_reset(hdw);
+		}
+		break;
+	case PVR2_DIGITAL_SCHEME_ONAIR:
+		/* Supposedly we should always have the power on whether in
+		   digital or analog mode.  But for now do what appears to
+		   work... */
+		pvr2_hdw_cmd_onair_fe_power_ctrl(hdw,digitalFl);
+		break;
+	default: break;
+	}
+
+	pvr2_hdw_untrip_unlocked(hdw);
+	hdw->pathway_state = cmode;
+}
+
+
+static void pvr2_led_ctrl_hauppauge(struct pvr2_hdw *hdw, int onoff)
+{
+	/* change some GPIO data
+	 *
+	 * note: bit d7 of dir appears to control the LED,
+	 * so we shut it off here.
+	 *
+	 */
+	if (onoff) {
+		pvr2_hdw_gpio_chg_dir(hdw, 0xffffffff, 0x00000481);
+	} else {
+		pvr2_hdw_gpio_chg_dir(hdw, 0xffffffff, 0x00000401);
+	}
+	pvr2_hdw_gpio_chg_out(hdw, 0xffffffff, 0x00000000);
+}
+
+
+typedef void (*led_method_func)(struct pvr2_hdw *,int);
+
+static led_method_func led_methods[] = {
+	[PVR2_LED_SCHEME_HAUPPAUGE] = pvr2_led_ctrl_hauppauge,
+};
+
+
+/* Toggle LED */
+static void pvr2_led_ctrl(struct pvr2_hdw *hdw,int onoff)
+{
+	unsigned int scheme_id;
+	led_method_func fp;
+
+	if ((!onoff) == (!hdw->led_on)) return;
+
+	hdw->led_on = onoff != 0;
+
+	scheme_id = hdw->hdw_desc->led_scheme;
+	if (scheme_id < ARRAY_SIZE(led_methods)) {
+		fp = led_methods[scheme_id];
+	} else {
+		fp = NULL;
+	}
+
+	if (fp) (*fp)(hdw,onoff);
+}
+
+
+/* Stop / start video stream transport */
+static int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl)
+{
+	int ret;
+
+	/* If we're in analog mode, then just issue the usual analog
+	   command. */
+	if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) {
+		return pvr2_issue_simple_cmd(hdw,
+					     (runFl ?
+					      FX2CMD_STREAMING_ON :
+					      FX2CMD_STREAMING_OFF));
+		/*Note: Not reached */
+	}
+
+	if (hdw->pathway_state != PVR2_PATHWAY_DIGITAL) {
+		/* Whoops, we don't know what mode we're in... */
+		return -EINVAL;
+	}
+
+	/* To get here we have to be in digital mode.  The mechanism here
+	   is unfortunately different for different vendors.  So we switch
+	   on the device's digital scheme attribute in order to figure out
+	   what to do. */
+	switch (hdw->hdw_desc->digital_control_scheme) {
+	case PVR2_DIGITAL_SCHEME_HAUPPAUGE:
+		return pvr2_issue_simple_cmd(hdw,
+					     (runFl ?
+					      FX2CMD_HCW_DTV_STREAMING_ON :
+					      FX2CMD_HCW_DTV_STREAMING_OFF));
+	case PVR2_DIGITAL_SCHEME_ONAIR:
+		ret = pvr2_issue_simple_cmd(hdw,
+					    (runFl ?
+					     FX2CMD_STREAMING_ON :
+					     FX2CMD_STREAMING_OFF));
+		if (ret) return ret;
+		return pvr2_hdw_cmd_onair_digital_path_ctrl(hdw,runFl);
+	default:
+		return -EINVAL;
+	}
+}
+
+
+/* Evaluate whether or not state_pathway_ok can change */
+static int state_eval_pathway_ok(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_pathway_ok) {
+		/* Nothing to do if pathway is already ok */
+		return 0;
+	}
+	if (!hdw->state_pipeline_idle) {
+		/* Not allowed to change anything if pipeline is not idle */
+		return 0;
+	}
+	pvr2_hdw_cmd_modeswitch(hdw,hdw->input_val == PVR2_CVAL_INPUT_DTV);
+	hdw->state_pathway_ok = !0;
+	trace_stbit("state_pathway_ok",hdw->state_pathway_ok);
+	return !0;
+}
+
+
+/* Evaluate whether or not state_encoder_ok can change */
+static int state_eval_encoder_ok(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_encoder_ok) return 0;
+	if (hdw->flag_tripped) return 0;
+	if (hdw->state_encoder_run) return 0;
+	if (hdw->state_encoder_config) return 0;
+	if (hdw->state_decoder_run) return 0;
+	if (hdw->state_usbstream_run) return 0;
+	if (hdw->pathway_state == PVR2_PATHWAY_DIGITAL) {
+		if (!hdw->hdw_desc->flag_digital_requires_cx23416) return 0;
+	} else if (hdw->pathway_state != PVR2_PATHWAY_ANALOG) {
+		return 0;
+	}
+
+	if (pvr2_upload_firmware2(hdw) < 0) {
+		hdw->flag_tripped = !0;
+		trace_stbit("flag_tripped",hdw->flag_tripped);
+		return !0;
+	}
+	hdw->state_encoder_ok = !0;
+	trace_stbit("state_encoder_ok",hdw->state_encoder_ok);
+	return !0;
+}
+
+
+/* Evaluate whether or not state_encoder_config can change */
+static int state_eval_encoder_config(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_encoder_config) {
+		if (hdw->state_encoder_ok) {
+			if (hdw->state_pipeline_req &&
+			    !hdw->state_pipeline_pause) return 0;
+		}
+		hdw->state_encoder_config = 0;
+		hdw->state_encoder_waitok = 0;
+		trace_stbit("state_encoder_waitok",hdw->state_encoder_waitok);
+		/* paranoia - solve race if timer just completed */
+		del_timer_sync(&hdw->encoder_wait_timer);
+	} else {
+		if (!hdw->state_pathway_ok ||
+		    (hdw->pathway_state != PVR2_PATHWAY_ANALOG) ||
+		    !hdw->state_encoder_ok ||
+		    !hdw->state_pipeline_idle ||
+		    hdw->state_pipeline_pause ||
+		    !hdw->state_pipeline_req ||
+		    !hdw->state_pipeline_config) {
+			/* We must reset the enforced wait interval if
+			   anything has happened that might have disturbed
+			   the encoder.  This should be a rare case. */
+			if (timer_pending(&hdw->encoder_wait_timer)) {
+				del_timer_sync(&hdw->encoder_wait_timer);
+			}
+			if (hdw->state_encoder_waitok) {
+				/* Must clear the state - therefore we did
+				   something to a state bit and must also
+				   return true. */
+				hdw->state_encoder_waitok = 0;
+				trace_stbit("state_encoder_waitok",
+					    hdw->state_encoder_waitok);
+				return !0;
+			}
+			return 0;
+		}
+		if (!hdw->state_encoder_waitok) {
+			if (!timer_pending(&hdw->encoder_wait_timer)) {
+				/* waitok flag wasn't set and timer isn't
+				   running.  Check flag once more to avoid
+				   a race then start the timer.  This is
+				   the point when we measure out a minimal
+				   quiet interval before doing something to
+				   the encoder. */
+				if (!hdw->state_encoder_waitok) {
+					hdw->encoder_wait_timer.expires =
+						jiffies + msecs_to_jiffies(
+						TIME_MSEC_ENCODER_WAIT);
+					add_timer(&hdw->encoder_wait_timer);
+				}
+			}
+			/* We can't continue until we know we have been
+			   quiet for the interval measured by this
+			   timer. */
+			return 0;
+		}
+		pvr2_encoder_configure(hdw);
+		if (hdw->state_encoder_ok) hdw->state_encoder_config = !0;
+	}
+	trace_stbit("state_encoder_config",hdw->state_encoder_config);
+	return !0;
+}
+
+
+/* Return true if the encoder should not be running. */
+static int state_check_disable_encoder_run(struct pvr2_hdw *hdw)
+{
+	if (!hdw->state_encoder_ok) {
+		/* Encoder isn't healthy at the moment, so stop it. */
+		return !0;
+	}
+	if (!hdw->state_pathway_ok) {
+		/* Mode is not understood at the moment (i.e. it wants to
+		   change), so encoder must be stopped. */
+		return !0;
+	}
+
+	switch (hdw->pathway_state) {
+	case PVR2_PATHWAY_ANALOG:
+		if (!hdw->state_decoder_run) {
+			/* We're in analog mode and the decoder is not
+			   running; thus the encoder should be stopped as
+			   well. */
+			return !0;
+		}
+		break;
+	case PVR2_PATHWAY_DIGITAL:
+		if (hdw->state_encoder_runok) {
+			/* This is a funny case.  We're in digital mode so
+			   really the encoder should be stopped.  However
+			   if it really is running, only kill it after
+			   runok has been set.  This gives a chance for the
+			   onair quirk to function (encoder must run
+			   briefly first, at least once, before onair
+			   digital streaming can work). */
+			return !0;
+		}
+		break;
+	default:
+		/* Unknown mode; so encoder should be stopped. */
+		return !0;
+	}
+
+	/* If we get here, we haven't found a reason to stop the
+	   encoder. */
+	return 0;
+}
+
+
+/* Return true if the encoder should be running. */
+static int state_check_enable_encoder_run(struct pvr2_hdw *hdw)
+{
+	if (!hdw->state_encoder_ok) {
+		/* Don't run the encoder if it isn't healthy... */
+		return 0;
+	}
+	if (!hdw->state_pathway_ok) {
+		/* Don't run the encoder if we don't (yet) know what mode
+		   we need to be in... */
+		return 0;
+	}
+
+	switch (hdw->pathway_state) {
+	case PVR2_PATHWAY_ANALOG:
+		if (hdw->state_decoder_run && hdw->state_decoder_ready) {
+			/* In analog mode, if the decoder is running, then
+			   run the encoder. */
+			return !0;
+		}
+		break;
+	case PVR2_PATHWAY_DIGITAL:
+		if ((hdw->hdw_desc->digital_control_scheme ==
+		     PVR2_DIGITAL_SCHEME_ONAIR) &&
+		    !hdw->state_encoder_runok) {
+			/* This is a quirk.  OnAir hardware won't stream
+			   digital until the encoder has been run at least
+			   once, for a minimal period of time (empiricially
+			   measured to be 1/4 second).  So if we're on
+			   OnAir hardware and the encoder has never been
+			   run at all, then start the encoder.  Normal
+			   state machine logic in the driver will
+			   automatically handle the remaining bits. */
+			return !0;
+		}
+		break;
+	default:
+		/* For completeness (unknown mode; encoder won't run ever) */
+		break;
+	}
+	/* If we get here, then we haven't found any reason to run the
+	   encoder, so don't run it. */
+	return 0;
+}
+
+
+/* Evaluate whether or not state_encoder_run can change */
+static int state_eval_encoder_run(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_encoder_run) {
+		if (!state_check_disable_encoder_run(hdw)) return 0;
+		if (hdw->state_encoder_ok) {
+			del_timer_sync(&hdw->encoder_run_timer);
+			if (pvr2_encoder_stop(hdw) < 0) return !0;
+		}
+		hdw->state_encoder_run = 0;
+	} else {
+		if (!state_check_enable_encoder_run(hdw)) return 0;
+		if (pvr2_encoder_start(hdw) < 0) return !0;
+		hdw->state_encoder_run = !0;
+		if (!hdw->state_encoder_runok) {
+			hdw->encoder_run_timer.expires = jiffies +
+				 msecs_to_jiffies(TIME_MSEC_ENCODER_OK);
+			add_timer(&hdw->encoder_run_timer);
+		}
+	}
+	trace_stbit("state_encoder_run",hdw->state_encoder_run);
+	return !0;
+}
+
+
+/* Timeout function for quiescent timer. */
+static void pvr2_hdw_quiescent_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, quiescent_timer);
+	hdw->state_decoder_quiescent = !0;
+	trace_stbit("state_decoder_quiescent",hdw->state_decoder_quiescent);
+	hdw->state_stale = !0;
+	schedule_work(&hdw->workpoll);
+}
+
+
+/* Timeout function for decoder stabilization timer. */
+static void pvr2_hdw_decoder_stabilization_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, decoder_stabilization_timer);
+	hdw->state_decoder_ready = !0;
+	trace_stbit("state_decoder_ready", hdw->state_decoder_ready);
+	hdw->state_stale = !0;
+	schedule_work(&hdw->workpoll);
+}
+
+
+/* Timeout function for encoder wait timer. */
+static void pvr2_hdw_encoder_wait_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, encoder_wait_timer);
+	hdw->state_encoder_waitok = !0;
+	trace_stbit("state_encoder_waitok",hdw->state_encoder_waitok);
+	hdw->state_stale = !0;
+	schedule_work(&hdw->workpoll);
+}
+
+
+/* Timeout function for encoder run timer. */
+static void pvr2_hdw_encoder_run_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, encoder_run_timer);
+	if (!hdw->state_encoder_runok) {
+		hdw->state_encoder_runok = !0;
+		trace_stbit("state_encoder_runok",hdw->state_encoder_runok);
+		hdw->state_stale = !0;
+		schedule_work(&hdw->workpoll);
+	}
+}
+
+
+/* Evaluate whether or not state_decoder_run can change */
+static int state_eval_decoder_run(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_decoder_run) {
+		if (hdw->state_encoder_ok) {
+			if (hdw->state_pipeline_req &&
+			    !hdw->state_pipeline_pause &&
+			    hdw->state_pathway_ok) return 0;
+		}
+		if (!hdw->flag_decoder_missed) {
+			pvr2_decoder_enable(hdw,0);
+		}
+		hdw->state_decoder_quiescent = 0;
+		hdw->state_decoder_run = 0;
+		/* paranoia - solve race if timer(s) just completed */
+		del_timer_sync(&hdw->quiescent_timer);
+		/* Kill the stabilization timer, in case we're killing the
+		   encoder before the previous stabilization interval has
+		   been properly timed. */
+		del_timer_sync(&hdw->decoder_stabilization_timer);
+		hdw->state_decoder_ready = 0;
+	} else {
+		if (!hdw->state_decoder_quiescent) {
+			if (!timer_pending(&hdw->quiescent_timer)) {
+				/* We don't do something about the
+				   quiescent timer until right here because
+				   we also want to catch cases where the
+				   decoder was already not running (like
+				   after initialization) as opposed to
+				   knowing that we had just stopped it.
+				   The second flag check is here to cover a
+				   race - the timer could have run and set
+				   this flag just after the previous check
+				   but before we did the pending check. */
+				if (!hdw->state_decoder_quiescent) {
+					hdw->quiescent_timer.expires =
+						jiffies + msecs_to_jiffies(
+						TIME_MSEC_DECODER_WAIT);
+					add_timer(&hdw->quiescent_timer);
+				}
+			}
+			/* Don't allow decoder to start again until it has
+			   been quiesced first.  This little detail should
+			   hopefully further stabilize the encoder. */
+			return 0;
+		}
+		if (!hdw->state_pathway_ok ||
+		    (hdw->pathway_state != PVR2_PATHWAY_ANALOG) ||
+		    !hdw->state_pipeline_req ||
+		    hdw->state_pipeline_pause ||
+		    !hdw->state_pipeline_config ||
+		    !hdw->state_encoder_config ||
+		    !hdw->state_encoder_ok) return 0;
+		del_timer_sync(&hdw->quiescent_timer);
+		if (hdw->flag_decoder_missed) return 0;
+		if (pvr2_decoder_enable(hdw,!0) < 0) return 0;
+		hdw->state_decoder_quiescent = 0;
+		hdw->state_decoder_ready = 0;
+		hdw->state_decoder_run = !0;
+		if (hdw->decoder_client_id == PVR2_CLIENT_ID_SAA7115) {
+			hdw->decoder_stabilization_timer.expires =
+				jiffies + msecs_to_jiffies(
+				TIME_MSEC_DECODER_STABILIZATION_WAIT);
+			add_timer(&hdw->decoder_stabilization_timer);
+		} else {
+			hdw->state_decoder_ready = !0;
+		}
+	}
+	trace_stbit("state_decoder_quiescent",hdw->state_decoder_quiescent);
+	trace_stbit("state_decoder_run",hdw->state_decoder_run);
+	trace_stbit("state_decoder_ready", hdw->state_decoder_ready);
+	return !0;
+}
+
+
+/* Evaluate whether or not state_usbstream_run can change */
+static int state_eval_usbstream_run(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_usbstream_run) {
+		int fl = !0;
+		if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) {
+			fl = (hdw->state_encoder_ok &&
+			      hdw->state_encoder_run);
+		} else if ((hdw->pathway_state == PVR2_PATHWAY_DIGITAL) &&
+			   (hdw->hdw_desc->flag_digital_requires_cx23416)) {
+			fl = hdw->state_encoder_ok;
+		}
+		if (fl &&
+		    hdw->state_pipeline_req &&
+		    !hdw->state_pipeline_pause &&
+		    hdw->state_pathway_ok) {
+			return 0;
+		}
+		pvr2_hdw_cmd_usbstream(hdw,0);
+		hdw->state_usbstream_run = 0;
+	} else {
+		if (!hdw->state_pipeline_req ||
+		    hdw->state_pipeline_pause ||
+		    !hdw->state_pathway_ok) return 0;
+		if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) {
+			if (!hdw->state_encoder_ok ||
+			    !hdw->state_encoder_run) return 0;
+		} else if ((hdw->pathway_state == PVR2_PATHWAY_DIGITAL) &&
+			   (hdw->hdw_desc->flag_digital_requires_cx23416)) {
+			if (!hdw->state_encoder_ok) return 0;
+			if (hdw->state_encoder_run) return 0;
+			if (hdw->hdw_desc->digital_control_scheme ==
+			    PVR2_DIGITAL_SCHEME_ONAIR) {
+				/* OnAir digital receivers won't stream
+				   unless the analog encoder has run first.
+				   Why?  I have no idea.  But don't even
+				   try until we know the analog side is
+				   known to have run. */
+				if (!hdw->state_encoder_runok) return 0;
+			}
+		}
+		if (pvr2_hdw_cmd_usbstream(hdw,!0) < 0) return 0;
+		hdw->state_usbstream_run = !0;
+	}
+	trace_stbit("state_usbstream_run",hdw->state_usbstream_run);
+	return !0;
+}
+
+
+/* Attempt to configure pipeline, if needed */
+static int state_eval_pipeline_config(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_pipeline_config ||
+	    hdw->state_pipeline_pause) return 0;
+	pvr2_hdw_commit_execute(hdw);
+	return !0;
+}
+
+
+/* Update pipeline idle and pipeline pause tracking states based on other
+   inputs.  This must be called whenever the other relevant inputs have
+   changed. */
+static int state_update_pipeline_state(struct pvr2_hdw *hdw)
+{
+	unsigned int st;
+	int updatedFl = 0;
+	/* Update pipeline state */
+	st = !(hdw->state_encoder_run ||
+	       hdw->state_decoder_run ||
+	       hdw->state_usbstream_run ||
+	       (!hdw->state_decoder_quiescent));
+	if (!st != !hdw->state_pipeline_idle) {
+		hdw->state_pipeline_idle = st;
+		updatedFl = !0;
+	}
+	if (hdw->state_pipeline_idle && hdw->state_pipeline_pause) {
+		hdw->state_pipeline_pause = 0;
+		updatedFl = !0;
+	}
+	return updatedFl;
+}
+
+
+typedef int (*state_eval_func)(struct pvr2_hdw *);
+
+/* Set of functions to be run to evaluate various states in the driver. */
+static const state_eval_func eval_funcs[] = {
+	state_eval_pathway_ok,
+	state_eval_pipeline_config,
+	state_eval_encoder_ok,
+	state_eval_encoder_config,
+	state_eval_decoder_run,
+	state_eval_encoder_run,
+	state_eval_usbstream_run,
+};
+
+
+/* Process various states and return true if we did anything interesting. */
+static int pvr2_hdw_state_update(struct pvr2_hdw *hdw)
+{
+	unsigned int i;
+	int state_updated = 0;
+	int check_flag;
+
+	if (!hdw->state_stale) return 0;
+	if ((hdw->fw1_state != FW1_STATE_OK) ||
+	    !hdw->flag_ok) {
+		hdw->state_stale = 0;
+		return !0;
+	}
+	/* This loop is the heart of the entire driver.  It keeps trying to
+	   evaluate various bits of driver state until nothing changes for
+	   one full iteration.  Each "bit of state" tracks some global
+	   aspect of the driver, e.g. whether decoder should run, if
+	   pipeline is configured, usb streaming is on, etc.  We separately
+	   evaluate each of those questions based on other driver state to
+	   arrive at the correct running configuration. */
+	do {
+		check_flag = 0;
+		state_update_pipeline_state(hdw);
+		/* Iterate over each bit of state */
+		for (i = 0; (i<ARRAY_SIZE(eval_funcs)) && hdw->flag_ok; i++) {
+			if ((*eval_funcs[i])(hdw)) {
+				check_flag = !0;
+				state_updated = !0;
+				state_update_pipeline_state(hdw);
+			}
+		}
+	} while (check_flag && hdw->flag_ok);
+	hdw->state_stale = 0;
+	trace_stbit("state_stale",hdw->state_stale);
+	return state_updated;
+}
+
+
+static unsigned int print_input_mask(unsigned int msk,
+				     char *buf,unsigned int acnt)
+{
+	unsigned int idx,ccnt;
+	unsigned int tcnt = 0;
+	for (idx = 0; idx < ARRAY_SIZE(control_values_input); idx++) {
+		if (!((1 << idx) & msk)) continue;
+		ccnt = scnprintf(buf+tcnt,
+				 acnt-tcnt,
+				 "%s%s",
+				 (tcnt ? ", " : ""),
+				 control_values_input[idx]);
+		tcnt += ccnt;
+	}
+	return tcnt;
+}
+
+
+static const char *pvr2_pathway_state_name(int id)
+{
+	switch (id) {
+	case PVR2_PATHWAY_ANALOG: return "analog";
+	case PVR2_PATHWAY_DIGITAL: return "digital";
+	default: return "unknown";
+	}
+}
+
+
+static unsigned int pvr2_hdw_report_unlocked(struct pvr2_hdw *hdw,int which,
+					     char *buf,unsigned int acnt)
+{
+	switch (which) {
+	case 0:
+		return scnprintf(
+			buf,acnt,
+			"driver:%s%s%s%s%s <mode=%s>",
+			(hdw->flag_ok ? " <ok>" : " <fail>"),
+			(hdw->flag_init_ok ? " <init>" : " <uninitialized>"),
+			(hdw->flag_disconnected ? " <disconnected>" :
+			 " <connected>"),
+			(hdw->flag_tripped ? " <tripped>" : ""),
+			(hdw->flag_decoder_missed ? " <no decoder>" : ""),
+			pvr2_pathway_state_name(hdw->pathway_state));
+
+	case 1:
+		return scnprintf(
+			buf,acnt,
+			"pipeline:%s%s%s%s",
+			(hdw->state_pipeline_idle ? " <idle>" : ""),
+			(hdw->state_pipeline_config ?
+			 " <configok>" : " <stale>"),
+			(hdw->state_pipeline_req ? " <req>" : ""),
+			(hdw->state_pipeline_pause ? " <pause>" : ""));
+	case 2:
+		return scnprintf(
+			buf,acnt,
+			"worker:%s%s%s%s%s%s%s",
+			(hdw->state_decoder_run ?
+			 (hdw->state_decoder_ready ?
+			  "<decode:run>" : " <decode:start>") :
+			 (hdw->state_decoder_quiescent ?
+			  "" : " <decode:stop>")),
+			(hdw->state_decoder_quiescent ?
+			 " <decode:quiescent>" : ""),
+			(hdw->state_encoder_ok ?
+			 "" : " <encode:init>"),
+			(hdw->state_encoder_run ?
+			 (hdw->state_encoder_runok ?
+			  " <encode:run>" :
+			  " <encode:firstrun>") :
+			 (hdw->state_encoder_runok ?
+			  " <encode:stop>" :
+			  " <encode:virgin>")),
+			(hdw->state_encoder_config ?
+			 " <encode:configok>" :
+			 (hdw->state_encoder_waitok ?
+			  "" : " <encode:waitok>")),
+			(hdw->state_usbstream_run ?
+			 " <usb:run>" : " <usb:stop>"),
+			(hdw->state_pathway_ok ?
+			 " <pathway:ok>" : ""));
+	case 3:
+		return scnprintf(
+			buf,acnt,
+			"state: %s",
+			pvr2_get_state_name(hdw->master_state));
+	case 4: {
+		unsigned int tcnt = 0;
+		unsigned int ccnt;
+
+		ccnt = scnprintf(buf,
+				 acnt,
+				 "Hardware supported inputs: ");
+		tcnt += ccnt;
+		tcnt += print_input_mask(hdw->input_avail_mask,
+					 buf+tcnt,
+					 acnt-tcnt);
+		if (hdw->input_avail_mask != hdw->input_allowed_mask) {
+			ccnt = scnprintf(buf+tcnt,
+					 acnt-tcnt,
+					 "; allowed inputs: ");
+			tcnt += ccnt;
+			tcnt += print_input_mask(hdw->input_allowed_mask,
+						 buf+tcnt,
+						 acnt-tcnt);
+		}
+		return tcnt;
+	}
+	case 5: {
+		struct pvr2_stream_stats stats;
+		if (!hdw->vid_stream) break;
+		pvr2_stream_get_stats(hdw->vid_stream,
+				      &stats,
+				      0);
+		return scnprintf(
+			buf,acnt,
+			"Bytes streamed=%u URBs: queued=%u idle=%u ready=%u processed=%u failed=%u",
+			stats.bytes_processed,
+			stats.buffers_in_queue,
+			stats.buffers_in_idle,
+			stats.buffers_in_ready,
+			stats.buffers_processed,
+			stats.buffers_failed);
+	}
+	case 6: {
+		unsigned int id = hdw->ir_scheme_active;
+		return scnprintf(buf, acnt, "ir scheme: id=%d %s", id,
+				 (id >= ARRAY_SIZE(ir_scheme_names) ?
+				  "?" : ir_scheme_names[id]));
+	}
+	default: break;
+	}
+	return 0;
+}
+
+
+/* Generate report containing info about attached sub-devices and attached
+   i2c clients, including an indication of which attached i2c clients are
+   actually sub-devices. */
+static unsigned int pvr2_hdw_report_clients(struct pvr2_hdw *hdw,
+					    char *buf, unsigned int acnt)
+{
+	struct v4l2_subdev *sd;
+	unsigned int tcnt = 0;
+	unsigned int ccnt;
+	struct i2c_client *client;
+	const char *p;
+	unsigned int id;
+
+	ccnt = scnprintf(buf, acnt, "Associated v4l2-subdev drivers and I2C clients:\n");
+	tcnt += ccnt;
+	v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev) {
+		id = sd->grp_id;
+		p = NULL;
+		if (id < ARRAY_SIZE(module_names)) p = module_names[id];
+		if (p) {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt, "  %s:", p);
+			tcnt += ccnt;
+		} else {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt,
+					 "  (unknown id=%u):", id);
+			tcnt += ccnt;
+		}
+		client = v4l2_get_subdevdata(sd);
+		if (client) {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt,
+					 " %s @ %02x\n", client->name,
+					 client->addr);
+			tcnt += ccnt;
+		} else {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt,
+					 " no i2c client\n");
+			tcnt += ccnt;
+		}
+	}
+	return tcnt;
+}
+
+
+unsigned int pvr2_hdw_state_report(struct pvr2_hdw *hdw,
+				   char *buf,unsigned int acnt)
+{
+	unsigned int bcnt,ccnt,idx;
+	bcnt = 0;
+	LOCK_TAKE(hdw->big_lock);
+	for (idx = 0; ; idx++) {
+		ccnt = pvr2_hdw_report_unlocked(hdw,idx,buf,acnt);
+		if (!ccnt) break;
+		bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+		if (!acnt) break;
+		buf[0] = '\n'; ccnt = 1;
+		bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	}
+	ccnt = pvr2_hdw_report_clients(hdw, buf, acnt);
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	LOCK_GIVE(hdw->big_lock);
+	return bcnt;
+}
+
+
+static void pvr2_hdw_state_log_state(struct pvr2_hdw *hdw)
+{
+	char buf[256];
+	unsigned int idx, ccnt;
+	unsigned int lcnt, ucnt;
+
+	for (idx = 0; ; idx++) {
+		ccnt = pvr2_hdw_report_unlocked(hdw,idx,buf,sizeof(buf));
+		if (!ccnt) break;
+		printk(KERN_INFO "%s %.*s\n",hdw->name,ccnt,buf);
+	}
+	ccnt = pvr2_hdw_report_clients(hdw, buf, sizeof(buf));
+	if (ccnt >= sizeof(buf))
+		ccnt = sizeof(buf);
+
+	ucnt = 0;
+	while (ucnt < ccnt) {
+		lcnt = 0;
+		while ((lcnt + ucnt < ccnt) && (buf[lcnt + ucnt] != '\n')) {
+			lcnt++;
+		}
+		printk(KERN_INFO "%s %.*s\n", hdw->name, lcnt, buf + ucnt);
+		ucnt += lcnt + 1;
+	}
+}
+
+
+/* Evaluate and update the driver's current state, taking various actions
+   as appropriate for the update. */
+static int pvr2_hdw_state_eval(struct pvr2_hdw *hdw)
+{
+	unsigned int st;
+	int state_updated = 0;
+	int callback_flag = 0;
+	int analog_mode;
+
+	pvr2_trace(PVR2_TRACE_STBITS,
+		   "Drive state check START");
+	if (pvrusb2_debug & PVR2_TRACE_STBITS) {
+		pvr2_hdw_state_log_state(hdw);
+	}
+
+	/* Process all state and get back over disposition */
+	state_updated = pvr2_hdw_state_update(hdw);
+
+	analog_mode = (hdw->pathway_state != PVR2_PATHWAY_DIGITAL);
+
+	/* Update master state based upon all other states. */
+	if (!hdw->flag_ok) {
+		st = PVR2_STATE_DEAD;
+	} else if (hdw->fw1_state != FW1_STATE_OK) {
+		st = PVR2_STATE_COLD;
+	} else if ((analog_mode ||
+		    hdw->hdw_desc->flag_digital_requires_cx23416) &&
+		   !hdw->state_encoder_ok) {
+		st = PVR2_STATE_WARM;
+	} else if (hdw->flag_tripped ||
+		   (analog_mode && hdw->flag_decoder_missed)) {
+		st = PVR2_STATE_ERROR;
+	} else if (hdw->state_usbstream_run &&
+		   (!analog_mode ||
+		    (hdw->state_encoder_run && hdw->state_decoder_run))) {
+		st = PVR2_STATE_RUN;
+	} else {
+		st = PVR2_STATE_READY;
+	}
+	if (hdw->master_state != st) {
+		pvr2_trace(PVR2_TRACE_STATE,
+			   "Device state change from %s to %s",
+			   pvr2_get_state_name(hdw->master_state),
+			   pvr2_get_state_name(st));
+		pvr2_led_ctrl(hdw,st == PVR2_STATE_RUN);
+		hdw->master_state = st;
+		state_updated = !0;
+		callback_flag = !0;
+	}
+	if (state_updated) {
+		/* Trigger anyone waiting on any state changes here. */
+		wake_up(&hdw->state_wait_data);
+	}
+
+	if (pvrusb2_debug & PVR2_TRACE_STBITS) {
+		pvr2_hdw_state_log_state(hdw);
+	}
+	pvr2_trace(PVR2_TRACE_STBITS,
+		   "Drive state check DONE callback=%d",callback_flag);
+
+	return callback_flag;
+}
+
+
+/* Cause kernel thread to check / update driver state */
+static void pvr2_hdw_state_sched(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_stale) return;
+	hdw->state_stale = !0;
+	trace_stbit("state_stale",hdw->state_stale);
+	schedule_work(&hdw->workpoll);
+}
+
+
+int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *dp)
+{
+	return pvr2_read_register(hdw,PVR2_GPIO_DIR,dp);
+}
+
+
+int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *dp)
+{
+	return pvr2_read_register(hdw,PVR2_GPIO_OUT,dp);
+}
+
+
+int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *dp)
+{
+	return pvr2_read_register(hdw,PVR2_GPIO_IN,dp);
+}
+
+
+int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val)
+{
+	u32 cval,nval;
+	int ret;
+	if (~msk) {
+		ret = pvr2_read_register(hdw,PVR2_GPIO_DIR,&cval);
+		if (ret) return ret;
+		nval = (cval & ~msk) | (val & msk);
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO direction changing 0x%x:0x%x from 0x%x to 0x%x",
+			   msk,val,cval,nval);
+	} else {
+		nval = val;
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO direction changing to 0x%x",nval);
+	}
+	return pvr2_write_register(hdw,PVR2_GPIO_DIR,nval);
+}
+
+
+int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val)
+{
+	u32 cval,nval;
+	int ret;
+	if (~msk) {
+		ret = pvr2_read_register(hdw,PVR2_GPIO_OUT,&cval);
+		if (ret) return ret;
+		nval = (cval & ~msk) | (val & msk);
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO output changing 0x%x:0x%x from 0x%x to 0x%x",
+			   msk,val,cval,nval);
+	} else {
+		nval = val;
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO output changing to 0x%x",nval);
+	}
+	return pvr2_write_register(hdw,PVR2_GPIO_OUT,nval);
+}
+
+
+void pvr2_hdw_status_poll(struct pvr2_hdw *hdw)
+{
+	struct v4l2_tuner *vtp = &hdw->tuner_signal_info;
+	memset(vtp, 0, sizeof(*vtp));
+	vtp->type = (hdw->input_val == PVR2_CVAL_INPUT_RADIO) ?
+		V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+	hdw->tuner_signal_stale = 0;
+	/* Note: There apparently is no replacement for VIDIOC_CROPCAP
+	   using v4l2-subdev - therefore we can't support that AT ALL right
+	   now.  (Of course, no sub-drivers seem to implement it either.
+	   But now it's a a chicken and egg problem...) */
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, g_tuner, vtp);
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev status poll type=%u strength=%u audio=0x%x cap=0x%x low=%u hi=%u",
+		   vtp->type,
+		   vtp->signal, vtp->rxsubchans, vtp->capability,
+		   vtp->rangelow, vtp->rangehigh);
+
+	/* We have to do this to avoid getting into constant polling if
+	   there's nobody to answer a poll of cropcap info. */
+	hdw->cropcap_stale = 0;
+}
+
+
+unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *hdw)
+{
+	return hdw->input_avail_mask;
+}
+
+
+unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *hdw)
+{
+	return hdw->input_allowed_mask;
+}
+
+
+static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v)
+{
+	if (hdw->input_val != v) {
+		hdw->input_val = v;
+		hdw->input_dirty = !0;
+	}
+
+	/* Handle side effects - if we switch to a mode that needs the RF
+	   tuner, then select the right frequency choice as well and mark
+	   it dirty. */
+	if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+		hdw->freqSelector = 0;
+		hdw->freqDirty = !0;
+	} else if ((hdw->input_val == PVR2_CVAL_INPUT_TV) ||
+		   (hdw->input_val == PVR2_CVAL_INPUT_DTV)) {
+		hdw->freqSelector = 1;
+		hdw->freqDirty = !0;
+	}
+	return 0;
+}
+
+
+int pvr2_hdw_set_input_allowed(struct pvr2_hdw *hdw,
+			       unsigned int change_mask,
+			       unsigned int change_val)
+{
+	int ret = 0;
+	unsigned int nv,m,idx;
+	LOCK_TAKE(hdw->big_lock);
+	do {
+		nv = hdw->input_allowed_mask & ~change_mask;
+		nv |= (change_val & change_mask);
+		nv &= hdw->input_avail_mask;
+		if (!nv) {
+			/* No legal modes left; return error instead. */
+			ret = -EPERM;
+			break;
+		}
+		hdw->input_allowed_mask = nv;
+		if ((1 << hdw->input_val) & hdw->input_allowed_mask) {
+			/* Current mode is still in the allowed mask, so
+			   we're done. */
+			break;
+		}
+		/* Select and switch to a mode that is still in the allowed
+		   mask */
+		if (!hdw->input_allowed_mask) {
+			/* Nothing legal; give up */
+			break;
+		}
+		m = hdw->input_allowed_mask;
+		for (idx = 0; idx < (sizeof(m) << 3); idx++) {
+			if (!((1 << idx) & m)) continue;
+			pvr2_hdw_set_input(hdw,idx);
+			break;
+		}
+	} while (0);
+	LOCK_GIVE(hdw->big_lock);
+	return ret;
+}
+
+
+/* Find I2C address of eeprom */
+static int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw)
+{
+	int result;
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_EEPROM_ADDR;
+		result = pvr2_send_request(hdw,
+					   hdw->cmd_buffer,1,
+					   hdw->cmd_buffer,1);
+		if (result < 0) break;
+		result = hdw->cmd_buffer[0];
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+	return result;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.h b/drivers/media/usb/pvrusb2/pvrusb2-hdw.h
new file mode 100644
index 0000000..25648ad
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.h
@@ -0,0 +1,338 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_HDW_H
+#define __PVRUSB2_HDW_H
+
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-dev.h>
+#include "pvrusb2-io.h"
+#include "pvrusb2-ctrl.h"
+
+
+/* Private internal control ids, look these up with
+   pvr2_hdw_get_ctrl_by_id() - these are NOT visible in V4L */
+#define PVR2_CID_STDCUR 2
+#define PVR2_CID_STDAVAIL 3
+#define PVR2_CID_INPUT 4
+#define PVR2_CID_AUDIOMODE 5
+#define PVR2_CID_FREQUENCY 6
+#define PVR2_CID_HRES 7
+#define PVR2_CID_VRES 8
+#define PVR2_CID_CROPL 9
+#define PVR2_CID_CROPT 10
+#define PVR2_CID_CROPW 11
+#define PVR2_CID_CROPH 12
+#define PVR2_CID_CROPCAPPAN 13
+#define PVR2_CID_CROPCAPPAD 14
+#define PVR2_CID_CROPCAPBL 15
+#define PVR2_CID_CROPCAPBT 16
+#define PVR2_CID_CROPCAPBW 17
+#define PVR2_CID_CROPCAPBH 18
+#define PVR2_CID_STDDETECT 19
+
+/* Legal values for the INPUT state variable */
+#define PVR2_CVAL_INPUT_TV 0
+#define PVR2_CVAL_INPUT_DTV 1
+#define PVR2_CVAL_INPUT_COMPOSITE 2
+#define PVR2_CVAL_INPUT_SVIDEO 3
+#define PVR2_CVAL_INPUT_RADIO 4
+
+enum pvr2_config {
+	pvr2_config_empty,    /* No configuration */
+	pvr2_config_mpeg,     /* Encoded / compressed video */
+	pvr2_config_vbi,      /* Standard vbi info */
+	pvr2_config_pcm,      /* Audio raw pcm stream */
+	pvr2_config_rawvideo, /* Video raw frames */
+};
+
+enum pvr2_v4l_type {
+	pvr2_v4l_type_video,
+	pvr2_v4l_type_vbi,
+	pvr2_v4l_type_radio,
+};
+
+/* Major states that we can be in:
+ *
+ *  DEAD - Device is in an unusable state and cannot be recovered.  This
+ *  can happen if we completely lose the ability to communicate with it
+ *  (but it might still on the bus).  In this state there's nothing we can
+ *  do; it must be replugged in order to recover.
+ *
+ *  COLD - Device is in an unusable state, needs microcontroller firmware.
+ *
+ *  WARM - We can communicate with the device and the proper
+ *  microcontroller firmware is running, but other device initialization is
+ *  still needed (e.g. encoder firmware).
+ *
+ *  ERROR - A problem prevents capture operation (e.g. encoder firmware
+ *  missing).
+ *
+ *  READY - Device is operational, but not streaming.
+ *
+ *  RUN - Device is streaming.
+ *
+ */
+#define PVR2_STATE_NONE 0
+#define PVR2_STATE_DEAD 1
+#define PVR2_STATE_COLD 2
+#define PVR2_STATE_WARM 3
+#define PVR2_STATE_ERROR 4
+#define PVR2_STATE_READY 5
+#define PVR2_STATE_RUN 6
+
+/* Translate configuration enum to a string label */
+const char *pvr2_config_get_name(enum pvr2_config);
+
+struct pvr2_hdw;
+
+/* Create and return a structure for interacting with the underlying
+   hardware */
+struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf,
+				 const struct usb_device_id *devid);
+
+/* Perform second stage initialization, passing in a notification callback
+   for when the master state changes. */
+int pvr2_hdw_initialize(struct pvr2_hdw *,
+			void (*callback_func)(void *),
+			void *callback_data);
+
+/* Destroy hardware interaction structure */
+void pvr2_hdw_destroy(struct pvr2_hdw *);
+
+/* Return true if in the ready (normal) state */
+int pvr2_hdw_dev_ok(struct pvr2_hdw *);
+
+/* Return small integer number [1..N] for logical instance number of this
+   device.  This is useful for indexing array-valued module parameters. */
+int pvr2_hdw_get_unit_number(struct pvr2_hdw *);
+
+/* Get pointer to underlying USB device */
+struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *);
+
+/* Retrieve serial number of device */
+unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *);
+
+/* Retrieve bus location info of device */
+const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *);
+
+/* Retrieve per-instance string identifier for this specific device */
+const char *pvr2_hdw_get_device_identifier(struct pvr2_hdw *);
+
+/* Called when hardware has been unplugged */
+void pvr2_hdw_disconnect(struct pvr2_hdw *);
+
+/* Sets v4l2_dev of a video_device struct */
+void pvr2_hdw_set_v4l2_dev(struct pvr2_hdw *, struct video_device *);
+
+/* Get the number of defined controls */
+unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *);
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *,unsigned int);
+
+/* Retrieve a control handle given its internal ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *,unsigned int);
+
+/* Retrieve a control handle given its V4L ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *,unsigned int ctl_id);
+
+/* Retrieve a control handle given its immediate predecessor V4L ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *,
+					    unsigned int ctl_id);
+
+/* Commit all control changes made up to this point */
+int pvr2_hdw_commit_ctl(struct pvr2_hdw *);
+
+/* Return a bit mask of valid input selections for this device.  Mask bits
+ * will be according to PVR_CVAL_INPUT_xxxx definitions. */
+unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *);
+
+/* Return a bit mask of allowed input selections for this device.  Mask bits
+ * will be according to PVR_CVAL_INPUT_xxxx definitions. */
+unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *);
+
+/* Change the set of allowed input selections for this device.  Both
+   change_mask and change_valu are mask bits according to
+   PVR_CVAL_INPUT_xxxx definitions.  The change_mask parameter indicate
+   which settings are being changed and the change_val parameter indicates
+   whether corresponding settings are being set or cleared. */
+int pvr2_hdw_set_input_allowed(struct pvr2_hdw *,
+			       unsigned int change_mask,
+			       unsigned int change_val);
+
+/* Return name for this driver instance */
+const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *);
+
+/* Mark tuner status stale so that it will be re-fetched */
+void pvr2_hdw_execute_tuner_poll(struct pvr2_hdw *);
+
+/* Return information about the tuner */
+int pvr2_hdw_get_tuner_status(struct pvr2_hdw *,struct v4l2_tuner *);
+
+/* Return information about cropping capabilities */
+int pvr2_hdw_get_cropcap(struct pvr2_hdw *, struct v4l2_cropcap *);
+
+/* Query device and see if it thinks it is on a high-speed USB link */
+int pvr2_hdw_is_hsm(struct pvr2_hdw *);
+
+/* Return a string token representative of the hardware type */
+const char *pvr2_hdw_get_type(struct pvr2_hdw *);
+
+/* Return a single line description of the hardware type */
+const char *pvr2_hdw_get_desc(struct pvr2_hdw *);
+
+/* Turn streaming on/off */
+int pvr2_hdw_set_streaming(struct pvr2_hdw *,int);
+
+/* Find out if streaming is on */
+int pvr2_hdw_get_streaming(struct pvr2_hdw *);
+
+/* Retrieve driver overall state */
+int pvr2_hdw_get_state(struct pvr2_hdw *);
+
+/* Configure the type of stream to generate */
+int pvr2_hdw_set_stream_type(struct pvr2_hdw *, enum pvr2_config);
+
+/* Get handle to video output stream */
+struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *);
+
+/* Enable / disable retrieval of CPU firmware or prom contents.  This must
+   be enabled before pvr2_hdw_cpufw_get() will function.  Note that doing
+   this may prevent the device from running (and leaving this mode may
+   imply a device reset). */
+void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *,
+				int mode, /* 0=8KB FX2, 1=16KB FX2, 2=PROM */
+				int enable_flag);
+
+/* Return true if we're in a mode for retrieval CPU firmware */
+int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *);
+
+/* Retrieve a piece of the CPU's firmware at the given offset.  Return
+   value is the number of bytes retrieved or zero if we're past the end or
+   an error otherwise (e.g. if firmware retrieval is not enabled). */
+int pvr2_hdw_cpufw_get(struct pvr2_hdw *,unsigned int offs,
+		       char *buf,unsigned int cnt);
+
+/* Retrieve a previously stored v4l minor device number */
+int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *,enum pvr2_v4l_type index);
+
+/* Store a v4l minor device number */
+void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *,
+				     enum pvr2_v4l_type index,int);
+
+/* The following entry points are all lower level things you normally don't
+   want to worry about. */
+
+/* Issue a command and get a response from the device.  LOTS of higher
+   level stuff is built on this. */
+int pvr2_send_request(struct pvr2_hdw *,
+		      void *write_ptr,unsigned int write_len,
+		      void *read_ptr,unsigned int read_len);
+
+/* Slightly higher level device communication functions. */
+int pvr2_write_register(struct pvr2_hdw *, u16, u32);
+
+/* Call if for any reason we can't talk to the hardware anymore - this will
+   cause the driver to stop flailing on the device. */
+void pvr2_hdw_render_useless(struct pvr2_hdw *);
+
+/* Set / clear 8051's reset bit */
+void pvr2_hdw_cpureset_assert(struct pvr2_hdw *,int);
+
+/* Execute a USB-commanded device reset */
+void pvr2_hdw_device_reset(struct pvr2_hdw *);
+
+/* Reset worker's error trapping circuit breaker */
+int pvr2_hdw_untrip(struct pvr2_hdw *);
+
+/* Execute hard reset command (after this point it's likely that the
+   encoder will have to be reconfigured).  This also clears the "useless"
+   state. */
+int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *);
+
+/* Execute simple reset command */
+int pvr2_hdw_cmd_powerup(struct pvr2_hdw *);
+
+/* Order decoder to reset */
+int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *);
+
+/* Direct manipulation of GPIO bits */
+int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val);
+int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val);
+
+/* This data structure is specifically for the next function... */
+struct pvr2_hdw_debug_info {
+	int big_lock_held;
+	int ctl_lock_held;
+	int flag_disconnected;
+	int flag_init_ok;
+	int flag_ok;
+	int fw1_state;
+	int flag_decoder_missed;
+	int flag_tripped;
+	int state_encoder_ok;
+	int state_encoder_run;
+	int state_decoder_run;
+	int state_decoder_ready;
+	int state_usbstream_run;
+	int state_decoder_quiescent;
+	int state_pipeline_config;
+	int state_pipeline_req;
+	int state_pipeline_pause;
+	int state_pipeline_idle;
+	int cmd_debug_state;
+	int cmd_debug_write_len;
+	int cmd_debug_read_len;
+	int cmd_debug_write_pend;
+	int cmd_debug_read_pend;
+	int cmd_debug_timeout;
+	int cmd_debug_rstatus;
+	int cmd_debug_wstatus;
+	unsigned char cmd_code;
+};
+
+/* Non-intrusively retrieve internal state info - this is useful for
+   diagnosing lockups.  Note that this operation is completed without any
+   kind of locking and so it is not atomic and may yield inconsistent
+   results.  This is *purely* a debugging aid. */
+void pvr2_hdw_get_debug_info_unlocked(const struct pvr2_hdw *hdw,
+				      struct pvr2_hdw_debug_info *);
+
+/* Intrusively retrieve internal state info - this is useful for
+   diagnosing overall driver state.  This operation synchronizes against
+   the overall driver mutex - so if there are locking problems this will
+   likely hang!  This is *purely* a debugging aid. */
+void pvr2_hdw_get_debug_info_locked(struct pvr2_hdw *hdw,
+				    struct pvr2_hdw_debug_info *);
+
+/* Report out several lines of text that describes driver internal state.
+   Results are written into the passed-in buffer. */
+unsigned int pvr2_hdw_state_report(struct pvr2_hdw *hdw,
+				   char *buf_ptr,unsigned int buf_size);
+
+/* Cause modules to log their state once */
+void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw);
+
+/* Cause encoder firmware to be uploaded into the device.  This is normally
+   done autonomously, but the interface is exported here because it is also
+   a debugging aid. */
+int pvr2_upload_firmware2(struct pvr2_hdw *hdw);
+
+#endif /* __PVRUSB2_HDW_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c
new file mode 100644
index 0000000..f3003ca
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c
@@ -0,0 +1,667 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <media/i2c/ir-kbd-i2c.h>
+#include "pvrusb2-i2c-core.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-fx2-cmd.h"
+#include "pvrusb2.h"
+
+#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__)
+
+/*
+
+  This module attempts to implement a compliant I2C adapter for the pvrusb2
+  device.
+
+*/
+
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time");
+
+static int ir_mode[PVR_NUM] = { [0 ... PVR_NUM-1] = 1 };
+module_param_array(ir_mode, int, NULL, 0444);
+MODULE_PARM_DESC(ir_mode,"specify: 0=disable IR reception, 1=normal IR");
+
+static int pvr2_disable_ir_video;
+module_param_named(disable_autoload_ir_video, pvr2_disable_ir_video,
+		   int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(disable_autoload_ir_video,
+		 "1=do not try to autoload ir_video IR receiver");
+
+static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */
+			  u8 i2c_addr,      /* I2C address we're talking to */
+			  u8 *data,         /* Data to write */
+			  u16 length)       /* Size of data to write */
+{
+	/* Return value - default 0 means success */
+	int ret;
+
+
+	if (!data) length = 0;
+	if (length > (sizeof(hdw->cmd_buffer) - 3)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Killing an I2C write to %u that is too large (desired=%u limit=%u)",
+			   i2c_addr,
+			   length,(unsigned int)(sizeof(hdw->cmd_buffer) - 3));
+		return -ENOTSUPP;
+	}
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	/* Clear the command buffer (likely to be paranoia) */
+	memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer));
+
+	/* Set up command buffer for an I2C write */
+	hdw->cmd_buffer[0] = FX2CMD_I2C_WRITE;      /* write prefix */
+	hdw->cmd_buffer[1] = i2c_addr;  /* i2c addr of chip */
+	hdw->cmd_buffer[2] = length;    /* length of what follows */
+	if (length) memcpy(hdw->cmd_buffer + 3, data, length);
+
+	/* Do the operation */
+	ret = pvr2_send_request(hdw,
+				hdw->cmd_buffer,
+				length + 3,
+				hdw->cmd_buffer,
+				1);
+	if (!ret) {
+		if (hdw->cmd_buffer[0] != 8) {
+			ret = -EIO;
+			if (hdw->cmd_buffer[0] != 7) {
+				trace_i2c("unexpected status from i2_write[%d]: %d",
+					  i2c_addr,hdw->cmd_buffer[0]);
+			}
+		}
+	}
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */
+			 u8 i2c_addr,       /* I2C address we're talking to */
+			 u8 *data,          /* Data to write */
+			 u16 dlen,          /* Size of data to write */
+			 u8 *res,           /* Where to put data we read */
+			 u16 rlen)          /* Amount of data to read */
+{
+	/* Return value - default 0 means success */
+	int ret;
+
+
+	if (!data) dlen = 0;
+	if (dlen > (sizeof(hdw->cmd_buffer) - 4)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Killing an I2C read to %u that has wlen too large (desired=%u limit=%u)",
+			   i2c_addr,
+			   dlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 4));
+		return -ENOTSUPP;
+	}
+	if (res && (rlen > (sizeof(hdw->cmd_buffer) - 1))) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Killing an I2C read to %u that has rlen too large (desired=%u limit=%u)",
+			   i2c_addr,
+			   rlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 1));
+		return -ENOTSUPP;
+	}
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	/* Clear the command buffer (likely to be paranoia) */
+	memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer));
+
+	/* Set up command buffer for an I2C write followed by a read */
+	hdw->cmd_buffer[0] = FX2CMD_I2C_READ;  /* read prefix */
+	hdw->cmd_buffer[1] = dlen;  /* arg length */
+	hdw->cmd_buffer[2] = rlen;  /* answer length. Device will send one
+				       more byte (status). */
+	hdw->cmd_buffer[3] = i2c_addr;  /* i2c addr of chip */
+	if (dlen) memcpy(hdw->cmd_buffer + 4, data, dlen);
+
+	/* Do the operation */
+	ret = pvr2_send_request(hdw,
+				hdw->cmd_buffer,
+				4 + dlen,
+				hdw->cmd_buffer,
+				rlen + 1);
+	if (!ret) {
+		if (hdw->cmd_buffer[0] != 8) {
+			ret = -EIO;
+			if (hdw->cmd_buffer[0] != 7) {
+				trace_i2c("unexpected status from i2_read[%d]: %d",
+					  i2c_addr,hdw->cmd_buffer[0]);
+			}
+		}
+	}
+
+	/* Copy back the result */
+	if (res && rlen) {
+		if (ret) {
+			/* Error, just blank out the return buffer */
+			memset(res, 0, rlen);
+		} else {
+			memcpy(res, hdw->cmd_buffer + 1, rlen);
+		}
+	}
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+/* This is the common low level entry point for doing I2C operations to the
+   hardware. */
+static int pvr2_i2c_basic_op(struct pvr2_hdw *hdw,
+			     u8 i2c_addr,
+			     u8 *wdata,
+			     u16 wlen,
+			     u8 *rdata,
+			     u16 rlen)
+{
+	if (!rdata) rlen = 0;
+	if (!wdata) wlen = 0;
+	if (rlen || !wlen) {
+		return pvr2_i2c_read(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+	} else {
+		return pvr2_i2c_write(hdw,i2c_addr,wdata,wlen);
+	}
+}
+
+
+/* This is a special entry point for cases of I2C transaction attempts to
+   the IR receiver.  The implementation here simulates the IR receiver by
+   issuing a command to the FX2 firmware and using that response to return
+   what the real I2C receiver would have returned.  We use this for 24xxx
+   devices, where the IR receiver chip has been removed and replaced with
+   FX2 related logic. */
+static int i2c_24xxx_ir(struct pvr2_hdw *hdw,
+			u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	u8 dat[4];
+	unsigned int stat;
+
+	if (!(rlen || wlen)) {
+		/* This is a probe attempt.  Just let it succeed. */
+		return 0;
+	}
+
+	/* We don't understand this kind of transaction */
+	if ((wlen != 0) || (rlen == 0)) return -EIO;
+
+	if (rlen < 3) {
+		/* Mike Isely <isely@pobox.com> Appears to be a probe
+		   attempt from lirc.  Just fill in zeroes and return.  If
+		   we try instead to do the full transaction here, then bad
+		   things seem to happen within the lirc driver module
+		   (version 0.8.0-7 sources from Debian, when run under
+		   vanilla 2.6.17.6 kernel) - and I don't have the patience
+		   to chase it down. */
+		if (rlen > 0) rdata[0] = 0;
+		if (rlen > 1) rdata[1] = 0;
+		return 0;
+	}
+
+	/* Issue a command to the FX2 to read the IR receiver. */
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_IR_CODE;
+		stat = pvr2_send_request(hdw,
+					 hdw->cmd_buffer,1,
+					 hdw->cmd_buffer,4);
+		dat[0] = hdw->cmd_buffer[0];
+		dat[1] = hdw->cmd_buffer[1];
+		dat[2] = hdw->cmd_buffer[2];
+		dat[3] = hdw->cmd_buffer[3];
+	} while (0); LOCK_GIVE(hdw->ctl_lock);
+
+	/* Give up if that operation failed. */
+	if (stat != 0) return stat;
+
+	/* Mangle the results into something that looks like the real IR
+	   receiver. */
+	rdata[2] = 0xc1;
+	if (dat[0] != 1) {
+		/* No code received. */
+		rdata[0] = 0;
+		rdata[1] = 0;
+	} else {
+		u16 val;
+		/* Mash the FX2 firmware-provided IR code into something
+		   that the normal i2c chip-level driver expects. */
+		val = dat[1];
+		val <<= 8;
+		val |= dat[2];
+		val >>= 1;
+		val &= ~0x0003;
+		val |= 0x8000;
+		rdata[0] = (val >> 8) & 0xffu;
+		rdata[1] = val & 0xffu;
+	}
+
+	return 0;
+}
+
+/* This is a special entry point that is entered if an I2C operation is
+   attempted to a wm8775 chip on model 24xxx hardware.  Autodetect of this
+   part doesn't work, but we know it is really there.  So let's look for
+   the autodetect attempt and just return success if we see that. */
+static int i2c_hack_wm8775(struct pvr2_hdw *hdw,
+			   u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	if (!(rlen || wlen)) {
+		// This is a probe attempt.  Just let it succeed.
+		return 0;
+	}
+	return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+}
+
+/* This is an entry point designed to always fail any attempt to perform a
+   transfer.  We use this to cause certain I2C addresses to not be
+   probed. */
+static int i2c_black_hole(struct pvr2_hdw *hdw,
+			   u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	return -EIO;
+}
+
+/* This is a special entry point that is entered if an I2C operation is
+   attempted to a cx25840 chip on model 24xxx hardware.  This chip can
+   sometimes wedge itself.  Worse still, when this happens msp3400 can
+   falsely detect this part and then the system gets hosed up after msp3400
+   gets confused and dies.  What we want to do here is try to keep msp3400
+   away and also try to notice if the chip is wedged and send a warning to
+   the system log. */
+static int i2c_hack_cx25840(struct pvr2_hdw *hdw,
+			    u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	int ret;
+	unsigned int subaddr;
+	u8 wbuf[2];
+	int state = hdw->i2c_cx25840_hack_state;
+
+	if (!(rlen || wlen)) {
+		// Probe attempt - always just succeed and don't bother the
+		// hardware (this helps to make the state machine further
+		// down somewhat easier).
+		return 0;
+	}
+
+	if (state == 3) {
+		return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+	}
+
+	/* We're looking for the exact pattern where the revision register
+	   is being read.  The cx25840 module will always look at the
+	   revision register first.  Any other pattern of access therefore
+	   has to be a probe attempt from somebody else so we'll reject it.
+	   Normally we could just let each client just probe the part
+	   anyway, but when the cx25840 is wedged, msp3400 will get a false
+	   positive and that just screws things up... */
+
+	if (wlen == 0) {
+		switch (state) {
+		case 1: subaddr = 0x0100; break;
+		case 2: subaddr = 0x0101; break;
+		default: goto fail;
+		}
+	} else if (wlen == 2) {
+		subaddr = (wdata[0] << 8) | wdata[1];
+		switch (subaddr) {
+		case 0x0100: state = 1; break;
+		case 0x0101: state = 2; break;
+		default: goto fail;
+		}
+	} else {
+		goto fail;
+	}
+	if (!rlen) goto success;
+	state = 0;
+	if (rlen != 1) goto fail;
+
+	/* If we get to here then we have a legitimate read for one of the
+	   two revision bytes, so pass it through. */
+	wbuf[0] = subaddr >> 8;
+	wbuf[1] = subaddr;
+	ret = pvr2_i2c_basic_op(hdw,i2c_addr,wbuf,2,rdata,rlen);
+
+	if ((ret != 0) || (*rdata == 0x04) || (*rdata == 0x0a)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: Detected a wedged cx25840 chip; the device will not work.");
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: Try power cycling the pvrusb2 device.");
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: Disabling further access to the device to prevent other foul-ups.");
+		// This blocks all further communication with the part.
+		hdw->i2c_func[0x44] = NULL;
+		pvr2_hdw_render_useless(hdw);
+		goto fail;
+	}
+
+	/* Success! */
+	pvr2_trace(PVR2_TRACE_CHIPS,"cx25840 appears to be OK.");
+	state = 3;
+
+ success:
+	hdw->i2c_cx25840_hack_state = state;
+	return 0;
+
+ fail:
+	hdw->i2c_cx25840_hack_state = state;
+	return -EIO;
+}
+
+/* This is a very, very limited I2C adapter implementation.  We can only
+   support what we actually know will work on the device... */
+static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
+			 struct i2c_msg msgs[],
+			 int num)
+{
+	int ret = -ENOTSUPP;
+	pvr2_i2c_func funcp = NULL;
+	struct pvr2_hdw *hdw = (struct pvr2_hdw *)(i2c_adap->algo_data);
+
+	if (!num) {
+		ret = -EINVAL;
+		goto done;
+	}
+	if (msgs[0].addr < PVR2_I2C_FUNC_CNT) {
+		funcp = hdw->i2c_func[msgs[0].addr];
+	}
+	if (!funcp) {
+		ret = -EIO;
+		goto done;
+	}
+
+	if (num == 1) {
+		if (msgs[0].flags & I2C_M_RD) {
+			/* Simple read */
+			u16 tcnt,bcnt,offs;
+			if (!msgs[0].len) {
+				/* Length == 0 read.  This is a probe. */
+				if (funcp(hdw,msgs[0].addr,NULL,0,NULL,0)) {
+					ret = -EIO;
+					goto done;
+				}
+				ret = 1;
+				goto done;
+			}
+			/* If the read is short enough we'll do the whole
+			   thing atomically.  Otherwise we have no choice
+			   but to break apart the reads. */
+			tcnt = msgs[0].len;
+			offs = 0;
+			while (tcnt) {
+				bcnt = tcnt;
+				if (bcnt > sizeof(hdw->cmd_buffer)-1) {
+					bcnt = sizeof(hdw->cmd_buffer)-1;
+				}
+				if (funcp(hdw,msgs[0].addr,NULL,0,
+					  msgs[0].buf+offs,bcnt)) {
+					ret = -EIO;
+					goto done;
+				}
+				offs += bcnt;
+				tcnt -= bcnt;
+			}
+			ret = 1;
+			goto done;
+		} else {
+			/* Simple write */
+			ret = 1;
+			if (funcp(hdw,msgs[0].addr,
+				  msgs[0].buf,msgs[0].len,NULL,0)) {
+				ret = -EIO;
+			}
+			goto done;
+		}
+	} else if (num == 2) {
+		if (msgs[0].addr != msgs[1].addr) {
+			trace_i2c("i2c refusing 2 phase transfer with conflicting target addresses");
+			ret = -ENOTSUPP;
+			goto done;
+		}
+		if ((!((msgs[0].flags & I2C_M_RD))) &&
+		    (msgs[1].flags & I2C_M_RD)) {
+			u16 tcnt,bcnt,wcnt,offs;
+			/* Write followed by atomic read.  If the read
+			   portion is short enough we'll do the whole thing
+			   atomically.  Otherwise we have no choice but to
+			   break apart the reads. */
+			tcnt = msgs[1].len;
+			wcnt = msgs[0].len;
+			offs = 0;
+			while (tcnt || wcnt) {
+				bcnt = tcnt;
+				if (bcnt > sizeof(hdw->cmd_buffer)-1) {
+					bcnt = sizeof(hdw->cmd_buffer)-1;
+				}
+				if (funcp(hdw,msgs[0].addr,
+					  msgs[0].buf,wcnt,
+					  msgs[1].buf+offs,bcnt)) {
+					ret = -EIO;
+					goto done;
+				}
+				offs += bcnt;
+				tcnt -= bcnt;
+				wcnt = 0;
+			}
+			ret = 2;
+			goto done;
+		} else {
+			trace_i2c("i2c refusing complex transfer read0=%d read1=%d",
+				  (msgs[0].flags & I2C_M_RD),
+				  (msgs[1].flags & I2C_M_RD));
+		}
+	} else {
+		trace_i2c("i2c refusing %d phase transfer",num);
+	}
+
+ done:
+	if (pvrusb2_debug & PVR2_TRACE_I2C_TRAF) {
+		unsigned int idx,offs,cnt;
+		for (idx = 0; idx < num; idx++) {
+			cnt = msgs[idx].len;
+			printk(KERN_INFO
+			       "pvrusb2 i2c xfer %u/%u: addr=0x%x len=%d %s",
+			       idx+1,num,
+			       msgs[idx].addr,
+			       cnt,
+			       (msgs[idx].flags & I2C_M_RD ?
+				"read" : "write"));
+			if ((ret > 0) || !(msgs[idx].flags & I2C_M_RD)) {
+				if (cnt > 8) cnt = 8;
+				printk(KERN_CONT " [");
+				for (offs = 0; offs < cnt; offs++) {
+					if (offs) printk(KERN_CONT " ");
+					printk(KERN_CONT "%02x",msgs[idx].buf[offs]);
+				}
+				if (offs < cnt) printk(KERN_CONT " ...");
+				printk(KERN_CONT "]");
+			}
+			if (idx+1 == num) {
+				printk(KERN_CONT " result=%d",ret);
+			}
+			printk(KERN_CONT "\n");
+		}
+		if (!num) {
+			printk(KERN_INFO
+			       "pvrusb2 i2c xfer null transfer result=%d\n",
+			       ret);
+		}
+	}
+	return ret;
+}
+
+static u32 pvr2_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm pvr2_i2c_algo_template = {
+	.master_xfer   = pvr2_i2c_xfer,
+	.functionality = pvr2_i2c_functionality,
+};
+
+static const struct i2c_adapter pvr2_i2c_adap_template = {
+	.owner         = THIS_MODULE,
+	.class	       = 0,
+};
+
+
+/* Return true if device exists at given address */
+static int do_i2c_probe(struct pvr2_hdw *hdw, int addr)
+{
+	struct i2c_msg msg[1];
+	int rc;
+	msg[0].addr = 0;
+	msg[0].flags = I2C_M_RD;
+	msg[0].len = 0;
+	msg[0].buf = NULL;
+	msg[0].addr = addr;
+	rc = i2c_transfer(&hdw->i2c_adap, msg, ARRAY_SIZE(msg));
+	return rc == 1;
+}
+
+static void do_i2c_scan(struct pvr2_hdw *hdw)
+{
+	int i;
+	printk(KERN_INFO "%s: i2c scan beginning\n", hdw->name);
+	for (i = 0; i < 128; i++) {
+		if (do_i2c_probe(hdw, i)) {
+			printk(KERN_INFO "%s: i2c scan: found device @ 0x%x\n",
+			       hdw->name, i);
+		}
+	}
+	printk(KERN_INFO "%s: i2c scan done.\n", hdw->name);
+}
+
+static void pvr2_i2c_register_ir(struct pvr2_hdw *hdw)
+{
+	struct i2c_board_info info;
+	struct IR_i2c_init_data *init_data = &hdw->ir_init_data;
+	if (pvr2_disable_ir_video) {
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Automatic binding of ir_video has been disabled.");
+		return;
+	}
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	switch (hdw->ir_scheme_active) {
+	case PVR2_IR_SCHEME_24XXX: /* FX2-controlled IR */
+	case PVR2_IR_SCHEME_29XXX: /* Original 29xxx device */
+		init_data->ir_codes              = RC_MAP_HAUPPAUGE;
+		init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP;
+		init_data->type                  = RC_PROTO_BIT_RC5;
+		init_data->name                  = hdw->hdw_desc->description;
+		init_data->polling_interval      = 100; /* ms From ir-kbd-i2c */
+		/* IR Receiver */
+		info.addr          = 0x18;
+		info.platform_data = init_data;
+		strlcpy(info.type, "ir_video", I2C_NAME_SIZE);
+		pvr2_trace(PVR2_TRACE_INFO, "Binding %s to i2c address 0x%02x.",
+			   info.type, info.addr);
+		i2c_new_device(&hdw->i2c_adap, &info);
+		break;
+	case PVR2_IR_SCHEME_ZILOG:     /* HVR-1950 style */
+	case PVR2_IR_SCHEME_24XXX_MCE: /* 24xxx MCE device */
+		init_data->ir_codes = RC_MAP_HAUPPAUGE;
+		init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR;
+		init_data->type = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_MCE |
+							RC_PROTO_BIT_RC6_6A_32;
+		init_data->name = hdw->hdw_desc->description;
+		/* IR Transceiver */
+		info.addr = 0x71;
+		info.platform_data = init_data;
+		strlcpy(info.type, "ir_z8f0811_haup", I2C_NAME_SIZE);
+		pvr2_trace(PVR2_TRACE_INFO, "Binding %s to i2c address 0x%02x.",
+			   info.type, info.addr);
+		i2c_new_device(&hdw->i2c_adap, &info);
+		break;
+	default:
+		/* The device either doesn't support I2C-based IR or we
+		   don't know (yet) how to operate IR on the device. */
+		break;
+	}
+}
+
+void pvr2_i2c_core_init(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+
+	/* The default action for all possible I2C addresses is just to do
+	   the transfer normally. */
+	for (idx = 0; idx < PVR2_I2C_FUNC_CNT; idx++) {
+		hdw->i2c_func[idx] = pvr2_i2c_basic_op;
+	}
+
+	/* However, deal with various special cases for 24xxx hardware. */
+	if (ir_mode[hdw->unit_number] == 0) {
+		printk(KERN_INFO "%s: IR disabled\n",hdw->name);
+		hdw->i2c_func[0x18] = i2c_black_hole;
+	} else if (ir_mode[hdw->unit_number] == 1) {
+		if (hdw->ir_scheme_active == PVR2_IR_SCHEME_24XXX) {
+			/* Set up translation so that our IR looks like a
+			   29xxx device */
+			hdw->i2c_func[0x18] = i2c_24xxx_ir;
+		}
+	}
+	if (hdw->hdw_desc->flag_has_cx25840) {
+		hdw->i2c_func[0x44] = i2c_hack_cx25840;
+	}
+	if (hdw->hdw_desc->flag_has_wm8775) {
+		hdw->i2c_func[0x1b] = i2c_hack_wm8775;
+	}
+
+	// Configure the adapter and set up everything else related to it.
+	hdw->i2c_adap = pvr2_i2c_adap_template;
+	hdw->i2c_algo = pvr2_i2c_algo_template;
+	strlcpy(hdw->i2c_adap.name,hdw->name,sizeof(hdw->i2c_adap.name));
+	hdw->i2c_adap.dev.parent = &hdw->usb_dev->dev;
+	hdw->i2c_adap.algo = &hdw->i2c_algo;
+	hdw->i2c_adap.algo_data = hdw;
+	hdw->i2c_linked = !0;
+	i2c_set_adapdata(&hdw->i2c_adap, &hdw->v4l2_dev);
+	i2c_add_adapter(&hdw->i2c_adap);
+	if (hdw->i2c_func[0x18] == i2c_24xxx_ir) {
+		/* Probe for a different type of IR receiver on this
+		   device.  This is really the only way to differentiate
+		   older 24xxx devices from 24xxx variants that include an
+		   IR blaster.  If the IR blaster is present, the IR
+		   receiver is part of that chip and thus we must disable
+		   the emulated IR receiver. */
+		if (do_i2c_probe(hdw, 0x71)) {
+			pvr2_trace(PVR2_TRACE_INFO,
+				   "Device has newer IR hardware; disabling unneeded virtual IR device");
+			hdw->i2c_func[0x18] = NULL;
+			/* Remember that this is a different device... */
+			hdw->ir_scheme_active = PVR2_IR_SCHEME_24XXX_MCE;
+		}
+	}
+	if (i2c_scan) do_i2c_scan(hdw);
+
+	pvr2_i2c_register_ir(hdw);
+}
+
+void pvr2_i2c_core_done(struct pvr2_hdw *hdw)
+{
+	if (hdw->i2c_linked) {
+		i2c_del_adapter(&hdw->i2c_adap);
+		hdw->i2c_linked = 0;
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h
new file mode 100644
index 0000000..1c44dee
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_I2C_CORE_H
+#define __PVRUSB2_I2C_CORE_H
+
+struct pvr2_hdw;
+
+void pvr2_i2c_core_init(struct pvr2_hdw *);
+void pvr2_i2c_core_done(struct pvr2_hdw *);
+
+
+#endif /* __PVRUSB2_I2C_ADAPTER_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-io.c b/drivers/media/usb/pvrusb2/pvrusb2-io.c
new file mode 100644
index 0000000..6d153fc
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-io.c
@@ -0,0 +1,682 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include "pvrusb2-io.h"
+#include "pvrusb2-debug.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state);
+
+#define BUFFER_SIG 0x47653271
+
+// #define SANITY_CHECK_BUFFERS
+
+
+#ifdef SANITY_CHECK_BUFFERS
+#define BUFFER_CHECK(bp) do { \
+	if ((bp)->signature != BUFFER_SIG) { \
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS, \
+		"Buffer %p is bad at %s:%d", \
+		(bp), __FILE__, __LINE__); \
+		pvr2_buffer_describe(bp, "BadSig"); \
+		BUG(); \
+	} \
+} while (0)
+#else
+#define BUFFER_CHECK(bp) do {} while (0)
+#endif
+
+struct pvr2_stream {
+	/* Buffers queued for reading */
+	struct list_head queued_list;
+	unsigned int q_count;
+	unsigned int q_bcount;
+	/* Buffers with retrieved data */
+	struct list_head ready_list;
+	unsigned int r_count;
+	unsigned int r_bcount;
+	/* Buffers available for use */
+	struct list_head idle_list;
+	unsigned int i_count;
+	unsigned int i_bcount;
+	/* Pointers to all buffers */
+	struct pvr2_buffer **buffers;
+	/* Array size of buffers */
+	unsigned int buffer_slot_count;
+	/* Total buffers actually in circulation */
+	unsigned int buffer_total_count;
+	/* Designed number of buffers to be in circulation */
+	unsigned int buffer_target_count;
+	/* Executed when ready list become non-empty */
+	pvr2_stream_callback callback_func;
+	void *callback_data;
+	/* Context for transfer endpoint */
+	struct usb_device *dev;
+	int endpoint;
+	/* Overhead for mutex enforcement */
+	spinlock_t list_lock;
+	struct mutex mutex;
+	/* Tracking state for tolerating errors */
+	unsigned int fail_count;
+	unsigned int fail_tolerance;
+
+	unsigned int buffers_processed;
+	unsigned int buffers_failed;
+	unsigned int bytes_processed;
+};
+
+struct pvr2_buffer {
+	int id;
+	int signature;
+	enum pvr2_buffer_state state;
+	void *ptr;               /* Pointer to storage area */
+	unsigned int max_count;  /* Size of storage area */
+	unsigned int used_count; /* Amount of valid data in storage area */
+	int status;              /* Transfer result status */
+	struct pvr2_stream *stream;
+	struct list_head list_overhead;
+	struct urb *purb;
+};
+
+static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state st)
+{
+	switch (st) {
+	case pvr2_buffer_state_none: return "none";
+	case pvr2_buffer_state_idle: return "idle";
+	case pvr2_buffer_state_queued: return "queued";
+	case pvr2_buffer_state_ready: return "ready";
+	}
+	return "unknown";
+}
+
+#ifdef SANITY_CHECK_BUFFERS
+static void pvr2_buffer_describe(struct pvr2_buffer *bp, const char *msg)
+{
+	pvr2_trace(PVR2_TRACE_INFO,
+		   "buffer%s%s %p state=%s id=%d status=%d stream=%p purb=%p sig=0x%x",
+		   (msg ? " " : ""),
+		   (msg ? msg : ""),
+		   bp,
+		   (bp ? pvr2_buffer_state_decode(bp->state) : "(invalid)"),
+		   (bp ? bp->id : 0),
+		   (bp ? bp->status : 0),
+		   (bp ? bp->stream : NULL),
+		   (bp ? bp->purb : NULL),
+		   (bp ? bp->signature : 0));
+}
+#endif  /*  SANITY_CHECK_BUFFERS  */
+
+static void pvr2_buffer_remove(struct pvr2_buffer *bp)
+{
+	unsigned int *cnt;
+	unsigned int *bcnt;
+	unsigned int ccnt;
+	struct pvr2_stream *sp = bp->stream;
+	switch (bp->state) {
+	case pvr2_buffer_state_idle:
+		cnt = &sp->i_count;
+		bcnt = &sp->i_bcount;
+		ccnt = bp->max_count;
+		break;
+	case pvr2_buffer_state_queued:
+		cnt = &sp->q_count;
+		bcnt = &sp->q_bcount;
+		ccnt = bp->max_count;
+		break;
+	case pvr2_buffer_state_ready:
+		cnt = &sp->r_count;
+		bcnt = &sp->r_bcount;
+		ccnt = bp->used_count;
+		break;
+	default:
+		return;
+	}
+	list_del_init(&bp->list_overhead);
+	(*cnt)--;
+	(*bcnt) -= ccnt;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s dec cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state), *bcnt, *cnt);
+	bp->state = pvr2_buffer_state_none;
+}
+
+static void pvr2_buffer_set_none(struct pvr2_buffer *bp)
+{
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_none));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	pvr2_buffer_remove(bp);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+static int pvr2_buffer_set_ready(struct pvr2_buffer *bp)
+{
+	int fl;
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_ready));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	fl = (sp->r_count == 0);
+	pvr2_buffer_remove(bp);
+	list_add_tail(&bp->list_overhead, &sp->ready_list);
+	bp->state = pvr2_buffer_state_ready;
+	(sp->r_count)++;
+	sp->r_bcount += bp->used_count;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s inc cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state),
+		   sp->r_bcount, sp->r_count);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	return fl;
+}
+
+static void pvr2_buffer_set_idle(struct pvr2_buffer *bp)
+{
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_idle));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	pvr2_buffer_remove(bp);
+	list_add_tail(&bp->list_overhead, &sp->idle_list);
+	bp->state = pvr2_buffer_state_idle;
+	(sp->i_count)++;
+	sp->i_bcount += bp->max_count;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s inc cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state),
+		   sp->i_bcount, sp->i_count);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+static void pvr2_buffer_set_queued(struct pvr2_buffer *bp)
+{
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_queued));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	pvr2_buffer_remove(bp);
+	list_add_tail(&bp->list_overhead, &sp->queued_list);
+	bp->state = pvr2_buffer_state_queued;
+	(sp->q_count)++;
+	sp->q_bcount += bp->max_count;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s inc cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state),
+		   sp->q_bcount, sp->q_count);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+static void pvr2_buffer_wipe(struct pvr2_buffer *bp)
+{
+	if (bp->state == pvr2_buffer_state_queued) {
+		usb_kill_urb(bp->purb);
+	}
+}
+
+static int pvr2_buffer_init(struct pvr2_buffer *bp,
+			    struct pvr2_stream *sp,
+			    unsigned int id)
+{
+	memset(bp, 0, sizeof(*bp));
+	bp->signature = BUFFER_SIG;
+	bp->id = id;
+	pvr2_trace(PVR2_TRACE_BUF_POOL,
+		   "/*---TRACE_FLOW---*/ bufferInit     %p stream=%p", bp, sp);
+	bp->stream = sp;
+	bp->state = pvr2_buffer_state_none;
+	INIT_LIST_HEAD(&bp->list_overhead);
+	bp->purb = usb_alloc_urb(0, GFP_KERNEL);
+	if (! bp->purb) return -ENOMEM;
+#ifdef SANITY_CHECK_BUFFERS
+	pvr2_buffer_describe(bp, "create");
+#endif
+	return 0;
+}
+
+static void pvr2_buffer_done(struct pvr2_buffer *bp)
+{
+#ifdef SANITY_CHECK_BUFFERS
+	pvr2_buffer_describe(bp, "delete");
+#endif
+	pvr2_buffer_wipe(bp);
+	pvr2_buffer_set_none(bp);
+	bp->signature = 0;
+	bp->stream = NULL;
+	usb_free_urb(bp->purb);
+	pvr2_trace(PVR2_TRACE_BUF_POOL, "/*---TRACE_FLOW---*/ bufferDone     %p",
+		   bp);
+}
+
+static int pvr2_stream_buffer_count(struct pvr2_stream *sp, unsigned int cnt)
+{
+	int ret;
+	unsigned int scnt;
+
+	/* Allocate buffers pointer array in multiples of 32 entries */
+	if (cnt == sp->buffer_total_count) return 0;
+
+	pvr2_trace(PVR2_TRACE_BUF_POOL,
+		   "/*---TRACE_FLOW---*/ poolResize	stream=%p cur=%d adj=%+d",
+		   sp,
+		   sp->buffer_total_count,
+		   cnt-sp->buffer_total_count);
+
+	scnt = cnt & ~0x1f;
+	if (cnt > scnt) scnt += 0x20;
+
+	if (cnt > sp->buffer_total_count) {
+		if (scnt > sp->buffer_slot_count) {
+			struct pvr2_buffer **nb;
+
+			nb = kmalloc_array(scnt, sizeof(*nb), GFP_KERNEL);
+			if (!nb) return -ENOMEM;
+			if (sp->buffer_slot_count) {
+				memcpy(nb, sp->buffers,
+				       sp->buffer_slot_count * sizeof(*nb));
+				kfree(sp->buffers);
+			}
+			sp->buffers = nb;
+			sp->buffer_slot_count = scnt;
+		}
+		while (sp->buffer_total_count < cnt) {
+			struct pvr2_buffer *bp;
+			bp = kmalloc(sizeof(*bp), GFP_KERNEL);
+			if (!bp) return -ENOMEM;
+			ret = pvr2_buffer_init(bp, sp, sp->buffer_total_count);
+			if (ret) {
+				kfree(bp);
+				return -ENOMEM;
+			}
+			sp->buffers[sp->buffer_total_count] = bp;
+			(sp->buffer_total_count)++;
+			pvr2_buffer_set_idle(bp);
+		}
+	} else {
+		while (sp->buffer_total_count > cnt) {
+			struct pvr2_buffer *bp;
+			bp = sp->buffers[sp->buffer_total_count - 1];
+			/* Paranoia */
+			sp->buffers[sp->buffer_total_count - 1] = NULL;
+			(sp->buffer_total_count)--;
+			pvr2_buffer_done(bp);
+			kfree(bp);
+		}
+		if (scnt < sp->buffer_slot_count) {
+			struct pvr2_buffer **nb = NULL;
+			if (scnt) {
+				nb = kmemdup(sp->buffers, scnt * sizeof(*nb),
+					     GFP_KERNEL);
+				if (!nb) return -ENOMEM;
+			}
+			kfree(sp->buffers);
+			sp->buffers = nb;
+			sp->buffer_slot_count = scnt;
+		}
+	}
+	return 0;
+}
+
+static int pvr2_stream_achieve_buffer_count(struct pvr2_stream *sp)
+{
+	struct pvr2_buffer *bp;
+	unsigned int cnt;
+
+	if (sp->buffer_total_count == sp->buffer_target_count) return 0;
+
+	pvr2_trace(PVR2_TRACE_BUF_POOL,
+		   "/*---TRACE_FLOW---*/ poolCheck	stream=%p cur=%d tgt=%d",
+		   sp, sp->buffer_total_count, sp->buffer_target_count);
+
+	if (sp->buffer_total_count < sp->buffer_target_count) {
+		return pvr2_stream_buffer_count(sp, sp->buffer_target_count);
+	}
+
+	cnt = 0;
+	while ((sp->buffer_total_count - cnt) > sp->buffer_target_count) {
+		bp = sp->buffers[sp->buffer_total_count - (cnt + 1)];
+		if (bp->state != pvr2_buffer_state_idle) break;
+		cnt++;
+	}
+	if (cnt) {
+		pvr2_stream_buffer_count(sp, sp->buffer_total_count - cnt);
+	}
+
+	return 0;
+}
+
+static void pvr2_stream_internal_flush(struct pvr2_stream *sp)
+{
+	struct list_head *lp;
+	struct pvr2_buffer *bp1;
+	while ((lp = sp->queued_list.next) != &sp->queued_list) {
+		bp1 = list_entry(lp, struct pvr2_buffer, list_overhead);
+		pvr2_buffer_wipe(bp1);
+		/* At this point, we should be guaranteed that no
+		   completion callback may happen on this buffer.  But it's
+		   possible that it might have completed after we noticed
+		   it but before we wiped it.  So double check its status
+		   here first. */
+		if (bp1->state != pvr2_buffer_state_queued) continue;
+		pvr2_buffer_set_idle(bp1);
+	}
+	if (sp->buffer_total_count != sp->buffer_target_count) {
+		pvr2_stream_achieve_buffer_count(sp);
+	}
+}
+
+static void pvr2_stream_init(struct pvr2_stream *sp)
+{
+	spin_lock_init(&sp->list_lock);
+	mutex_init(&sp->mutex);
+	INIT_LIST_HEAD(&sp->queued_list);
+	INIT_LIST_HEAD(&sp->ready_list);
+	INIT_LIST_HEAD(&sp->idle_list);
+}
+
+static void pvr2_stream_done(struct pvr2_stream *sp)
+{
+	mutex_lock(&sp->mutex); do {
+		pvr2_stream_internal_flush(sp);
+		pvr2_stream_buffer_count(sp, 0);
+	} while (0); mutex_unlock(&sp->mutex);
+}
+
+static void buffer_complete(struct urb *urb)
+{
+	struct pvr2_buffer *bp = urb->context;
+	struct pvr2_stream *sp;
+	unsigned long irq_flags;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	bp->used_count = 0;
+	bp->status = 0;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferComplete %p stat=%d cnt=%d",
+		   bp, urb->status, urb->actual_length);
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	if ((!(urb->status)) ||
+	    (urb->status == -ENOENT) ||
+	    (urb->status == -ECONNRESET) ||
+	    (urb->status == -ESHUTDOWN)) {
+		(sp->buffers_processed)++;
+		sp->bytes_processed += urb->actual_length;
+		bp->used_count = urb->actual_length;
+		if (sp->fail_count) {
+			pvr2_trace(PVR2_TRACE_TOLERANCE,
+				   "stream %p transfer ok - fail count reset",
+				   sp);
+			sp->fail_count = 0;
+		}
+	} else if (sp->fail_count < sp->fail_tolerance) {
+		// We can tolerate this error, because we're below the
+		// threshold...
+		(sp->fail_count)++;
+		(sp->buffers_failed)++;
+		pvr2_trace(PVR2_TRACE_TOLERANCE,
+			   "stream %p ignoring error %d - fail count increased to %u",
+			   sp, urb->status, sp->fail_count);
+	} else {
+		(sp->buffers_failed)++;
+		bp->status = urb->status;
+	}
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	pvr2_buffer_set_ready(bp);
+	if (sp->callback_func) {
+		sp->callback_func(sp->callback_data);
+	}
+}
+
+struct pvr2_stream *pvr2_stream_create(void)
+{
+	struct pvr2_stream *sp;
+	sp = kzalloc(sizeof(*sp), GFP_KERNEL);
+	if (!sp) return sp;
+	pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_create: sp=%p", sp);
+	pvr2_stream_init(sp);
+	return sp;
+}
+
+void pvr2_stream_destroy(struct pvr2_stream *sp)
+{
+	if (!sp) return;
+	pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_destroy: sp=%p", sp);
+	pvr2_stream_done(sp);
+	kfree(sp);
+}
+
+void pvr2_stream_setup(struct pvr2_stream *sp,
+		       struct usb_device *dev,
+		       int endpoint,
+		       unsigned int tolerance)
+{
+	mutex_lock(&sp->mutex); do {
+		pvr2_stream_internal_flush(sp);
+		sp->dev = dev;
+		sp->endpoint = endpoint;
+		sp->fail_tolerance = tolerance;
+	} while (0); mutex_unlock(&sp->mutex);
+}
+
+void pvr2_stream_set_callback(struct pvr2_stream *sp,
+			      pvr2_stream_callback func,
+			      void *data)
+{
+	unsigned long irq_flags;
+	mutex_lock(&sp->mutex);
+	do {
+		spin_lock_irqsave(&sp->list_lock, irq_flags);
+		sp->callback_data = data;
+		sp->callback_func = func;
+		spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+}
+
+void pvr2_stream_get_stats(struct pvr2_stream *sp,
+			   struct pvr2_stream_stats *stats,
+			   int zero_counts)
+{
+	unsigned long irq_flags;
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	if (stats) {
+		stats->buffers_in_queue = sp->q_count;
+		stats->buffers_in_idle = sp->i_count;
+		stats->buffers_in_ready = sp->r_count;
+		stats->buffers_processed = sp->buffers_processed;
+		stats->buffers_failed = sp->buffers_failed;
+		stats->bytes_processed = sp->bytes_processed;
+	}
+	if (zero_counts) {
+		sp->buffers_processed = 0;
+		sp->buffers_failed = 0;
+		sp->bytes_processed = 0;
+	}
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+/* Query / set the nominal buffer count */
+int pvr2_stream_get_buffer_count(struct pvr2_stream *sp)
+{
+	return sp->buffer_target_count;
+}
+
+int pvr2_stream_set_buffer_count(struct pvr2_stream *sp, unsigned int cnt)
+{
+	int ret;
+	if (sp->buffer_target_count == cnt) return 0;
+	mutex_lock(&sp->mutex);
+	do {
+		sp->buffer_target_count = cnt;
+		ret = pvr2_stream_achieve_buffer_count(sp);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+	return ret;
+}
+
+struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *sp)
+{
+	struct list_head *lp = sp->idle_list.next;
+	if (lp == &sp->idle_list) return NULL;
+	return list_entry(lp, struct pvr2_buffer, list_overhead);
+}
+
+struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *sp)
+{
+	struct list_head *lp = sp->ready_list.next;
+	if (lp == &sp->ready_list) return NULL;
+	return list_entry(lp, struct pvr2_buffer, list_overhead);
+}
+
+struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp, int id)
+{
+	if (id < 0) return NULL;
+	if (id >= sp->buffer_total_count) return NULL;
+	return sp->buffers[id];
+}
+
+int pvr2_stream_get_ready_count(struct pvr2_stream *sp)
+{
+	return sp->r_count;
+}
+
+void pvr2_stream_kill(struct pvr2_stream *sp)
+{
+	struct pvr2_buffer *bp;
+	mutex_lock(&sp->mutex);
+	do {
+		pvr2_stream_internal_flush(sp);
+		while ((bp = pvr2_stream_get_ready_buffer(sp)) != NULL) {
+			pvr2_buffer_set_idle(bp);
+		}
+		if (sp->buffer_total_count != sp->buffer_target_count) {
+			pvr2_stream_achieve_buffer_count(sp);
+		}
+	} while (0);
+	mutex_unlock(&sp->mutex);
+}
+
+int pvr2_buffer_queue(struct pvr2_buffer *bp)
+{
+#undef SEED_BUFFER
+#ifdef SEED_BUFFER
+	unsigned int idx;
+	unsigned int val;
+#endif
+	int ret = 0;
+	struct pvr2_stream *sp;
+	if (!bp) return -EINVAL;
+	sp = bp->stream;
+	mutex_lock(&sp->mutex);
+	do {
+		pvr2_buffer_wipe(bp);
+		if (!sp->dev) {
+			ret = -EIO;
+			break;
+		}
+		pvr2_buffer_set_queued(bp);
+#ifdef SEED_BUFFER
+		for (idx = 0; idx < (bp->max_count) / 4; idx++) {
+			val = bp->id << 24;
+			val |= idx;
+			((unsigned int *)(bp->ptr))[idx] = val;
+		}
+#endif
+		bp->status = -EINPROGRESS;
+		usb_fill_bulk_urb(bp->purb,      // struct urb *urb
+				  sp->dev,       // struct usb_device *dev
+				  // endpoint (below)
+				  usb_rcvbulkpipe(sp->dev, sp->endpoint),
+				  bp->ptr,       // void *transfer_buffer
+				  bp->max_count, // int buffer_length
+				  buffer_complete,
+				  bp);
+		usb_submit_urb(bp->purb, GFP_KERNEL);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+	return ret;
+}
+
+int pvr2_buffer_set_buffer(struct pvr2_buffer *bp, void *ptr, unsigned int cnt)
+{
+	int ret = 0;
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	if (!bp) return -EINVAL;
+	sp = bp->stream;
+	mutex_lock(&sp->mutex);
+	do {
+		spin_lock_irqsave(&sp->list_lock, irq_flags);
+		if (bp->state != pvr2_buffer_state_idle) {
+			ret = -EPERM;
+		} else {
+			bp->ptr = ptr;
+			bp->stream->i_bcount -= bp->max_count;
+			bp->max_count = cnt;
+			bp->stream->i_bcount += bp->max_count;
+			pvr2_trace(PVR2_TRACE_BUF_FLOW,
+				   "/*---TRACE_FLOW---*/ bufferPool	%8s cap cap=%07d cnt=%02d",
+				   pvr2_buffer_state_decode(
+					   pvr2_buffer_state_idle),
+				   bp->stream->i_bcount, bp->stream->i_count);
+		}
+		spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+	return ret;
+}
+
+unsigned int pvr2_buffer_get_count(struct pvr2_buffer *bp)
+{
+	return bp->used_count;
+}
+
+int pvr2_buffer_get_status(struct pvr2_buffer *bp)
+{
+	return bp->status;
+}
+
+int pvr2_buffer_get_id(struct pvr2_buffer *bp)
+{
+	return bp->id;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-io.h b/drivers/media/usb/pvrusb2/pvrusb2-io.h
new file mode 100644
index 0000000..e769aeb
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-io.h
@@ -0,0 +1,88 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_IO_H
+#define __PVRUSB2_IO_H
+
+#include <linux/usb.h>
+#include <linux/list.h>
+
+typedef void (*pvr2_stream_callback)(void *);
+
+enum pvr2_buffer_state {
+	pvr2_buffer_state_none = 0,   // Not on any list
+	pvr2_buffer_state_idle = 1,   // Buffer is ready to be used again
+	pvr2_buffer_state_queued = 2, // Buffer has been queued for filling
+	pvr2_buffer_state_ready = 3,  // Buffer has data available
+};
+
+struct pvr2_stream;
+struct pvr2_buffer;
+
+struct pvr2_stream_stats {
+	unsigned int buffers_in_queue;
+	unsigned int buffers_in_idle;
+	unsigned int buffers_in_ready;
+	unsigned int buffers_processed;
+	unsigned int buffers_failed;
+	unsigned int bytes_processed;
+};
+
+/* Initialize / tear down stream structure */
+struct pvr2_stream *pvr2_stream_create(void);
+void pvr2_stream_destroy(struct pvr2_stream *);
+void pvr2_stream_setup(struct pvr2_stream *,
+		       struct usb_device *dev,int endpoint,
+		       unsigned int tolerance);
+void pvr2_stream_set_callback(struct pvr2_stream *,
+			      pvr2_stream_callback func,
+			      void *data);
+void pvr2_stream_get_stats(struct pvr2_stream *,
+			   struct pvr2_stream_stats *,
+			   int zero_counts);
+
+/* Query / set the nominal buffer count */
+int pvr2_stream_get_buffer_count(struct pvr2_stream *);
+int pvr2_stream_set_buffer_count(struct pvr2_stream *,unsigned int);
+
+/* Get a pointer to a buffer that is either idle, ready, or is specified
+   named. */
+struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *);
+struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *);
+struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp,int id);
+
+/* Find out how many buffers are idle or ready */
+int pvr2_stream_get_ready_count(struct pvr2_stream *);
+
+
+/* Kill all pending buffers and throw away any ready buffers as well */
+void pvr2_stream_kill(struct pvr2_stream *);
+
+/* Set up the actual storage for a buffer */
+int pvr2_buffer_set_buffer(struct pvr2_buffer *,void *ptr,unsigned int cnt);
+
+/* Find out size of data in the given ready buffer */
+unsigned int pvr2_buffer_get_count(struct pvr2_buffer *);
+
+/* Retrieve completion code for given ready buffer */
+int pvr2_buffer_get_status(struct pvr2_buffer *);
+
+/* Retrieve ID of given buffer */
+int pvr2_buffer_get_id(struct pvr2_buffer *);
+
+/* Start reading into given buffer (kill it if needed) */
+int pvr2_buffer_queue(struct pvr2_buffer *);
+
+#endif /* __PVRUSB2_IO_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ioread.c b/drivers/media/usb/pvrusb2/pvrusb2-ioread.c
new file mode 100644
index 0000000..602097b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ioread.c
@@ -0,0 +1,495 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include "pvrusb2-ioread.h"
+#include "pvrusb2-debug.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+
+#define BUFFER_COUNT 32
+#define BUFFER_SIZE PAGE_ALIGN(0x4000)
+
+struct pvr2_ioread {
+	struct pvr2_stream *stream;
+	char *buffer_storage[BUFFER_COUNT];
+	char *sync_key_ptr;
+	unsigned int sync_key_len;
+	unsigned int sync_buf_offs;
+	unsigned int sync_state;
+	unsigned int sync_trashed_count;
+	int enabled;         // Streaming is on
+	int spigot_open;     // OK to pass data to client
+	int stream_running;  // Passing data to client now
+
+	/* State relevant to current buffer being read */
+	struct pvr2_buffer *c_buf;
+	char *c_data_ptr;
+	unsigned int c_data_len;
+	unsigned int c_data_offs;
+	struct mutex mutex;
+};
+
+static int pvr2_ioread_init(struct pvr2_ioread *cp)
+{
+	unsigned int idx;
+
+	cp->stream = NULL;
+	mutex_init(&cp->mutex);
+
+	for (idx = 0; idx < BUFFER_COUNT; idx++) {
+		cp->buffer_storage[idx] = kmalloc(BUFFER_SIZE,GFP_KERNEL);
+		if (!(cp->buffer_storage[idx])) break;
+	}
+
+	if (idx < BUFFER_COUNT) {
+		// An allocation appears to have failed
+		for (idx = 0; idx < BUFFER_COUNT; idx++) {
+			if (!(cp->buffer_storage[idx])) continue;
+			kfree(cp->buffer_storage[idx]);
+		}
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static void pvr2_ioread_done(struct pvr2_ioread *cp)
+{
+	unsigned int idx;
+
+	pvr2_ioread_setup(cp,NULL);
+	for (idx = 0; idx < BUFFER_COUNT; idx++) {
+		if (!(cp->buffer_storage[idx])) continue;
+		kfree(cp->buffer_storage[idx]);
+	}
+}
+
+struct pvr2_ioread *pvr2_ioread_create(void)
+{
+	struct pvr2_ioread *cp;
+	cp = kzalloc(sizeof(*cp),GFP_KERNEL);
+	if (!cp) return NULL;
+	pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_create id=%p",cp);
+	if (pvr2_ioread_init(cp) < 0) {
+		kfree(cp);
+		return NULL;
+	}
+	return cp;
+}
+
+void pvr2_ioread_destroy(struct pvr2_ioread *cp)
+{
+	if (!cp) return;
+	pvr2_ioread_done(cp);
+	pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_destroy id=%p",cp);
+	if (cp->sync_key_ptr) {
+		kfree(cp->sync_key_ptr);
+		cp->sync_key_ptr = NULL;
+	}
+	kfree(cp);
+}
+
+void pvr2_ioread_set_sync_key(struct pvr2_ioread *cp,
+			      const char *sync_key_ptr,
+			      unsigned int sync_key_len)
+{
+	if (!cp) return;
+
+	if (!sync_key_ptr) sync_key_len = 0;
+	if ((sync_key_len == cp->sync_key_len) &&
+	    ((!sync_key_len) ||
+	     (!memcmp(sync_key_ptr,cp->sync_key_ptr,sync_key_len)))) return;
+
+	if (sync_key_len != cp->sync_key_len) {
+		if (cp->sync_key_ptr) {
+			kfree(cp->sync_key_ptr);
+			cp->sync_key_ptr = NULL;
+		}
+		cp->sync_key_len = 0;
+		if (sync_key_len) {
+			cp->sync_key_ptr = kmalloc(sync_key_len,GFP_KERNEL);
+			if (cp->sync_key_ptr) {
+				cp->sync_key_len = sync_key_len;
+			}
+		}
+	}
+	if (!cp->sync_key_len) return;
+	memcpy(cp->sync_key_ptr,sync_key_ptr,cp->sync_key_len);
+}
+
+static void pvr2_ioread_stop(struct pvr2_ioread *cp)
+{
+	if (!(cp->enabled)) return;
+	pvr2_trace(PVR2_TRACE_START_STOP,
+		   "/*---TRACE_READ---*/ pvr2_ioread_stop id=%p",cp);
+	pvr2_stream_kill(cp->stream);
+	cp->c_buf = NULL;
+	cp->c_data_ptr = NULL;
+	cp->c_data_len = 0;
+	cp->c_data_offs = 0;
+	cp->enabled = 0;
+	cp->stream_running = 0;
+	cp->spigot_open = 0;
+	if (cp->sync_state) {
+		pvr2_trace(PVR2_TRACE_DATA_FLOW,
+			   "/*---TRACE_READ---*/ sync_state <== 0");
+		cp->sync_state = 0;
+	}
+}
+
+static int pvr2_ioread_start(struct pvr2_ioread *cp)
+{
+	int stat;
+	struct pvr2_buffer *bp;
+	if (cp->enabled) return 0;
+	if (!(cp->stream)) return 0;
+	pvr2_trace(PVR2_TRACE_START_STOP,
+		   "/*---TRACE_READ---*/ pvr2_ioread_start id=%p",cp);
+	while ((bp = pvr2_stream_get_idle_buffer(cp->stream)) != NULL) {
+		stat = pvr2_buffer_queue(bp);
+		if (stat < 0) {
+			pvr2_trace(PVR2_TRACE_DATA_FLOW,
+				   "/*---TRACE_READ---*/ pvr2_ioread_start id=%p error=%d",
+				   cp,stat);
+			pvr2_ioread_stop(cp);
+			return stat;
+		}
+	}
+	cp->enabled = !0;
+	cp->c_buf = NULL;
+	cp->c_data_ptr = NULL;
+	cp->c_data_len = 0;
+	cp->c_data_offs = 0;
+	cp->stream_running = 0;
+	if (cp->sync_key_len) {
+		pvr2_trace(PVR2_TRACE_DATA_FLOW,
+			   "/*---TRACE_READ---*/ sync_state <== 1");
+		cp->sync_state = 1;
+		cp->sync_trashed_count = 0;
+		cp->sync_buf_offs = 0;
+	}
+	cp->spigot_open = 0;
+	return 0;
+}
+
+struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *cp)
+{
+	return cp->stream;
+}
+
+int pvr2_ioread_setup(struct pvr2_ioread *cp,struct pvr2_stream *sp)
+{
+	int ret;
+	unsigned int idx;
+	struct pvr2_buffer *bp;
+
+	mutex_lock(&cp->mutex);
+	do {
+		if (cp->stream) {
+			pvr2_trace(PVR2_TRACE_START_STOP,
+				   "/*---TRACE_READ---*/ pvr2_ioread_setup (tear-down) id=%p",
+				   cp);
+			pvr2_ioread_stop(cp);
+			pvr2_stream_kill(cp->stream);
+			if (pvr2_stream_get_buffer_count(cp->stream)) {
+				pvr2_stream_set_buffer_count(cp->stream,0);
+			}
+			cp->stream = NULL;
+		}
+		if (sp) {
+			pvr2_trace(PVR2_TRACE_START_STOP,
+				   "/*---TRACE_READ---*/ pvr2_ioread_setup (setup) id=%p",
+				   cp);
+			pvr2_stream_kill(sp);
+			ret = pvr2_stream_set_buffer_count(sp,BUFFER_COUNT);
+			if (ret < 0) {
+				mutex_unlock(&cp->mutex);
+				return ret;
+			}
+			for (idx = 0; idx < BUFFER_COUNT; idx++) {
+				bp = pvr2_stream_get_buffer(sp,idx);
+				pvr2_buffer_set_buffer(bp,
+						       cp->buffer_storage[idx],
+						       BUFFER_SIZE);
+			}
+			cp->stream = sp;
+		}
+	} while (0);
+	mutex_unlock(&cp->mutex);
+
+	return 0;
+}
+
+int pvr2_ioread_set_enabled(struct pvr2_ioread *cp,int fl)
+{
+	int ret = 0;
+	if ((!fl) == (!(cp->enabled))) return ret;
+
+	mutex_lock(&cp->mutex);
+	do {
+		if (fl) {
+			ret = pvr2_ioread_start(cp);
+		} else {
+			pvr2_ioread_stop(cp);
+		}
+	} while (0);
+	mutex_unlock(&cp->mutex);
+	return ret;
+}
+
+static int pvr2_ioread_get_buffer(struct pvr2_ioread *cp)
+{
+	int stat;
+
+	while (cp->c_data_len <= cp->c_data_offs) {
+		if (cp->c_buf) {
+			// Flush out current buffer first.
+			stat = pvr2_buffer_queue(cp->c_buf);
+			if (stat < 0) {
+				// Streaming error...
+				pvr2_trace(PVR2_TRACE_DATA_FLOW,
+					   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p queue_error=%d",
+					   cp,stat);
+				pvr2_ioread_stop(cp);
+				return 0;
+			}
+			cp->c_buf = NULL;
+			cp->c_data_ptr = NULL;
+			cp->c_data_len = 0;
+			cp->c_data_offs = 0;
+		}
+		// Now get a freshly filled buffer.
+		cp->c_buf = pvr2_stream_get_ready_buffer(cp->stream);
+		if (!cp->c_buf) break; // Nothing ready; done.
+		cp->c_data_len = pvr2_buffer_get_count(cp->c_buf);
+		if (!cp->c_data_len) {
+			// Nothing transferred.  Was there an error?
+			stat = pvr2_buffer_get_status(cp->c_buf);
+			if (stat < 0) {
+				// Streaming error...
+				pvr2_trace(PVR2_TRACE_DATA_FLOW,
+					   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p buffer_error=%d",
+					   cp,stat);
+				pvr2_ioread_stop(cp);
+				// Give up.
+				return 0;
+			}
+			// Start over...
+			continue;
+		}
+		cp->c_data_offs = 0;
+		cp->c_data_ptr = cp->buffer_storage[
+			pvr2_buffer_get_id(cp->c_buf)];
+	}
+	return !0;
+}
+
+static void pvr2_ioread_filter(struct pvr2_ioread *cp)
+{
+	unsigned int idx;
+	if (!cp->enabled) return;
+	if (cp->sync_state != 1) return;
+
+	// Search the stream for our synchronization key.  This is made
+	// complicated by the fact that in order to be honest with
+	// ourselves here we must search across buffer boundaries...
+	mutex_lock(&cp->mutex);
+	while (1) {
+		// Ensure we have a buffer
+		if (!pvr2_ioread_get_buffer(cp)) break;
+		if (!cp->c_data_len) break;
+
+		// Now walk the buffer contents until we match the key or
+		// run out of buffer data.
+		for (idx = cp->c_data_offs; idx < cp->c_data_len; idx++) {
+			if (cp->sync_buf_offs >= cp->sync_key_len) break;
+			if (cp->c_data_ptr[idx] ==
+			    cp->sync_key_ptr[cp->sync_buf_offs]) {
+				// Found the next key byte
+				(cp->sync_buf_offs)++;
+			} else {
+				// Whoops, mismatched.  Start key over...
+				cp->sync_buf_offs = 0;
+			}
+		}
+
+		// Consume what we've walked through
+		cp->c_data_offs += idx;
+		cp->sync_trashed_count += idx;
+
+		// If we've found the key, then update state and get out.
+		if (cp->sync_buf_offs >= cp->sync_key_len) {
+			cp->sync_trashed_count -= cp->sync_key_len;
+			pvr2_trace(PVR2_TRACE_DATA_FLOW,
+				   "/*---TRACE_READ---*/ sync_state <== 2 (skipped %u bytes)",
+				   cp->sync_trashed_count);
+			cp->sync_state = 2;
+			cp->sync_buf_offs = 0;
+			break;
+		}
+
+		if (cp->c_data_offs < cp->c_data_len) {
+			// Sanity check - should NEVER get here
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "ERROR: pvr2_ioread filter sync problem len=%u offs=%u",
+				   cp->c_data_len,cp->c_data_offs);
+			// Get out so we don't get stuck in an infinite
+			// loop.
+			break;
+		}
+
+		continue; // (for clarity)
+	}
+	mutex_unlock(&cp->mutex);
+}
+
+int pvr2_ioread_avail(struct pvr2_ioread *cp)
+{
+	int ret;
+	if (!(cp->enabled)) {
+		// Stream is not enabled; so this is an I/O error
+		return -EIO;
+	}
+
+	if (cp->sync_state == 1) {
+		pvr2_ioread_filter(cp);
+		if (cp->sync_state == 1) return -EAGAIN;
+	}
+
+	ret = 0;
+	if (cp->stream_running) {
+		if (!pvr2_stream_get_ready_count(cp->stream)) {
+			// No data available at all right now.
+			ret = -EAGAIN;
+		}
+	} else {
+		if (pvr2_stream_get_ready_count(cp->stream) < BUFFER_COUNT/2) {
+			// Haven't buffered up enough yet; try again later
+			ret = -EAGAIN;
+		}
+	}
+
+	if ((!(cp->spigot_open)) != (!(ret == 0))) {
+		cp->spigot_open = (ret == 0);
+		pvr2_trace(PVR2_TRACE_DATA_FLOW,
+			   "/*---TRACE_READ---*/ data is %s",
+			   cp->spigot_open ? "available" : "pending");
+	}
+
+	return ret;
+}
+
+int pvr2_ioread_read(struct pvr2_ioread *cp,void __user *buf,unsigned int cnt)
+{
+	unsigned int copied_cnt;
+	unsigned int bcnt;
+	const char *src;
+	int stat;
+	int ret = 0;
+	unsigned int req_cnt = cnt;
+
+	if (!cnt) {
+		pvr2_trace(PVR2_TRACE_TRAP,
+			   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p ZERO Request? Returning zero.",
+cp);
+		return 0;
+	}
+
+	stat = pvr2_ioread_avail(cp);
+	if (stat < 0) return stat;
+
+	cp->stream_running = !0;
+
+	mutex_lock(&cp->mutex);
+	do {
+
+		// Suck data out of the buffers and copy to the user
+		copied_cnt = 0;
+		if (!buf) cnt = 0;
+		while (1) {
+			if (!pvr2_ioread_get_buffer(cp)) {
+				ret = -EIO;
+				break;
+			}
+
+			if (!cnt) break;
+
+			if (cp->sync_state == 2) {
+				// We're repeating the sync key data into
+				// the stream.
+				src = cp->sync_key_ptr + cp->sync_buf_offs;
+				bcnt = cp->sync_key_len - cp->sync_buf_offs;
+			} else {
+				// Normal buffer copy
+				src = cp->c_data_ptr + cp->c_data_offs;
+				bcnt = cp->c_data_len - cp->c_data_offs;
+			}
+
+			if (!bcnt) break;
+
+			// Don't run past user's buffer
+			if (bcnt > cnt) bcnt = cnt;
+
+			if (copy_to_user(buf,src,bcnt)) {
+				// User supplied a bad pointer?
+				// Give up - this *will* cause data
+				// to be lost.
+				ret = -EFAULT;
+				break;
+			}
+			cnt -= bcnt;
+			buf += bcnt;
+			copied_cnt += bcnt;
+
+			if (cp->sync_state == 2) {
+				// Update offset inside sync key that we're
+				// repeating back out.
+				cp->sync_buf_offs += bcnt;
+				if (cp->sync_buf_offs >= cp->sync_key_len) {
+					// Consumed entire key; switch mode
+					// to normal.
+					pvr2_trace(PVR2_TRACE_DATA_FLOW,
+						   "/*---TRACE_READ---*/ sync_state <== 0");
+					cp->sync_state = 0;
+				}
+			} else {
+				// Update buffer offset.
+				cp->c_data_offs += bcnt;
+			}
+		}
+
+	} while (0);
+	mutex_unlock(&cp->mutex);
+
+	if (!ret) {
+		if (copied_cnt) {
+			// If anything was copied, return that count
+			ret = copied_cnt;
+		} else {
+			// Nothing copied; suggest to caller that another
+			// attempt should be tried again later
+			ret = -EAGAIN;
+		}
+	}
+
+	pvr2_trace(PVR2_TRACE_DATA_FLOW,
+		   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p request=%d result=%d",
+		   cp,req_cnt,ret);
+	return ret;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ioread.h b/drivers/media/usb/pvrusb2/pvrusb2-ioread.h
new file mode 100644
index 0000000..5827ea0
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ioread.h
@@ -0,0 +1,34 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_IOREAD_H
+#define __PVRUSB2_IOREAD_H
+
+#include "pvrusb2-io.h"
+
+struct pvr2_ioread;
+
+struct pvr2_ioread *pvr2_ioread_create(void);
+void pvr2_ioread_destroy(struct pvr2_ioread *);
+int pvr2_ioread_setup(struct pvr2_ioread *,struct pvr2_stream *);
+struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *);
+void pvr2_ioread_set_sync_key(struct pvr2_ioread *,
+			      const char *sync_key_ptr,
+			      unsigned int sync_key_len);
+int pvr2_ioread_set_enabled(struct pvr2_ioread *,int fl);
+int pvr2_ioread_read(struct pvr2_ioread *,void __user *buf,unsigned int cnt);
+int pvr2_ioread_avail(struct pvr2_ioread *);
+
+#endif /* __PVRUSB2_IOREAD_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-main.c b/drivers/media/usb/pvrusb2/pvrusb2-main.c
new file mode 100644
index 0000000..cbe2c3a
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-main.c
@@ -0,0 +1,167 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-devattr.h"
+#include "pvrusb2-context.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-v4l2.h"
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+#include "pvrusb2-sysfs.h"
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+#define DRIVER_AUTHOR "Mike Isely <isely@pobox.com>"
+#define DRIVER_DESC "Hauppauge WinTV-PVR-USB2 MPEG2 Encoder/Tuner"
+#define DRIVER_VERSION "V4L in-tree version"
+
+#define DEFAULT_DEBUG_MASK (PVR2_TRACE_ERROR_LEGS| \
+			    PVR2_TRACE_INFO| \
+			    PVR2_TRACE_STD| \
+			    PVR2_TRACE_TOLERANCE| \
+			    PVR2_TRACE_TRAP| \
+			    0)
+
+int pvrusb2_debug = DEFAULT_DEBUG_MASK;
+
+module_param_named(debug,pvrusb2_debug,int,S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug trace mask");
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+static struct pvr2_sysfs_class *class_ptr = NULL;
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+static void pvr_setup_attach(struct pvr2_context *pvr)
+{
+	/* Create association with v4l layer */
+	pvr2_v4l2_create(pvr);
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+	/* Create association with dvb layer */
+	pvr2_dvb_create(pvr);
+#endif
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+	pvr2_sysfs_create(pvr,class_ptr);
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+}
+
+static int pvr_probe(struct usb_interface *intf,
+		     const struct usb_device_id *devid)
+{
+	struct pvr2_context *pvr;
+
+	/* Create underlying hardware interface */
+	pvr = pvr2_context_create(intf,devid,pvr_setup_attach);
+	if (!pvr) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to create hdw handler");
+		return -ENOMEM;
+	}
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_probe(pvr=%p)",pvr);
+
+	usb_set_intfdata(intf, pvr);
+
+	return 0;
+}
+
+/*
+ * pvr_disconnect()
+ *
+ */
+static void pvr_disconnect(struct usb_interface *intf)
+{
+	struct pvr2_context *pvr = usb_get_intfdata(intf);
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) BEGIN",pvr);
+
+	usb_set_intfdata (intf, NULL);
+	pvr2_context_disconnect(pvr);
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) DONE",pvr);
+
+}
+
+static struct usb_driver pvr_driver = {
+	.name =         "pvrusb2",
+	.id_table =     pvr2_device_table,
+	.probe =        pvr_probe,
+	.disconnect =   pvr_disconnect
+};
+
+/*
+ * pvr_init() / pvr_exit()
+ *
+ * This code is run to initialize/exit the driver.
+ *
+ */
+static int __init pvr_init(void)
+{
+	int ret;
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_init");
+
+	ret = pvr2_context_global_init();
+	if (ret != 0) {
+		pvr2_trace(PVR2_TRACE_INIT,"pvr_init failure code=%d",ret);
+		return ret;
+	}
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+	class_ptr = pvr2_sysfs_class_create();
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+	ret = usb_register(&pvr_driver);
+
+	if (ret == 0)
+		printk(KERN_INFO "pvrusb2: " DRIVER_VERSION ":"
+		       DRIVER_DESC "\n");
+	if (pvrusb2_debug)
+		printk(KERN_INFO "pvrusb2: Debug mask is %d (0x%x)\n",
+		       pvrusb2_debug,pvrusb2_debug);
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_init complete");
+
+	return ret;
+}
+
+static void __exit pvr_exit(void)
+{
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_exit");
+
+	usb_deregister(&pvr_driver);
+
+	pvr2_context_global_done();
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+	pvr2_sysfs_class_destroy(class_ptr);
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_exit complete");
+}
+
+module_init(pvr_init);
+module_exit(pvr_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.9.1");
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-std.c b/drivers/media/usb/pvrusb2/pvrusb2-std.c
new file mode 100644
index 0000000..6b651f8
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-std.c
@@ -0,0 +1,395 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include "pvrusb2-std.h"
+#include "pvrusb2-debug.h"
+#include <asm/string.h>
+#include <linux/slab.h>
+
+struct std_name {
+	const char *name;
+	v4l2_std_id id;
+};
+
+
+#define CSTD_PAL \
+	(V4L2_STD_PAL_B| \
+	 V4L2_STD_PAL_B1| \
+	 V4L2_STD_PAL_G| \
+	 V4L2_STD_PAL_H| \
+	 V4L2_STD_PAL_I| \
+	 V4L2_STD_PAL_D| \
+	 V4L2_STD_PAL_D1| \
+	 V4L2_STD_PAL_K| \
+	 V4L2_STD_PAL_M| \
+	 V4L2_STD_PAL_N| \
+	 V4L2_STD_PAL_Nc| \
+	 V4L2_STD_PAL_60)
+
+#define CSTD_NTSC \
+	(V4L2_STD_NTSC_M| \
+	 V4L2_STD_NTSC_M_JP| \
+	 V4L2_STD_NTSC_M_KR| \
+	 V4L2_STD_NTSC_443)
+
+#define CSTD_ATSC \
+	(V4L2_STD_ATSC_8_VSB| \
+	 V4L2_STD_ATSC_16_VSB)
+
+#define CSTD_SECAM \
+	(V4L2_STD_SECAM_B| \
+	 V4L2_STD_SECAM_D| \
+	 V4L2_STD_SECAM_G| \
+	 V4L2_STD_SECAM_H| \
+	 V4L2_STD_SECAM_K| \
+	 V4L2_STD_SECAM_K1| \
+	 V4L2_STD_SECAM_L| \
+	 V4L2_STD_SECAM_LC)
+
+#define TSTD_B   (V4L2_STD_PAL_B|V4L2_STD_SECAM_B)
+#define TSTD_B1  (V4L2_STD_PAL_B1)
+#define TSTD_D   (V4L2_STD_PAL_D|V4L2_STD_SECAM_D)
+#define TSTD_D1  (V4L2_STD_PAL_D1)
+#define TSTD_G   (V4L2_STD_PAL_G|V4L2_STD_SECAM_G)
+#define TSTD_H   (V4L2_STD_PAL_H|V4L2_STD_SECAM_H)
+#define TSTD_I   (V4L2_STD_PAL_I)
+#define TSTD_K   (V4L2_STD_PAL_K|V4L2_STD_SECAM_K)
+#define TSTD_K1  (V4L2_STD_SECAM_K1)
+#define TSTD_L   (V4L2_STD_SECAM_L)
+#define TSTD_M   (V4L2_STD_PAL_M|V4L2_STD_NTSC_M)
+#define TSTD_N   (V4L2_STD_PAL_N)
+#define TSTD_Nc  (V4L2_STD_PAL_Nc)
+#define TSTD_60  (V4L2_STD_PAL_60)
+
+#define CSTD_ALL (CSTD_PAL|CSTD_NTSC|CSTD_ATSC|CSTD_SECAM)
+
+/* Mapping of standard bits to color system */
+static const struct std_name std_groups[] = {
+	{"PAL",CSTD_PAL},
+	{"NTSC",CSTD_NTSC},
+	{"SECAM",CSTD_SECAM},
+	{"ATSC",CSTD_ATSC},
+};
+
+/* Mapping of standard bits to modulation system */
+static const struct std_name std_items[] = {
+	{"B",TSTD_B},
+	{"B1",TSTD_B1},
+	{"D",TSTD_D},
+	{"D1",TSTD_D1},
+	{"G",TSTD_G},
+	{"H",TSTD_H},
+	{"I",TSTD_I},
+	{"K",TSTD_K},
+	{"K1",TSTD_K1},
+	{"L",TSTD_L},
+	{"LC",V4L2_STD_SECAM_LC},
+	{"M",TSTD_M},
+	{"Mj",V4L2_STD_NTSC_M_JP},
+	{"443",V4L2_STD_NTSC_443},
+	{"Mk",V4L2_STD_NTSC_M_KR},
+	{"N",TSTD_N},
+	{"Nc",TSTD_Nc},
+	{"60",TSTD_60},
+	{"8VSB",V4L2_STD_ATSC_8_VSB},
+	{"16VSB",V4L2_STD_ATSC_16_VSB},
+};
+
+
+// Search an array of std_name structures and return a pointer to the
+// element with the matching name.
+static const struct std_name *find_std_name(const struct std_name *arrPtr,
+					    unsigned int arrSize,
+					    const char *bufPtr,
+					    unsigned int bufSize)
+{
+	unsigned int idx;
+	const struct std_name *p;
+	for (idx = 0; idx < arrSize; idx++) {
+		p = arrPtr + idx;
+		if (strlen(p->name) != bufSize) continue;
+		if (!memcmp(bufPtr,p->name,bufSize)) return p;
+	}
+	return NULL;
+}
+
+
+int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr,
+		       unsigned int bufSize)
+{
+	v4l2_std_id id = 0;
+	v4l2_std_id cmsk = 0;
+	v4l2_std_id t;
+	int mMode = 0;
+	unsigned int cnt;
+	char ch;
+	const struct std_name *sp;
+
+	while (bufSize) {
+		if (!mMode) {
+			cnt = 0;
+			while ((cnt < bufSize) && (bufPtr[cnt] != '-')) cnt++;
+			if (cnt >= bufSize) return 0; // No more characters
+			sp = find_std_name(std_groups, ARRAY_SIZE(std_groups),
+					   bufPtr,cnt);
+			if (!sp) return 0; // Illegal color system name
+			cnt++;
+			bufPtr += cnt;
+			bufSize -= cnt;
+			mMode = !0;
+			cmsk = sp->id;
+			continue;
+		}
+		cnt = 0;
+		while (cnt < bufSize) {
+			ch = bufPtr[cnt];
+			if (ch == ';') {
+				mMode = 0;
+				break;
+			}
+			if (ch == '/') break;
+			cnt++;
+		}
+		sp = find_std_name(std_items, ARRAY_SIZE(std_items),
+				   bufPtr,cnt);
+		if (!sp) return 0; // Illegal modulation system ID
+		t = sp->id & cmsk;
+		if (!t) return 0; // Specific color + modulation system illegal
+		id |= t;
+		if (cnt < bufSize) cnt++;
+		bufPtr += cnt;
+		bufSize -= cnt;
+	}
+
+	if (idPtr) *idPtr = id;
+	return !0;
+}
+
+
+unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize,
+				v4l2_std_id id)
+{
+	unsigned int idx1,idx2;
+	const struct std_name *ip,*gp;
+	int gfl,cfl;
+	unsigned int c1,c2;
+	cfl = 0;
+	c1 = 0;
+	for (idx1 = 0; idx1 < ARRAY_SIZE(std_groups); idx1++) {
+		gp = std_groups + idx1;
+		gfl = 0;
+		for (idx2 = 0; idx2 < ARRAY_SIZE(std_items); idx2++) {
+			ip = std_items + idx2;
+			if (!(gp->id & ip->id & id)) continue;
+			if (!gfl) {
+				if (cfl) {
+					c2 = scnprintf(bufPtr,bufSize,";");
+					c1 += c2;
+					bufSize -= c2;
+					bufPtr += c2;
+				}
+				cfl = !0;
+				c2 = scnprintf(bufPtr,bufSize,
+					       "%s-",gp->name);
+				gfl = !0;
+			} else {
+				c2 = scnprintf(bufPtr,bufSize,"/");
+			}
+			c1 += c2;
+			bufSize -= c2;
+			bufPtr += c2;
+			c2 = scnprintf(bufPtr,bufSize,
+				       ip->name);
+			c1 += c2;
+			bufSize -= c2;
+			bufPtr += c2;
+		}
+	}
+	return c1;
+}
+
+
+// Template data for possible enumerated video standards.  Here we group
+// standards which share common frame rates and resolution.
+static struct v4l2_standard generic_standards[] = {
+	{
+		.id             = (TSTD_B|TSTD_B1|
+				   TSTD_D|TSTD_D1|
+				   TSTD_G|
+				   TSTD_H|
+				   TSTD_I|
+				   TSTD_K|TSTD_K1|
+				   TSTD_L|
+				   V4L2_STD_SECAM_LC |
+				   TSTD_N|TSTD_Nc),
+		.frameperiod    =
+		{
+			.numerator  = 1,
+			.denominator= 25
+		},
+		.framelines     = 625,
+		.reserved       = {0,0,0,0}
+	}, {
+		.id             = (TSTD_M|
+				   V4L2_STD_NTSC_M_JP|
+				   V4L2_STD_NTSC_M_KR),
+		.frameperiod    =
+		{
+			.numerator  = 1001,
+			.denominator= 30000
+		},
+		.framelines     = 525,
+		.reserved       = {0,0,0,0}
+	}, { // This is a total wild guess
+		.id             = (TSTD_60),
+		.frameperiod    =
+		{
+			.numerator  = 1001,
+			.denominator= 30000
+		},
+		.framelines     = 525,
+		.reserved       = {0,0,0,0}
+	}, { // This is total wild guess
+		.id             = V4L2_STD_NTSC_443,
+		.frameperiod    =
+		{
+			.numerator  = 1001,
+			.denominator= 30000
+		},
+		.framelines     = 525,
+		.reserved       = {0,0,0,0}
+	}
+};
+
+static struct v4l2_standard *match_std(v4l2_std_id id)
+{
+	unsigned int idx;
+	for (idx = 0; idx < ARRAY_SIZE(generic_standards); idx++) {
+		if (generic_standards[idx].id & id) {
+			return generic_standards + idx;
+		}
+	}
+	return NULL;
+}
+
+static int pvr2_std_fill(struct v4l2_standard *std,v4l2_std_id id)
+{
+	struct v4l2_standard *template;
+	int idx;
+	unsigned int bcnt;
+	template = match_std(id);
+	if (!template) return 0;
+	idx = std->index;
+	memcpy(std,template,sizeof(*template));
+	std->index = idx;
+	std->id = id;
+	bcnt = pvr2_std_id_to_str(std->name,sizeof(std->name)-1,id);
+	std->name[bcnt] = 0;
+	pvr2_trace(PVR2_TRACE_STD,"Set up standard idx=%u name=%s",
+		   std->index,std->name);
+	return !0;
+}
+
+/* These are special cases of combined standards that we should enumerate
+   separately if the component pieces are present. */
+static v4l2_std_id std_mixes[] = {
+	V4L2_STD_PAL_B | V4L2_STD_PAL_G,
+	V4L2_STD_PAL_D | V4L2_STD_PAL_K,
+	V4L2_STD_SECAM_B | V4L2_STD_SECAM_G,
+	V4L2_STD_SECAM_D | V4L2_STD_SECAM_K,
+};
+
+struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr,
+					   v4l2_std_id id)
+{
+	unsigned int std_cnt = 0;
+	unsigned int idx,bcnt,idx2;
+	v4l2_std_id idmsk,cmsk,fmsk;
+	struct v4l2_standard *stddefs;
+
+	if (pvrusb2_debug & PVR2_TRACE_STD) {
+		char buf[100];
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),id);
+		pvr2_trace(
+			PVR2_TRACE_STD,"Mapping standards mask=0x%x (%.*s)",
+			(int)id,bcnt,buf);
+	}
+
+	*countptr = 0;
+	std_cnt = 0;
+	fmsk = 0;
+	for (idmsk = 1, cmsk = id; cmsk; idmsk <<= 1) {
+		if (!(idmsk & cmsk)) continue;
+		cmsk &= ~idmsk;
+		if (match_std(idmsk)) {
+			std_cnt++;
+			continue;
+		}
+		fmsk |= idmsk;
+	}
+
+	for (idx2 = 0; idx2 < ARRAY_SIZE(std_mixes); idx2++) {
+		if ((id & std_mixes[idx2]) == std_mixes[idx2]) std_cnt++;
+	}
+
+	/* Don't complain about ATSC standard values */
+	fmsk &= ~CSTD_ATSC;
+
+	if (fmsk) {
+		char buf[100];
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),fmsk);
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"WARNING: Failed to classify the following standard(s): %.*s",
+			bcnt,buf);
+	}
+
+	pvr2_trace(PVR2_TRACE_STD,"Setting up %u unique standard(s)",
+		   std_cnt);
+	if (!std_cnt) return NULL; // paranoia
+
+	stddefs = kcalloc(std_cnt, sizeof(struct v4l2_standard),
+			  GFP_KERNEL);
+	if (!stddefs)
+		return NULL;
+
+	for (idx = 0; idx < std_cnt; idx++)
+		stddefs[idx].index = idx;
+
+	idx = 0;
+
+	/* Enumerate potential special cases */
+	for (idx2 = 0; (idx2 < ARRAY_SIZE(std_mixes)) && (idx < std_cnt);
+	     idx2++) {
+		if (!(id & std_mixes[idx2])) continue;
+		if (pvr2_std_fill(stddefs+idx,std_mixes[idx2])) idx++;
+	}
+	/* Now enumerate individual pieces */
+	for (idmsk = 1, cmsk = id; cmsk && (idx < std_cnt); idmsk <<= 1) {
+		if (!(idmsk & cmsk)) continue;
+		cmsk &= ~idmsk;
+		if (!pvr2_std_fill(stddefs+idx,idmsk)) continue;
+		idx++;
+	}
+
+	*countptr = std_cnt;
+	return stddefs;
+}
+
+v4l2_std_id pvr2_std_get_usable(void)
+{
+	return CSTD_ALL;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-std.h b/drivers/media/usb/pvrusb2/pvrusb2-std.h
new file mode 100644
index 0000000..b48304f
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-std.h
@@ -0,0 +1,45 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_STD_H
+#define __PVRUSB2_STD_H
+
+#include <linux/videodev2.h>
+
+// Convert string describing one or more video standards into a mask of V4L
+// standard bits.  Return true if conversion succeeds otherwise return
+// false.  String is expected to be of the form: C1-x/y;C2-a/b where C1 and
+// C2 are color system names (e.g. "PAL", "NTSC") and x, y, a, and b are
+// modulation schemes (e.g. "M", "B", "G", etc).
+int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr,
+		       unsigned int bufSize);
+
+// Convert any arbitrary set of video standard bits into an unambiguous
+// readable string.  Return value is the number of bytes consumed in the
+// buffer.  The formatted string is of a form that can be parsed by our
+// sibling std_std_to_id() function.
+unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize,
+				v4l2_std_id id);
+
+// Create an array of suitable v4l2_standard structures given a bit mask of
+// video standards to support.  The array is allocated from the heap, and
+// the number of elements is returned in the first argument.
+struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr,
+					   v4l2_std_id id);
+
+// Return mask of which video standard bits are valid
+v4l2_std_id pvr2_std_get_usable(void);
+
+#endif /* __PVRUSB2_STD_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c
new file mode 100644
index 0000000..7bc6d09
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c
@@ -0,0 +1,845 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/slab.h>
+#include "pvrusb2-sysfs.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+#include "pvrusb2-debugifc.h"
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+#define pvr2_sysfs_trace(...) pvr2_trace(PVR2_TRACE_SYSFS,__VA_ARGS__)
+
+struct pvr2_sysfs {
+	struct pvr2_channel channel;
+	struct device *class_dev;
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+	struct pvr2_sysfs_debugifc *debugifc;
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+	struct pvr2_sysfs_ctl_item *item_first;
+	struct pvr2_sysfs_ctl_item *item_last;
+	struct device_attribute attr_v4l_minor_number;
+	struct device_attribute attr_v4l_radio_minor_number;
+	struct device_attribute attr_unit_number;
+	struct device_attribute attr_bus_info;
+	struct device_attribute attr_hdw_name;
+	struct device_attribute attr_hdw_desc;
+	int v4l_minor_number_created_ok;
+	int v4l_radio_minor_number_created_ok;
+	int unit_number_created_ok;
+	int bus_info_created_ok;
+	int hdw_name_created_ok;
+	int hdw_desc_created_ok;
+};
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+struct pvr2_sysfs_debugifc {
+	struct device_attribute attr_debugcmd;
+	struct device_attribute attr_debuginfo;
+	int debugcmd_created_ok;
+	int debuginfo_created_ok;
+};
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+struct pvr2_sysfs_ctl_item {
+	struct device_attribute attr_name;
+	struct device_attribute attr_type;
+	struct device_attribute attr_min;
+	struct device_attribute attr_max;
+	struct device_attribute attr_def;
+	struct device_attribute attr_enum;
+	struct device_attribute attr_bits;
+	struct device_attribute attr_val;
+	struct device_attribute attr_custom;
+	struct pvr2_ctrl *cptr;
+	int ctl_id;
+	struct pvr2_sysfs *chptr;
+	struct pvr2_sysfs_ctl_item *item_next;
+	struct attribute *attr_gen[8];
+	struct attribute_group grp;
+	int created_ok;
+	char name[80];
+};
+
+struct pvr2_sysfs_class {
+	struct class class;
+};
+
+static ssize_t show_name(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	const char *name;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_name);
+	name = pvr2_ctrl_get_desc(cip->cptr);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_name(cid=%d) is %s",
+			 cip->chptr, cip->ctl_id, name);
+	if (!name) return -EINVAL;
+	return scnprintf(buf, PAGE_SIZE, "%s\n", name);
+}
+
+static ssize_t show_type(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	const char *name;
+	enum pvr2_ctl_type tp;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_type);
+	tp = pvr2_ctrl_get_type(cip->cptr);
+	switch (tp) {
+	case pvr2_ctl_int: name = "integer"; break;
+	case pvr2_ctl_enum: name = "enum"; break;
+	case pvr2_ctl_bitmask: name = "bitmask"; break;
+	case pvr2_ctl_bool: name = "boolean"; break;
+	default: name = "?"; break;
+	}
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_type(cid=%d) is %s",
+			 cip->chptr, cip->ctl_id, name);
+	return scnprintf(buf, PAGE_SIZE, "%s\n", name);
+}
+
+static ssize_t show_min(struct device *class_dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	long val;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_min);
+	val = pvr2_ctrl_get_min(cip->cptr);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_min(cid=%d) is %ld",
+			 cip->chptr, cip->ctl_id, val);
+	return scnprintf(buf, PAGE_SIZE, "%ld\n", val);
+}
+
+static ssize_t show_max(struct device *class_dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	long val;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_max);
+	val = pvr2_ctrl_get_max(cip->cptr);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_max(cid=%d) is %ld",
+			 cip->chptr, cip->ctl_id, val);
+	return scnprintf(buf, PAGE_SIZE, "%ld\n", val);
+}
+
+static ssize_t show_def(struct device *class_dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int val;
+	int ret;
+	unsigned int cnt = 0;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_def);
+	ret = pvr2_ctrl_get_def(cip->cptr, &val);
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_value_to_sym(cip->cptr, ~0, val,
+				     buf, PAGE_SIZE - 1, &cnt);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_def(cid=%d) is %.*s (%d)",
+			 cip->chptr, cip->ctl_id, cnt, buf, val);
+	buf[cnt] = '\n';
+	return cnt + 1;
+}
+
+static ssize_t show_val_norm(struct device *class_dev,
+			     struct device_attribute *attr,
+			     char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int val;
+	int ret;
+	unsigned int cnt = 0;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_val);
+	ret = pvr2_ctrl_get_value(cip->cptr, &val);
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_value_to_sym(cip->cptr, ~0, val,
+				     buf, PAGE_SIZE - 1, &cnt);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_norm(cid=%d) is %.*s (%d)",
+			 cip->chptr, cip->ctl_id, cnt, buf, val);
+	buf[cnt] = '\n';
+	return cnt+1;
+}
+
+static ssize_t show_val_custom(struct device *class_dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int val;
+	int ret;
+	unsigned int cnt = 0;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_custom);
+	ret = pvr2_ctrl_get_value(cip->cptr, &val);
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_custom_value_to_sym(cip->cptr, ~0, val,
+					    buf, PAGE_SIZE - 1, &cnt);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_custom(cid=%d) is %.*s (%d)",
+			 cip->chptr, cip->ctl_id, cnt, buf, val);
+	buf[cnt] = '\n';
+	return cnt+1;
+}
+
+static ssize_t show_enum(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	long val;
+	unsigned int bcnt, ccnt, ecnt;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_enum);
+	ecnt = pvr2_ctrl_get_cnt(cip->cptr);
+	bcnt = 0;
+	for (val = 0; val < ecnt; val++) {
+		pvr2_ctrl_get_valname(cip->cptr, val, buf + bcnt,
+				      PAGE_SIZE - bcnt, &ccnt);
+		if (!ccnt) continue;
+		bcnt += ccnt;
+		if (bcnt >= PAGE_SIZE) break;
+		buf[bcnt] = '\n';
+		bcnt++;
+	}
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_enum(cid=%d)",
+			 cip->chptr, cip->ctl_id);
+	return bcnt;
+}
+
+static ssize_t show_bits(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int valid_bits, msk;
+	unsigned int bcnt, ccnt;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_bits);
+	valid_bits = pvr2_ctrl_get_mask(cip->cptr);
+	bcnt = 0;
+	for (msk = 1; valid_bits; msk <<= 1) {
+		if (!(msk & valid_bits)) continue;
+		valid_bits &= ~msk;
+		pvr2_ctrl_get_valname(cip->cptr, msk, buf + bcnt,
+				      PAGE_SIZE - bcnt, &ccnt);
+		bcnt += ccnt;
+		if (bcnt >= PAGE_SIZE) break;
+		buf[bcnt] = '\n';
+		bcnt++;
+	}
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_bits(cid=%d)",
+			 cip->chptr, cip->ctl_id);
+	return bcnt;
+}
+
+static int store_val_any(struct pvr2_sysfs_ctl_item *cip, int customfl,
+			 const char *buf,unsigned int count)
+{
+	int ret;
+	int mask,val;
+	if (customfl) {
+		ret = pvr2_ctrl_custom_sym_to_value(cip->cptr, buf, count,
+						    &mask, &val);
+	} else {
+		ret = pvr2_ctrl_sym_to_value(cip->cptr, buf, count,
+					     &mask, &val);
+	}
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_set_mask_value(cip->cptr, mask, val);
+	pvr2_hdw_commit_ctl(cip->chptr->channel.hdw);
+	return ret;
+}
+
+static ssize_t store_val_norm(struct device *class_dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int ret;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_val);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_norm(cid=%d) \"%.*s\"",
+			 cip->chptr, cip->ctl_id, (int)count, buf);
+	ret = store_val_any(cip, 0, buf, count);
+	if (!ret) ret = count;
+	return ret;
+}
+
+static ssize_t store_val_custom(struct device *class_dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int ret;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_custom);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_custom(cid=%d) \"%.*s\"",
+			 cip->chptr, cip->ctl_id, (int)count, buf);
+	ret = store_val_any(cip, 1, buf, count);
+	if (!ret) ret = count;
+	return ret;
+}
+
+static void pvr2_sysfs_add_control(struct pvr2_sysfs *sfp,int ctl_id)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	struct pvr2_ctrl *cptr;
+	unsigned int cnt,acnt;
+	int ret;
+
+	cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,ctl_id);
+	if (!cptr) return;
+
+	cip = kzalloc(sizeof(*cip),GFP_KERNEL);
+	if (!cip) return;
+	pvr2_sysfs_trace("Creating pvr2_sysfs_ctl_item id=%p",cip);
+
+	cip->cptr = cptr;
+	cip->ctl_id = ctl_id;
+
+	cip->chptr = sfp;
+	cip->item_next = NULL;
+	if (sfp->item_last) {
+		sfp->item_last->item_next = cip;
+	} else {
+		sfp->item_first = cip;
+	}
+	sfp->item_last = cip;
+
+	sysfs_attr_init(&cip->attr_name.attr);
+	cip->attr_name.attr.name = "name";
+	cip->attr_name.attr.mode = S_IRUGO;
+	cip->attr_name.show = show_name;
+
+	sysfs_attr_init(&cip->attr_type.attr);
+	cip->attr_type.attr.name = "type";
+	cip->attr_type.attr.mode = S_IRUGO;
+	cip->attr_type.show = show_type;
+
+	sysfs_attr_init(&cip->attr_min.attr);
+	cip->attr_min.attr.name = "min_val";
+	cip->attr_min.attr.mode = S_IRUGO;
+	cip->attr_min.show = show_min;
+
+	sysfs_attr_init(&cip->attr_max.attr);
+	cip->attr_max.attr.name = "max_val";
+	cip->attr_max.attr.mode = S_IRUGO;
+	cip->attr_max.show = show_max;
+
+	sysfs_attr_init(&cip->attr_def.attr);
+	cip->attr_def.attr.name = "def_val";
+	cip->attr_def.attr.mode = S_IRUGO;
+	cip->attr_def.show = show_def;
+
+	sysfs_attr_init(&cip->attr_val.attr);
+	cip->attr_val.attr.name = "cur_val";
+	cip->attr_val.attr.mode = S_IRUGO;
+
+	sysfs_attr_init(&cip->attr_custom.attr);
+	cip->attr_custom.attr.name = "custom_val";
+	cip->attr_custom.attr.mode = S_IRUGO;
+
+	sysfs_attr_init(&cip->attr_enum.attr);
+	cip->attr_enum.attr.name = "enum_val";
+	cip->attr_enum.attr.mode = S_IRUGO;
+	cip->attr_enum.show = show_enum;
+
+	sysfs_attr_init(&cip->attr_bits.attr);
+	cip->attr_bits.attr.name = "bit_val";
+	cip->attr_bits.attr.mode = S_IRUGO;
+	cip->attr_bits.show = show_bits;
+
+	if (pvr2_ctrl_is_writable(cptr)) {
+		cip->attr_val.attr.mode |= S_IWUSR|S_IWGRP;
+		cip->attr_custom.attr.mode |= S_IWUSR|S_IWGRP;
+	}
+
+	acnt = 0;
+	cip->attr_gen[acnt++] = &cip->attr_name.attr;
+	cip->attr_gen[acnt++] = &cip->attr_type.attr;
+	cip->attr_gen[acnt++] = &cip->attr_val.attr;
+	cip->attr_gen[acnt++] = &cip->attr_def.attr;
+	cip->attr_val.show = show_val_norm;
+	cip->attr_val.store = store_val_norm;
+	if (pvr2_ctrl_has_custom_symbols(cptr)) {
+		cip->attr_gen[acnt++] = &cip->attr_custom.attr;
+		cip->attr_custom.show = show_val_custom;
+		cip->attr_custom.store = store_val_custom;
+	}
+	switch (pvr2_ctrl_get_type(cptr)) {
+	case pvr2_ctl_enum:
+		// Control is an enumeration
+		cip->attr_gen[acnt++] = &cip->attr_enum.attr;
+		break;
+	case pvr2_ctl_int:
+		// Control is an integer
+		cip->attr_gen[acnt++] = &cip->attr_min.attr;
+		cip->attr_gen[acnt++] = &cip->attr_max.attr;
+		break;
+	case pvr2_ctl_bitmask:
+		// Control is an bitmask
+		cip->attr_gen[acnt++] = &cip->attr_bits.attr;
+		break;
+	default: break;
+	}
+
+	cnt = scnprintf(cip->name,sizeof(cip->name)-1,"ctl_%s",
+			pvr2_ctrl_get_name(cptr));
+	cip->name[cnt] = 0;
+	cip->grp.name = cip->name;
+	cip->grp.attrs = cip->attr_gen;
+
+	ret = sysfs_create_group(&sfp->class_dev->kobj,&cip->grp);
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "sysfs_create_group error: %d",
+			   ret);
+		return;
+	}
+	cip->created_ok = !0;
+}
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+static ssize_t debuginfo_show(struct device *, struct device_attribute *,
+			      char *);
+static ssize_t debugcmd_show(struct device *, struct device_attribute *,
+			     char *);
+static ssize_t debugcmd_store(struct device *, struct device_attribute *,
+			      const char *, size_t count);
+
+static void pvr2_sysfs_add_debugifc(struct pvr2_sysfs *sfp)
+{
+	struct pvr2_sysfs_debugifc *dip;
+	int ret;
+
+	dip = kzalloc(sizeof(*dip),GFP_KERNEL);
+	if (!dip) return;
+	sysfs_attr_init(&dip->attr_debugcmd.attr);
+	dip->attr_debugcmd.attr.name = "debugcmd";
+	dip->attr_debugcmd.attr.mode = S_IRUGO|S_IWUSR|S_IWGRP;
+	dip->attr_debugcmd.show = debugcmd_show;
+	dip->attr_debugcmd.store = debugcmd_store;
+	sysfs_attr_init(&dip->attr_debuginfo.attr);
+	dip->attr_debuginfo.attr.name = "debuginfo";
+	dip->attr_debuginfo.attr.mode = S_IRUGO;
+	dip->attr_debuginfo.show = debuginfo_show;
+	sfp->debugifc = dip;
+	ret = device_create_file(sfp->class_dev,&dip->attr_debugcmd);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		dip->debugcmd_created_ok = !0;
+	}
+	ret = device_create_file(sfp->class_dev,&dip->attr_debuginfo);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		dip->debuginfo_created_ok = !0;
+	}
+}
+
+
+static void pvr2_sysfs_tear_down_debugifc(struct pvr2_sysfs *sfp)
+{
+	if (!sfp->debugifc) return;
+	if (sfp->debugifc->debuginfo_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->debugifc->attr_debuginfo);
+	}
+	if (sfp->debugifc->debugcmd_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->debugifc->attr_debugcmd);
+	}
+	kfree(sfp->debugifc);
+	sfp->debugifc = NULL;
+}
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+
+static void pvr2_sysfs_add_controls(struct pvr2_sysfs *sfp)
+{
+	unsigned int idx,cnt;
+	cnt = pvr2_hdw_get_ctrl_count(sfp->channel.hdw);
+	for (idx = 0; idx < cnt; idx++) {
+		pvr2_sysfs_add_control(sfp,idx);
+	}
+}
+
+
+static void pvr2_sysfs_tear_down_controls(struct pvr2_sysfs *sfp)
+{
+	struct pvr2_sysfs_ctl_item *cip1,*cip2;
+	for (cip1 = sfp->item_first; cip1; cip1 = cip2) {
+		cip2 = cip1->item_next;
+		if (cip1->created_ok) {
+			sysfs_remove_group(&sfp->class_dev->kobj,&cip1->grp);
+		}
+		pvr2_sysfs_trace("Destroying pvr2_sysfs_ctl_item id=%p",cip1);
+		kfree(cip1);
+	}
+}
+
+
+static void pvr2_sysfs_class_release(struct class *class)
+{
+	struct pvr2_sysfs_class *clp;
+	clp = container_of(class,struct pvr2_sysfs_class,class);
+	pvr2_sysfs_trace("Destroying pvr2_sysfs_class id=%p",clp);
+	kfree(clp);
+}
+
+
+static void pvr2_sysfs_release(struct device *class_dev)
+{
+	pvr2_sysfs_trace("Releasing class_dev id=%p",class_dev);
+	kfree(class_dev);
+}
+
+
+static void class_dev_destroy(struct pvr2_sysfs *sfp)
+{
+	struct device *dev;
+	if (!sfp->class_dev) return;
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+	pvr2_sysfs_tear_down_debugifc(sfp);
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+	pvr2_sysfs_tear_down_controls(sfp);
+	if (sfp->hdw_desc_created_ok) {
+		device_remove_file(sfp->class_dev,
+				   &sfp->attr_hdw_desc);
+	}
+	if (sfp->hdw_name_created_ok) {
+		device_remove_file(sfp->class_dev,
+				   &sfp->attr_hdw_name);
+	}
+	if (sfp->bus_info_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_bus_info);
+	}
+	if (sfp->v4l_minor_number_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_v4l_minor_number);
+	}
+	if (sfp->v4l_radio_minor_number_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_v4l_radio_minor_number);
+	}
+	if (sfp->unit_number_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_unit_number);
+	}
+	pvr2_sysfs_trace("Destroying class_dev id=%p",sfp->class_dev);
+	dev_set_drvdata(sfp->class_dev, NULL);
+	dev = sfp->class_dev->parent;
+	sfp->class_dev->parent = NULL;
+	put_device(dev);
+	device_unregister(sfp->class_dev);
+	sfp->class_dev = NULL;
+}
+
+
+static ssize_t v4l_minor_number_show(struct device *class_dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%d\n",
+			 pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw,
+						       pvr2_v4l_type_video));
+}
+
+
+static ssize_t bus_info_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%s\n",
+			 pvr2_hdw_get_bus_info(sfp->channel.hdw));
+}
+
+
+static ssize_t hdw_name_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%s\n",
+			 pvr2_hdw_get_type(sfp->channel.hdw));
+}
+
+
+static ssize_t hdw_desc_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%s\n",
+			 pvr2_hdw_get_desc(sfp->channel.hdw));
+}
+
+
+static ssize_t v4l_radio_minor_number_show(struct device *class_dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%d\n",
+			 pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw,
+						       pvr2_v4l_type_radio));
+}
+
+
+static ssize_t unit_number_show(struct device *class_dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%d\n",
+			 pvr2_hdw_get_unit_number(sfp->channel.hdw));
+}
+
+
+static void class_dev_create(struct pvr2_sysfs *sfp,
+			     struct pvr2_sysfs_class *class_ptr)
+{
+	struct usb_device *usb_dev;
+	struct device *class_dev;
+	int ret;
+
+	usb_dev = pvr2_hdw_get_dev(sfp->channel.hdw);
+	if (!usb_dev) return;
+	class_dev = kzalloc(sizeof(*class_dev),GFP_KERNEL);
+	if (!class_dev) return;
+
+	pvr2_sysfs_trace("Creating class_dev id=%p",class_dev);
+
+	class_dev->class = &class_ptr->class;
+
+	dev_set_name(class_dev, "%s",
+		     pvr2_hdw_get_device_identifier(sfp->channel.hdw));
+
+	class_dev->parent = get_device(&usb_dev->dev);
+
+	sfp->class_dev = class_dev;
+	dev_set_drvdata(class_dev, sfp);
+	ret = device_register(class_dev);
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_register failed");
+		put_device(class_dev);
+		return;
+	}
+
+	sysfs_attr_init(&sfp->attr_v4l_minor_number.attr);
+	sfp->attr_v4l_minor_number.attr.name = "v4l_minor_number";
+	sfp->attr_v4l_minor_number.attr.mode = S_IRUGO;
+	sfp->attr_v4l_minor_number.show = v4l_minor_number_show;
+	sfp->attr_v4l_minor_number.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				       &sfp->attr_v4l_minor_number);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->v4l_minor_number_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_v4l_radio_minor_number.attr);
+	sfp->attr_v4l_radio_minor_number.attr.name = "v4l_radio_minor_number";
+	sfp->attr_v4l_radio_minor_number.attr.mode = S_IRUGO;
+	sfp->attr_v4l_radio_minor_number.show = v4l_radio_minor_number_show;
+	sfp->attr_v4l_radio_minor_number.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				       &sfp->attr_v4l_radio_minor_number);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->v4l_radio_minor_number_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_unit_number.attr);
+	sfp->attr_unit_number.attr.name = "unit_number";
+	sfp->attr_unit_number.attr.mode = S_IRUGO;
+	sfp->attr_unit_number.show = unit_number_show;
+	sfp->attr_unit_number.store = NULL;
+	ret = device_create_file(sfp->class_dev,&sfp->attr_unit_number);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->unit_number_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_bus_info.attr);
+	sfp->attr_bus_info.attr.name = "bus_info_str";
+	sfp->attr_bus_info.attr.mode = S_IRUGO;
+	sfp->attr_bus_info.show = bus_info_show;
+	sfp->attr_bus_info.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				       &sfp->attr_bus_info);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->bus_info_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_hdw_name.attr);
+	sfp->attr_hdw_name.attr.name = "device_hardware_type";
+	sfp->attr_hdw_name.attr.mode = S_IRUGO;
+	sfp->attr_hdw_name.show = hdw_name_show;
+	sfp->attr_hdw_name.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				 &sfp->attr_hdw_name);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->hdw_name_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_hdw_desc.attr);
+	sfp->attr_hdw_desc.attr.name = "device_hardware_description";
+	sfp->attr_hdw_desc.attr.mode = S_IRUGO;
+	sfp->attr_hdw_desc.show = hdw_desc_show;
+	sfp->attr_hdw_desc.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				 &sfp->attr_hdw_desc);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->hdw_desc_created_ok = !0;
+	}
+
+	pvr2_sysfs_add_controls(sfp);
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+	pvr2_sysfs_add_debugifc(sfp);
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+}
+
+
+static void pvr2_sysfs_internal_check(struct pvr2_channel *chp)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = container_of(chp,struct pvr2_sysfs,channel);
+	if (!sfp->channel.mc_head->disconnect_flag) return;
+	pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_sysfs id=%p",sfp);
+	class_dev_destroy(sfp);
+	pvr2_channel_done(&sfp->channel);
+	kfree(sfp);
+}
+
+
+struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *mp,
+				     struct pvr2_sysfs_class *class_ptr)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = kzalloc(sizeof(*sfp),GFP_KERNEL);
+	if (!sfp) return sfp;
+	pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_sysfs id=%p",sfp);
+	pvr2_channel_init(&sfp->channel,mp);
+	sfp->channel.check_func = pvr2_sysfs_internal_check;
+
+	class_dev_create(sfp,class_ptr);
+	return sfp;
+}
+
+
+
+struct pvr2_sysfs_class *pvr2_sysfs_class_create(void)
+{
+	struct pvr2_sysfs_class *clp;
+	clp = kzalloc(sizeof(*clp),GFP_KERNEL);
+	if (!clp) return clp;
+	pvr2_sysfs_trace("Creating and registering pvr2_sysfs_class id=%p",
+			 clp);
+	clp->class.name = "pvrusb2";
+	clp->class.class_release = pvr2_sysfs_class_release;
+	clp->class.dev_release = pvr2_sysfs_release;
+	if (class_register(&clp->class)) {
+		pvr2_sysfs_trace(
+			"Registration failed for pvr2_sysfs_class id=%p",clp);
+		kfree(clp);
+		clp = NULL;
+	}
+	return clp;
+}
+
+
+void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *clp)
+{
+	pvr2_sysfs_trace("Unregistering pvr2_sysfs_class id=%p", clp);
+	class_unregister(&clp->class);
+}
+
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+static ssize_t debuginfo_show(struct device *class_dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	pvr2_hdw_trigger_module_log(sfp->channel.hdw);
+	return pvr2_debugifc_print_info(sfp->channel.hdw,buf,PAGE_SIZE);
+}
+
+
+static ssize_t debugcmd_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return pvr2_debugifc_print_status(sfp->channel.hdw,buf,PAGE_SIZE);
+}
+
+
+static ssize_t debugcmd_store(struct device *class_dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct pvr2_sysfs *sfp;
+	int ret;
+
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+
+	ret = pvr2_debugifc_docmd(sfp->channel.hdw,buf,count);
+	if (ret < 0) return ret;
+	return count;
+}
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h
new file mode 100644
index 0000000..431f4fd
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h
@@ -0,0 +1,32 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_SYSFS_H
+#define __PVRUSB2_SYSFS_H
+
+#include <linux/list.h>
+#include <linux/sysfs.h>
+#include "pvrusb2-context.h"
+
+struct pvr2_sysfs;
+struct pvr2_sysfs_class;
+
+struct pvr2_sysfs_class *pvr2_sysfs_class_create(void);
+void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *);
+
+struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *,
+				     struct pvr2_sysfs_class *);
+
+#endif /* __PVRUSB2_SYSFS_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-util.h b/drivers/media/usb/pvrusb2/pvrusb2-util.h
new file mode 100644
index 0000000..b03ca3e
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-util.h
@@ -0,0 +1,48 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_UTIL_H
+#define __PVRUSB2_UTIL_H
+
+#define PVR2_DECOMPOSE_LE(t,i,d) \
+    do {    \
+	(t)[i] = (d) & 0xff;\
+	(t)[i+1] = ((d) >> 8) & 0xff;\
+	(t)[i+2] = ((d) >> 16) & 0xff;\
+	(t)[i+3] = ((d) >> 24) & 0xff;\
+    } while(0)
+
+#define PVR2_DECOMPOSE_BE(t,i,d) \
+    do {    \
+	(t)[i+3] = (d) & 0xff;\
+	(t)[i+2] = ((d) >> 8) & 0xff;\
+	(t)[i+1] = ((d) >> 16) & 0xff;\
+	(t)[i] = ((d) >> 24) & 0xff;\
+    } while(0)
+
+#define PVR2_COMPOSE_LE(t,i) \
+    ((((u32)((t)[i+3])) << 24) | \
+     (((u32)((t)[i+2])) << 16) | \
+     (((u32)((t)[i+1])) << 8) | \
+     ((u32)((t)[i])))
+
+#define PVR2_COMPOSE_BE(t,i) \
+    ((((u32)((t)[i])) << 24) | \
+     (((u32)((t)[i+1])) << 16) | \
+     (((u32)((t)[i+2])) << 8) | \
+     ((u32)((t)[i+3])))
+
+
+#endif /* __PVRUSB2_UTIL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c
new file mode 100644
index 0000000..e53a80b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c
@@ -0,0 +1,1299 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "pvrusb2-context.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-v4l2.h"
+#include "pvrusb2-ioread.h"
+#include <linux/videodev2.h>
+#include <linux/module.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+struct pvr2_v4l2_dev;
+struct pvr2_v4l2_fh;
+struct pvr2_v4l2;
+
+struct pvr2_v4l2_dev {
+	struct video_device devbase; /* MUST be first! */
+	struct pvr2_v4l2 *v4lp;
+	struct pvr2_context_stream *stream;
+	/* Information about this device: */
+	enum pvr2_config config; /* Expected stream format */
+	int v4l_type; /* V4L defined type for this device node */
+	enum pvr2_v4l_type minor_type; /* pvr2-understood minor device type */
+};
+
+struct pvr2_v4l2_fh {
+	struct v4l2_fh fh;
+	struct pvr2_channel channel;
+	struct pvr2_v4l2_dev *pdi;
+	struct pvr2_ioread *rhp;
+	struct file *file;
+	wait_queue_head_t wait_data;
+	int fw_mode_flag;
+	/* Map contiguous ordinal value to input id */
+	unsigned char *input_map;
+	unsigned int input_cnt;
+};
+
+struct pvr2_v4l2 {
+	struct pvr2_channel channel;
+
+	/* streams - Note that these must be separately, individually,
+	 * allocated pointers.  This is because the v4l core is going to
+	 * manage their deletion - separately, individually...  */
+	struct pvr2_v4l2_dev *dev_video;
+	struct pvr2_v4l2_dev *dev_radio;
+};
+
+static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(video_nr, int, NULL, 0444);
+MODULE_PARM_DESC(video_nr, "Offset for device's video dev minor");
+static int radio_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(radio_nr, int, NULL, 0444);
+MODULE_PARM_DESC(radio_nr, "Offset for device's radio dev minor");
+static int vbi_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(vbi_nr, int, NULL, 0444);
+MODULE_PARM_DESC(vbi_nr, "Offset for device's vbi dev minor");
+
+#define PVR_FORMAT_PIX  0
+#define PVR_FORMAT_VBI  1
+
+static struct v4l2_format pvr_format [] = {
+	[PVR_FORMAT_PIX] = {
+		.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+		.fmt    = {
+			.pix        = {
+				.width          = 720,
+				.height         = 576,
+				.pixelformat    = V4L2_PIX_FMT_MPEG,
+				.field          = V4L2_FIELD_INTERLACED,
+				/* FIXME : Don't know what to put here... */
+				.sizeimage      = 32 * 1024,
+			}
+		}
+	},
+	[PVR_FORMAT_VBI] = {
+		.type   = V4L2_BUF_TYPE_VBI_CAPTURE,
+		.fmt    = {
+			.vbi        = {
+				.sampling_rate = 27000000,
+				.offset = 248,
+				.samples_per_line = 1443,
+				.sample_format = V4L2_PIX_FMT_GREY,
+				.start = { 0, 0 },
+				.count = { 0, 0 },
+				.flags = 0,
+			}
+		}
+	}
+};
+
+
+
+/*
+ * This is part of Video 4 Linux API. These procedures handle ioctl() calls.
+ */
+static int pvr2_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	strlcpy(cap->driver, "pvrusb2", sizeof(cap->driver));
+	strlcpy(cap->bus_info, pvr2_hdw_get_bus_info(hdw),
+			sizeof(cap->bus_info));
+	strlcpy(cap->card, pvr2_hdw_get_desc(hdw), sizeof(cap->card));
+	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
+			    V4L2_CAP_AUDIO | V4L2_CAP_RADIO |
+			    V4L2_CAP_READWRITE | V4L2_CAP_DEVICE_CAPS;
+	switch (fh->pdi->devbase.vfl_type) {
+	case VFL_TYPE_GRABBER:
+		cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_AUDIO;
+		break;
+	case VFL_TYPE_RADIO:
+		cap->device_caps = V4L2_CAP_RADIO;
+		break;
+	default:
+		return -EINVAL;
+	}
+	cap->device_caps |= V4L2_CAP_TUNER | V4L2_CAP_READWRITE;
+	return 0;
+}
+
+static int pvr2_g_std(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDCUR), &val);
+	*std = val;
+	return ret;
+}
+
+static int pvr2_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	ret = pvr2_ctrl_set_value(
+		pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDCUR), std);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_querystd(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_value(
+		pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDDETECT), &val);
+	*std = val;
+	return ret;
+}
+
+static int pvr2_enum_input(struct file *file, void *priv, struct v4l2_input *vi)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_ctrl *cptr;
+	struct v4l2_input tmp;
+	unsigned int cnt;
+	int val;
+
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT);
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.index = vi->index;
+	if (vi->index >= fh->input_cnt)
+		return -EINVAL;
+	val = fh->input_map[vi->index];
+	switch (val) {
+	case PVR2_CVAL_INPUT_TV:
+	case PVR2_CVAL_INPUT_DTV:
+	case PVR2_CVAL_INPUT_RADIO:
+		tmp.type = V4L2_INPUT_TYPE_TUNER;
+		break;
+	case PVR2_CVAL_INPUT_SVIDEO:
+	case PVR2_CVAL_INPUT_COMPOSITE:
+		tmp.type = V4L2_INPUT_TYPE_CAMERA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	cnt = 0;
+	pvr2_ctrl_get_valname(cptr, val,
+			tmp.name, sizeof(tmp.name) - 1, &cnt);
+	tmp.name[cnt] = 0;
+
+	/* Don't bother with audioset, since this driver currently
+	   always switches the audio whenever the video is
+	   switched. */
+
+	/* Handling std is a tougher problem.  It doesn't make
+	   sense in cases where a device might be multi-standard.
+	   We could just copy out the current value for the
+	   standard, but it can change over time.  For now just
+	   leave it zero. */
+	*vi = tmp;
+	return 0;
+}
+
+static int pvr2_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int val;
+	int ret;
+
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT);
+	val = 0;
+	ret = pvr2_ctrl_get_value(cptr, &val);
+	*i = 0;
+	for (idx = 0; idx < fh->input_cnt; idx++) {
+		if (fh->input_map[idx] == val) {
+			*i = idx;
+			break;
+		}
+	}
+	return ret;
+}
+
+static int pvr2_s_input(struct file *file, void *priv, unsigned int inp)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (inp >= fh->input_cnt)
+		return -EINVAL;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT),
+			fh->input_map[inp]);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_enumaudio(struct file *file, void *priv, struct v4l2_audio *vin)
+{
+	/* pkt: FIXME: We are returning one "fake" input here
+	   which could very well be called "whatever_we_like".
+	   This is for apps that want to see an audio input
+	   just to feel comfortable, as well as to test if
+	   it can do stereo or sth. There is actually no guarantee
+	   that the actual audio input cannot change behind the app's
+	   back, but most applications should not mind that either.
+
+	   Hopefully, mplayer people will work with us on this (this
+	   whole mess is to support mplayer pvr://), or Hans will come
+	   up with a more standard way to say "we have inputs but we
+	   don 't want you to change them independent of video" which
+	   will sort this mess.
+	 */
+
+	if (vin->index > 0)
+		return -EINVAL;
+	strncpy(vin->name, "PVRUSB2 Audio", 14);
+	vin->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int pvr2_g_audio(struct file *file, void *priv, struct v4l2_audio *vin)
+{
+	/* pkt: FIXME: see above comment (VIDIOC_ENUMAUDIO) */
+	vin->index = 0;
+	strncpy(vin->name, "PVRUSB2 Audio", 14);
+	vin->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int pvr2_s_audio(struct file *file, void *priv, const struct v4l2_audio *vout)
+{
+	if (vout->index)
+		return -EINVAL;
+	return 0;
+}
+
+static int pvr2_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	if (vt->index != 0)
+		return -EINVAL; /* Only answer for the 1st tuner */
+
+	pvr2_hdw_execute_tuner_poll(hdw);
+	return pvr2_hdw_get_tuner_status(hdw, vt);
+}
+
+static int pvr2_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (vt->index != 0)
+		return -EINVAL;
+
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_AUDIOMODE),
+			vt->audmode);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	unsigned long fv;
+	struct v4l2_tuner vt;
+	int cur_input;
+	struct pvr2_ctrl *ctrlp;
+	int ret;
+
+	ret = pvr2_hdw_get_tuner_status(hdw, &vt);
+	if (ret != 0)
+		return ret;
+	ctrlp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT);
+	ret = pvr2_ctrl_get_value(ctrlp, &cur_input);
+	if (ret != 0)
+		return ret;
+	if (vf->type == V4L2_TUNER_RADIO) {
+		if (cur_input != PVR2_CVAL_INPUT_RADIO)
+			pvr2_ctrl_set_value(ctrlp, PVR2_CVAL_INPUT_RADIO);
+	} else {
+		if (cur_input == PVR2_CVAL_INPUT_RADIO)
+			pvr2_ctrl_set_value(ctrlp, PVR2_CVAL_INPUT_TV);
+	}
+	fv = vf->frequency;
+	if (vt.capability & V4L2_TUNER_CAP_LOW)
+		fv = (fv * 125) / 2;
+	else
+		fv = fv * 62500;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY),fv);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_g_frequency(struct file *file, void *priv, struct v4l2_frequency *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int cur_input;
+	struct v4l2_tuner vt;
+	int ret;
+
+	ret = pvr2_hdw_get_tuner_status(hdw, &vt);
+	if (ret != 0)
+		return ret;
+	ret = pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_FREQUENCY),
+			&val);
+	if (ret != 0)
+		return ret;
+	pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT),
+			&cur_input);
+	if (cur_input == PVR2_CVAL_INPUT_RADIO)
+		vf->type = V4L2_TUNER_RADIO;
+	else
+		vf->type = V4L2_TUNER_ANALOG_TV;
+	if (vt.capability & V4L2_TUNER_CAP_LOW)
+		val = (val * 2) / 125;
+	else
+		val /= 62500;
+	vf->frequency = val;
+	return 0;
+}
+
+static int pvr2_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *fd)
+{
+	/* Only one format is supported: MPEG. */
+	if (fd->index)
+		return -EINVAL;
+
+	fd->pixelformat = V4L2_PIX_FMT_MPEG;
+	return 0;
+}
+
+static int pvr2_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val;
+
+	memcpy(vf, &pvr_format[PVR_FORMAT_PIX], sizeof(struct v4l2_format));
+	val = 0;
+	pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES),
+			&val);
+	vf->fmt.pix.width = val;
+	val = 0;
+	pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES),
+			&val);
+	vf->fmt.pix.height = val;
+	return 0;
+}
+
+static int pvr2_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int lmin, lmax, ldef;
+	struct pvr2_ctrl *hcp, *vcp;
+	int h = vf->fmt.pix.height;
+	int w = vf->fmt.pix.width;
+
+	hcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES);
+	vcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES);
+
+	lmin = pvr2_ctrl_get_min(hcp);
+	lmax = pvr2_ctrl_get_max(hcp);
+	pvr2_ctrl_get_def(hcp, &ldef);
+	if (w == -1)
+		w = ldef;
+	else if (w < lmin)
+		w = lmin;
+	else if (w > lmax)
+		w = lmax;
+	lmin = pvr2_ctrl_get_min(vcp);
+	lmax = pvr2_ctrl_get_max(vcp);
+	pvr2_ctrl_get_def(vcp, &ldef);
+	if (h == -1)
+		h = ldef;
+	else if (h < lmin)
+		h = lmin;
+	else if (h > lmax)
+		h = lmax;
+
+	memcpy(vf, &pvr_format[PVR_FORMAT_PIX],
+			sizeof(struct v4l2_format));
+	vf->fmt.pix.width = w;
+	vf->fmt.pix.height = h;
+	return 0;
+}
+
+static int pvr2_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_ctrl *hcp, *vcp;
+	int ret = pvr2_try_fmt_vid_cap(file, fh, vf);
+
+	if (ret)
+		return ret;
+	hcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES);
+	vcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES);
+	pvr2_ctrl_set_value(hcp, vf->fmt.pix.width);
+	pvr2_ctrl_set_value(vcp, vf->fmt.pix.height);
+	pvr2_hdw_commit_ctl(hdw);
+	return 0;
+}
+
+static int pvr2_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_v4l2_dev *pdi = fh->pdi;
+	int ret;
+
+	if (!fh->pdi->stream) {
+		/* No stream defined for this node.  This means
+		   that we're not currently allowed to stream from
+		   this node. */
+		return -EPERM;
+	}
+	ret = pvr2_hdw_set_stream_type(hdw, pdi->config);
+	if (ret < 0)
+		return ret;
+	return pvr2_hdw_set_streaming(hdw, !0);
+}
+
+static int pvr2_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	if (!fh->pdi->stream) {
+		/* No stream defined for this node.  This means
+		   that we're not currently allowed to stream from
+		   this node. */
+		return -EPERM;
+	}
+	return pvr2_hdw_set_streaming(hdw, 0);
+}
+
+static int pvr2_queryctrl(struct file *file, void *priv,
+		struct v4l2_queryctrl *vc)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_ctrl *cptr;
+	int val;
+
+	if (vc->id & V4L2_CTRL_FLAG_NEXT_CTRL) {
+		cptr = pvr2_hdw_get_ctrl_nextv4l(
+				hdw, (vc->id & ~V4L2_CTRL_FLAG_NEXT_CTRL));
+		if (cptr)
+			vc->id = pvr2_ctrl_get_v4lid(cptr);
+	} else {
+		cptr = pvr2_hdw_get_ctrl_v4l(hdw, vc->id);
+	}
+	if (!cptr) {
+		pvr2_trace(PVR2_TRACE_V4LIOCTL,
+				"QUERYCTRL id=0x%x not implemented here",
+				vc->id);
+		return -EINVAL;
+	}
+
+	pvr2_trace(PVR2_TRACE_V4LIOCTL,
+			"QUERYCTRL id=0x%x mapping name=%s (%s)",
+			vc->id, pvr2_ctrl_get_name(cptr),
+			pvr2_ctrl_get_desc(cptr));
+	strlcpy(vc->name, pvr2_ctrl_get_desc(cptr), sizeof(vc->name));
+	vc->flags = pvr2_ctrl_get_v4lflags(cptr);
+	pvr2_ctrl_get_def(cptr, &val);
+	vc->default_value = val;
+	switch (pvr2_ctrl_get_type(cptr)) {
+	case pvr2_ctl_enum:
+		vc->type = V4L2_CTRL_TYPE_MENU;
+		vc->minimum = 0;
+		vc->maximum = pvr2_ctrl_get_cnt(cptr) - 1;
+		vc->step = 1;
+		break;
+	case pvr2_ctl_bool:
+		vc->type = V4L2_CTRL_TYPE_BOOLEAN;
+		vc->minimum = 0;
+		vc->maximum = 1;
+		vc->step = 1;
+		break;
+	case pvr2_ctl_int:
+		vc->type = V4L2_CTRL_TYPE_INTEGER;
+		vc->minimum = pvr2_ctrl_get_min(cptr);
+		vc->maximum = pvr2_ctrl_get_max(cptr);
+		vc->step = 1;
+		break;
+	default:
+		pvr2_trace(PVR2_TRACE_V4LIOCTL,
+				"QUERYCTRL id=0x%x name=%s not mappable",
+				vc->id, pvr2_ctrl_get_name(cptr));
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int pvr2_querymenu(struct file *file, void *priv, struct v4l2_querymenu *vm)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	unsigned int cnt = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_valname(pvr2_hdw_get_ctrl_v4l(hdw, vm->id),
+			vm->index,
+			vm->name, sizeof(vm->name) - 1,
+			&cnt);
+	vm->name[cnt] = 0;
+	return ret;
+}
+
+static int pvr2_g_ctrl(struct file *file, void *priv, struct v4l2_control *vc)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_value(pvr2_hdw_get_ctrl_v4l(hdw, vc->id),
+			&val);
+	vc->value = val;
+	return ret;
+}
+
+static int pvr2_s_ctrl(struct file *file, void *priv, struct v4l2_control *vc)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	ret = pvr2_ctrl_set_value(pvr2_hdw_get_ctrl_v4l(hdw, vc->id),
+			vc->value);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_g_ext_ctrls(struct file *file, void *priv,
+					struct v4l2_ext_controls *ctls)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_ext_control *ctrl;
+	struct pvr2_ctrl *cptr;
+	unsigned int idx;
+	int val;
+	int ret;
+
+	ret = 0;
+	for (idx = 0; idx < ctls->count; idx++) {
+		ctrl = ctls->controls + idx;
+		cptr = pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id);
+		if (cptr) {
+			if (ctls->which == V4L2_CTRL_WHICH_DEF_VAL)
+				pvr2_ctrl_get_def(cptr, &val);
+			else
+				ret = pvr2_ctrl_get_value(cptr, &val);
+		} else
+			ret = -EINVAL;
+
+		if (ret) {
+			ctls->error_idx = idx;
+			return ret;
+		}
+		/* Ensure that if read as a 64 bit value, the user
+		   will still get a hopefully sane value */
+		ctrl->value64 = 0;
+		ctrl->value = val;
+	}
+	return 0;
+}
+
+static int pvr2_s_ext_ctrls(struct file *file, void *priv,
+		struct v4l2_ext_controls *ctls)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_ext_control *ctrl;
+	unsigned int idx;
+	int ret;
+
+	/* Default value cannot be changed */
+	if (ctls->which == V4L2_CTRL_WHICH_DEF_VAL)
+		return -EINVAL;
+
+	ret = 0;
+	for (idx = 0; idx < ctls->count; idx++) {
+		ctrl = ctls->controls + idx;
+		ret = pvr2_ctrl_set_value(
+				pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id),
+				ctrl->value);
+		if (ret) {
+			ctls->error_idx = idx;
+			goto commit;
+		}
+	}
+commit:
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_try_ext_ctrls(struct file *file, void *priv,
+		struct v4l2_ext_controls *ctls)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_ext_control *ctrl;
+	struct pvr2_ctrl *pctl;
+	unsigned int idx;
+
+	/* For the moment just validate that the requested control
+	   actually exists. */
+	for (idx = 0; idx < ctls->count; idx++) {
+		ctrl = ctls->controls + idx;
+		pctl = pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id);
+		if (!pctl) {
+			ctls->error_idx = idx;
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int pvr2_cropcap(struct file *file, void *priv, struct v4l2_cropcap *cap)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	ret = pvr2_hdw_get_cropcap(hdw, cap);
+	cap->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* paranoia */
+	return ret;
+}
+
+static int pvr2_g_selection(struct file *file, void *priv,
+			    struct v4l2_selection *sel)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_cropcap cap;
+	int val = 0;
+	int ret;
+
+	if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP:
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPL), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.left = val;
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPT), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.top = val;
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPW), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.width = val;
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPH), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.height = val;
+		break;
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+		ret = pvr2_hdw_get_cropcap(hdw, &cap);
+		sel->r = cap.defrect;
+		break;
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		ret = pvr2_hdw_get_cropcap(hdw, &cap);
+		sel->r = cap.bounds;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return ret;
+}
+
+static int pvr2_s_selection(struct file *file, void *priv,
+			    struct v4l2_selection *sel)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+	    sel->target != V4L2_SEL_TGT_CROP)
+		return -EINVAL;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPL),
+			sel->r.left);
+	if (ret != 0)
+		goto commit;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPT),
+			sel->r.top);
+	if (ret != 0)
+		goto commit;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPW),
+			sel->r.width);
+	if (ret != 0)
+		goto commit;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPH),
+			sel->r.height);
+commit:
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_log_status(struct file *file, void *priv)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	pvr2_hdw_trigger_module_log(hdw);
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops pvr2_ioctl_ops = {
+	.vidioc_querycap		    = pvr2_querycap,
+	.vidioc_s_audio			    = pvr2_s_audio,
+	.vidioc_g_audio			    = pvr2_g_audio,
+	.vidioc_enumaudio		    = pvr2_enumaudio,
+	.vidioc_enum_input		    = pvr2_enum_input,
+	.vidioc_cropcap			    = pvr2_cropcap,
+	.vidioc_s_selection		    = pvr2_s_selection,
+	.vidioc_g_selection		    = pvr2_g_selection,
+	.vidioc_g_input			    = pvr2_g_input,
+	.vidioc_s_input			    = pvr2_s_input,
+	.vidioc_g_frequency		    = pvr2_g_frequency,
+	.vidioc_s_frequency		    = pvr2_s_frequency,
+	.vidioc_s_tuner			    = pvr2_s_tuner,
+	.vidioc_g_tuner			    = pvr2_g_tuner,
+	.vidioc_g_std			    = pvr2_g_std,
+	.vidioc_s_std			    = pvr2_s_std,
+	.vidioc_querystd		    = pvr2_querystd,
+	.vidioc_log_status		    = pvr2_log_status,
+	.vidioc_enum_fmt_vid_cap	    = pvr2_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap		    = pvr2_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		    = pvr2_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		    = pvr2_try_fmt_vid_cap,
+	.vidioc_streamon		    = pvr2_streamon,
+	.vidioc_streamoff		    = pvr2_streamoff,
+	.vidioc_queryctrl		    = pvr2_queryctrl,
+	.vidioc_querymenu		    = pvr2_querymenu,
+	.vidioc_g_ctrl			    = pvr2_g_ctrl,
+	.vidioc_s_ctrl			    = pvr2_s_ctrl,
+	.vidioc_g_ext_ctrls		    = pvr2_g_ext_ctrls,
+	.vidioc_s_ext_ctrls		    = pvr2_s_ext_ctrls,
+	.vidioc_try_ext_ctrls		    = pvr2_try_ext_ctrls,
+};
+
+static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip)
+{
+	struct pvr2_hdw *hdw = dip->v4lp->channel.mc_head->hdw;
+	enum pvr2_config cfg = dip->config;
+	char msg[80];
+	unsigned int mcnt;
+
+	/* Construct the unregistration message *before* we actually
+	   perform the unregistration step.  By doing it this way we don't
+	   have to worry about potentially touching deleted resources. */
+	mcnt = scnprintf(msg, sizeof(msg) - 1,
+			 "pvrusb2: unregistered device %s [%s]",
+			 video_device_node_name(&dip->devbase),
+			 pvr2_config_get_name(cfg));
+	msg[mcnt] = 0;
+
+	pvr2_hdw_v4l_store_minor_number(hdw,dip->minor_type,-1);
+
+	/* Paranoia */
+	dip->v4lp = NULL;
+	dip->stream = NULL;
+
+	/* Actual deallocation happens later when all internal references
+	   are gone. */
+	video_unregister_device(&dip->devbase);
+
+	printk(KERN_INFO "%s\n", msg);
+
+}
+
+
+static void pvr2_v4l2_dev_disassociate_parent(struct pvr2_v4l2_dev *dip)
+{
+	if (!dip) return;
+	if (!dip->devbase.v4l2_dev->dev) return;
+	dip->devbase.v4l2_dev->dev = NULL;
+	device_move(&dip->devbase.dev, NULL, DPM_ORDER_NONE);
+}
+
+
+static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp)
+{
+	if (vp->dev_video) {
+		pvr2_v4l2_dev_destroy(vp->dev_video);
+		vp->dev_video = NULL;
+	}
+	if (vp->dev_radio) {
+		pvr2_v4l2_dev_destroy(vp->dev_radio);
+		vp->dev_radio = NULL;
+	}
+
+	pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp);
+	pvr2_channel_done(&vp->channel);
+	kfree(vp);
+}
+
+
+static void pvr2_video_device_release(struct video_device *vdev)
+{
+	struct pvr2_v4l2_dev *dev;
+	dev = container_of(vdev,struct pvr2_v4l2_dev,devbase);
+	kfree(dev);
+}
+
+
+static void pvr2_v4l2_internal_check(struct pvr2_channel *chp)
+{
+	struct pvr2_v4l2 *vp;
+	vp = container_of(chp,struct pvr2_v4l2,channel);
+	if (!vp->channel.mc_head->disconnect_flag) return;
+	pvr2_v4l2_dev_disassociate_parent(vp->dev_video);
+	pvr2_v4l2_dev_disassociate_parent(vp->dev_radio);
+	if (!list_empty(&vp->dev_video->devbase.fh_list) ||
+	    !list_empty(&vp->dev_radio->devbase.fh_list))
+		return;
+	pvr2_v4l2_destroy_no_lock(vp);
+}
+
+
+static int pvr2_v4l2_release(struct file *file)
+{
+	struct pvr2_v4l2_fh *fhp = file->private_data;
+	struct pvr2_v4l2 *vp = fhp->pdi->v4lp;
+	struct pvr2_hdw *hdw = fhp->channel.mc_head->hdw;
+
+	pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_release");
+
+	if (fhp->rhp) {
+		struct pvr2_stream *sp;
+		pvr2_hdw_set_streaming(hdw,0);
+		sp = pvr2_ioread_get_stream(fhp->rhp);
+		if (sp) pvr2_stream_set_callback(sp,NULL,NULL);
+		pvr2_ioread_destroy(fhp->rhp);
+		fhp->rhp = NULL;
+	}
+
+	v4l2_fh_del(&fhp->fh);
+	v4l2_fh_exit(&fhp->fh);
+	file->private_data = NULL;
+
+	pvr2_channel_done(&fhp->channel);
+	pvr2_trace(PVR2_TRACE_STRUCT,
+		   "Destroying pvr_v4l2_fh id=%p",fhp);
+	if (fhp->input_map) {
+		kfree(fhp->input_map);
+		fhp->input_map = NULL;
+	}
+	kfree(fhp);
+	if (vp->channel.mc_head->disconnect_flag &&
+	    list_empty(&vp->dev_video->devbase.fh_list) &&
+	    list_empty(&vp->dev_radio->devbase.fh_list)) {
+		pvr2_v4l2_destroy_no_lock(vp);
+	}
+	return 0;
+}
+
+
+static int pvr2_v4l2_open(struct file *file)
+{
+	struct pvr2_v4l2_dev *dip; /* Our own context pointer */
+	struct pvr2_v4l2_fh *fhp;
+	struct pvr2_v4l2 *vp;
+	struct pvr2_hdw *hdw;
+	unsigned int input_mask = 0;
+	unsigned int input_cnt,idx;
+	int ret = 0;
+
+	dip = container_of(video_devdata(file),struct pvr2_v4l2_dev,devbase);
+
+	vp = dip->v4lp;
+	hdw = vp->channel.hdw;
+
+	pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_open");
+
+	if (!pvr2_hdw_dev_ok(hdw)) {
+		pvr2_trace(PVR2_TRACE_OPEN_CLOSE,
+			   "pvr2_v4l2_open: hardware not ready");
+		return -EIO;
+	}
+
+	fhp = kzalloc(sizeof(*fhp),GFP_KERNEL);
+	if (!fhp) {
+		return -ENOMEM;
+	}
+
+	v4l2_fh_init(&fhp->fh, &dip->devbase);
+	init_waitqueue_head(&fhp->wait_data);
+	fhp->pdi = dip;
+
+	pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_v4l2_fh id=%p",fhp);
+	pvr2_channel_init(&fhp->channel,vp->channel.mc_head);
+
+	if (dip->v4l_type == VFL_TYPE_RADIO) {
+		/* Opening device as a radio, legal input selection subset
+		   is just the radio. */
+		input_mask = (1 << PVR2_CVAL_INPUT_RADIO);
+	} else {
+		/* Opening the main V4L device, legal input selection
+		   subset includes all analog inputs. */
+		input_mask = ((1 << PVR2_CVAL_INPUT_RADIO) |
+			      (1 << PVR2_CVAL_INPUT_TV) |
+			      (1 << PVR2_CVAL_INPUT_COMPOSITE) |
+			      (1 << PVR2_CVAL_INPUT_SVIDEO));
+	}
+	ret = pvr2_channel_limit_inputs(&fhp->channel,input_mask);
+	if (ret) {
+		pvr2_channel_done(&fhp->channel);
+		pvr2_trace(PVR2_TRACE_STRUCT,
+			   "Destroying pvr_v4l2_fh id=%p (input mask error)",
+			   fhp);
+		v4l2_fh_exit(&fhp->fh);
+		kfree(fhp);
+		return ret;
+	}
+
+	input_mask &= pvr2_hdw_get_input_available(hdw);
+	input_cnt = 0;
+	for (idx = 0; idx < (sizeof(input_mask) << 3); idx++) {
+		if (input_mask & (1 << idx)) input_cnt++;
+	}
+	fhp->input_cnt = input_cnt;
+	fhp->input_map = kzalloc(input_cnt,GFP_KERNEL);
+	if (!fhp->input_map) {
+		pvr2_channel_done(&fhp->channel);
+		pvr2_trace(PVR2_TRACE_STRUCT,
+			   "Destroying pvr_v4l2_fh id=%p (input map failure)",
+			   fhp);
+		v4l2_fh_exit(&fhp->fh);
+		kfree(fhp);
+		return -ENOMEM;
+	}
+	input_cnt = 0;
+	for (idx = 0; idx < (sizeof(input_mask) << 3); idx++) {
+		if (!(input_mask & (1 << idx))) continue;
+		fhp->input_map[input_cnt++] = idx;
+	}
+
+	fhp->file = file;
+	file->private_data = fhp;
+
+	fhp->fw_mode_flag = pvr2_hdw_cpufw_get_enabled(hdw);
+	v4l2_fh_add(&fhp->fh);
+
+	return 0;
+}
+
+
+static void pvr2_v4l2_notify(struct pvr2_v4l2_fh *fhp)
+{
+	wake_up(&fhp->wait_data);
+}
+
+static int pvr2_v4l2_iosetup(struct pvr2_v4l2_fh *fh)
+{
+	int ret;
+	struct pvr2_stream *sp;
+	struct pvr2_hdw *hdw;
+	if (fh->rhp) return 0;
+
+	if (!fh->pdi->stream) {
+		/* No stream defined for this node.  This means that we're
+		   not currently allowed to stream from this node. */
+		return -EPERM;
+	}
+
+	/* First read() attempt.  Try to claim the stream and start
+	   it... */
+	if ((ret = pvr2_channel_claim_stream(&fh->channel,
+					     fh->pdi->stream)) != 0) {
+		/* Someone else must already have it */
+		return ret;
+	}
+
+	fh->rhp = pvr2_channel_create_mpeg_stream(fh->pdi->stream);
+	if (!fh->rhp) {
+		pvr2_channel_claim_stream(&fh->channel,NULL);
+		return -ENOMEM;
+	}
+
+	hdw = fh->channel.mc_head->hdw;
+	sp = fh->pdi->stream->stream;
+	pvr2_stream_set_callback(sp,(pvr2_stream_callback)pvr2_v4l2_notify,fh);
+	pvr2_hdw_set_stream_type(hdw,fh->pdi->config);
+	if ((ret = pvr2_hdw_set_streaming(hdw,!0)) < 0) return ret;
+	return pvr2_ioread_set_enabled(fh->rhp,!0);
+}
+
+
+static ssize_t pvr2_v4l2_read(struct file *file,
+			      char __user *buff, size_t count, loff_t *ppos)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	int ret;
+
+	if (fh->fw_mode_flag) {
+		struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+		char *tbuf;
+		int c1,c2;
+		int tcnt = 0;
+		unsigned int offs = *ppos;
+
+		tbuf = kmalloc(PAGE_SIZE,GFP_KERNEL);
+		if (!tbuf) return -ENOMEM;
+
+		while (count) {
+			c1 = count;
+			if (c1 > PAGE_SIZE) c1 = PAGE_SIZE;
+			c2 = pvr2_hdw_cpufw_get(hdw,offs,tbuf,c1);
+			if (c2 < 0) {
+				tcnt = c2;
+				break;
+			}
+			if (!c2) break;
+			if (copy_to_user(buff,tbuf,c2)) {
+				tcnt = -EFAULT;
+				break;
+			}
+			offs += c2;
+			tcnt += c2;
+			buff += c2;
+			count -= c2;
+			*ppos += c2;
+		}
+		kfree(tbuf);
+		return tcnt;
+	}
+
+	if (!fh->rhp) {
+		ret = pvr2_v4l2_iosetup(fh);
+		if (ret) {
+			return ret;
+		}
+	}
+
+	for (;;) {
+		ret = pvr2_ioread_read(fh->rhp,buff,count);
+		if (ret >= 0) break;
+		if (ret != -EAGAIN) break;
+		if (file->f_flags & O_NONBLOCK) break;
+		/* Doing blocking I/O.  Wait here. */
+		ret = wait_event_interruptible(
+			fh->wait_data,
+			pvr2_ioread_avail(fh->rhp) >= 0);
+		if (ret < 0) break;
+	}
+
+	return ret;
+}
+
+
+static __poll_t pvr2_v4l2_poll(struct file *file, poll_table *wait)
+{
+	__poll_t mask = 0;
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	int ret;
+
+	if (fh->fw_mode_flag) {
+		mask |= EPOLLIN | EPOLLRDNORM;
+		return mask;
+	}
+
+	if (!fh->rhp) {
+		ret = pvr2_v4l2_iosetup(fh);
+		if (ret) return EPOLLERR;
+	}
+
+	poll_wait(file,&fh->wait_data,wait);
+
+	if (pvr2_ioread_avail(fh->rhp) >= 0) {
+		mask |= EPOLLIN | EPOLLRDNORM;
+	}
+
+	return mask;
+}
+
+
+static const struct v4l2_file_operations vdev_fops = {
+	.owner      = THIS_MODULE,
+	.open       = pvr2_v4l2_open,
+	.release    = pvr2_v4l2_release,
+	.read       = pvr2_v4l2_read,
+	.unlocked_ioctl = video_ioctl2,
+	.poll       = pvr2_v4l2_poll,
+};
+
+
+static const struct video_device vdev_template = {
+	.fops       = &vdev_fops,
+};
+
+
+static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip,
+			       struct pvr2_v4l2 *vp,
+			       int v4l_type)
+{
+	int mindevnum;
+	int unit_number;
+	struct pvr2_hdw *hdw;
+	int *nr_ptr = NULL;
+	dip->v4lp = vp;
+
+	hdw = vp->channel.mc_head->hdw;
+	dip->v4l_type = v4l_type;
+	switch (v4l_type) {
+	case VFL_TYPE_GRABBER:
+		dip->stream = &vp->channel.mc_head->video_stream;
+		dip->config = pvr2_config_mpeg;
+		dip->minor_type = pvr2_v4l_type_video;
+		nr_ptr = video_nr;
+		if (!dip->stream) {
+			pr_err(KBUILD_MODNAME
+				": Failed to set up pvrusb2 v4l video dev due to missing stream instance\n");
+			return;
+		}
+		break;
+	case VFL_TYPE_VBI:
+		dip->config = pvr2_config_vbi;
+		dip->minor_type = pvr2_v4l_type_vbi;
+		nr_ptr = vbi_nr;
+		break;
+	case VFL_TYPE_RADIO:
+		dip->stream = &vp->channel.mc_head->video_stream;
+		dip->config = pvr2_config_mpeg;
+		dip->minor_type = pvr2_v4l_type_radio;
+		nr_ptr = radio_nr;
+		break;
+	default:
+		/* Bail out (this should be impossible) */
+		pr_err(KBUILD_MODNAME ": Failed to set up pvrusb2 v4l dev due to unrecognized config\n");
+		return;
+	}
+
+	dip->devbase = vdev_template;
+	dip->devbase.release = pvr2_video_device_release;
+	dip->devbase.ioctl_ops = &pvr2_ioctl_ops;
+	{
+		int val;
+		pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,
+						PVR2_CID_STDAVAIL), &val);
+		dip->devbase.tvnorms = (v4l2_std_id)val;
+	}
+
+	mindevnum = -1;
+	unit_number = pvr2_hdw_get_unit_number(hdw);
+	if (nr_ptr && (unit_number >= 0) && (unit_number < PVR_NUM)) {
+		mindevnum = nr_ptr[unit_number];
+	}
+	pvr2_hdw_set_v4l2_dev(hdw, &dip->devbase);
+	if ((video_register_device(&dip->devbase,
+				   dip->v4l_type, mindevnum) < 0) &&
+	    (video_register_device(&dip->devbase,
+				   dip->v4l_type, -1) < 0)) {
+		pr_err(KBUILD_MODNAME
+			": Failed to register pvrusb2 v4l device\n");
+	}
+
+	printk(KERN_INFO "pvrusb2: registered device %s [%s]\n",
+	       video_device_node_name(&dip->devbase),
+	       pvr2_config_get_name(dip->config));
+
+	pvr2_hdw_v4l_store_minor_number(hdw,
+					dip->minor_type,dip->devbase.minor);
+}
+
+
+struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp)
+{
+	struct pvr2_v4l2 *vp;
+
+	vp = kzalloc(sizeof(*vp),GFP_KERNEL);
+	if (!vp) return vp;
+	pvr2_channel_init(&vp->channel,mnp);
+	pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp);
+
+	vp->channel.check_func = pvr2_v4l2_internal_check;
+
+	/* register streams */
+	vp->dev_video = kzalloc(sizeof(*vp->dev_video),GFP_KERNEL);
+	if (!vp->dev_video) goto fail;
+	pvr2_v4l2_dev_init(vp->dev_video,vp,VFL_TYPE_GRABBER);
+	if (pvr2_hdw_get_input_available(vp->channel.mc_head->hdw) &
+	    (1 << PVR2_CVAL_INPUT_RADIO)) {
+		vp->dev_radio = kzalloc(sizeof(*vp->dev_radio),GFP_KERNEL);
+		if (!vp->dev_radio) goto fail;
+		pvr2_v4l2_dev_init(vp->dev_radio,vp,VFL_TYPE_RADIO);
+	}
+
+	return vp;
+ fail:
+	pvr2_trace(PVR2_TRACE_STRUCT,"Failure creating pvr2_v4l2 id=%p",vp);
+	pvr2_v4l2_destroy_no_lock(vp);
+	return NULL;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h
new file mode 100644
index 0000000..ec755ee
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License
+ *
+ *  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.
+ *
+ */
+#ifndef __PVRUSB2_V4L2_H
+#define __PVRUSB2_V4L2_H
+
+#include "pvrusb2-context.h"
+
+struct pvr2_v4l2;
+
+struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *);
+
+#endif /* __PVRUSB2_V4L2_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c
new file mode 100644
index 0000000..b68aec2
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c
@@ -0,0 +1,97 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   saa711x support that is available in the v4l available starting
+   with linux 2.6.15.
+
+*/
+
+#include "pvrusb2-video-v4l.h"
+
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/i2c/saa7115.h>
+#include <linux/errno.h>
+
+struct routing_scheme {
+	const int *def;
+	unsigned int cnt;
+};
+
+
+static const int routing_scheme0[] = {
+	[PVR2_CVAL_INPUT_TV] = SAA7115_COMPOSITE4,
+	/* In radio mode, we mute the video, but point at one
+	   spot just to stay consistent */
+	[PVR2_CVAL_INPUT_RADIO] = SAA7115_COMPOSITE5,
+	[PVR2_CVAL_INPUT_COMPOSITE] = SAA7115_COMPOSITE5,
+	[PVR2_CVAL_INPUT_SVIDEO] =  SAA7115_SVIDEO2,
+};
+
+static const struct routing_scheme routing_def0 = {
+	.def = routing_scheme0,
+	.cnt = ARRAY_SIZE(routing_scheme0),
+};
+
+static const int routing_scheme1[] = {
+	[PVR2_CVAL_INPUT_TV] = SAA7115_COMPOSITE4,
+	[PVR2_CVAL_INPUT_RADIO] = SAA7115_COMPOSITE5,
+	[PVR2_CVAL_INPUT_COMPOSITE] = SAA7115_COMPOSITE3,
+	[PVR2_CVAL_INPUT_SVIDEO] =  SAA7115_SVIDEO2, /* or SVIDEO0, it seems */
+};
+
+static const struct routing_scheme routing_def1 = {
+	.def = routing_scheme1,
+	.cnt = ARRAY_SIZE(routing_scheme1),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0,
+	[PVR2_ROUTING_SCHEME_ONAIR] = &routing_def1,
+};
+
+void pvr2_saa7115_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+		u32 input;
+
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_input(%d)",
+			   hdw->input_val);
+
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+		if ((sp == NULL) ||
+		    (hdw->input_val < 0) ||
+		    (hdw->input_val >= sp->cnt)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev v4l2 set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		input = sp->def[hdw->input_val];
+		sd->ops->video->s_routing(sd, input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h
new file mode 100644
index 0000000..fa33f20
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h
@@ -0,0 +1,34 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_VIDEO_V4L_H
+#define __PVRUSB2_VIDEO_V4L_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles device video processing.  This interface is
+   used internally by the driver; higher level code should only
+   interact through the interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+#include "pvrusb2-hdw-internal.h"
+void pvr2_saa7115_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *);
+
+#endif /* __PVRUSB2_VIDEO_V4L_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c
new file mode 100644
index 0000000..8f357f7
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c
@@ -0,0 +1,53 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   wm8775.
+
+*/
+
+#include "pvrusb2-wm8775.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+
+void pvr2_wm8775_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		u32 input;
+
+		switch (hdw->input_val) {
+		case PVR2_CVAL_INPUT_RADIO:
+			input = 1;
+			break;
+		default:
+			/* All other cases just use the second input */
+			input = 2;
+			break;
+		}
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev wm8775 set_input(val=%d route=0x%x)",
+			   hdw->input_val, input);
+
+		sd->ops->audio->s_routing(sd, input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h
new file mode 100644
index 0000000..c4ac7c2
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_WM8775_H
+#define __PVRUSB2_WM8775_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which performs analog -> digital audio conversion for
+   external audio inputs.  This interface is used internally by the
+   driver; higher level code should only interact through the
+   interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+
+#include "pvrusb2-hdw-internal.h"
+
+void pvr2_wm8775_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *sd);
+
+
+#endif /* __PVRUSB2_WM8775_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2.h b/drivers/media/usb/pvrusb2/pvrusb2.h
new file mode 100644
index 0000000..955290b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2.h
@@ -0,0 +1,28 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+#ifndef __PVRUSB2_H
+#define __PVRUSB2_H
+
+/* Maximum number of pvrusb2 instances we can track at once.  You
+   might want to increase this - however the driver operation will not
+   be impaired if it is too small.  Instead additional units just
+   won't have an ID assigned and it might not be possible to specify
+   module parameters for those extra units. */
+#define PVR_NUM 20
+
+#endif /* __PVRUSB2_H */