File-copy from v4.4.100

This is the result of 'cp' from a linux-stable tree with the 'v4.4.100'
tag checked out (commit 26d6298789e695c9f627ce49a7bbd2286405798a) on
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

Please refer to that tree for all history prior to this point.

Change-Id: I8a9ee2aea93cd29c52c847d0ce33091a73ae6afe
diff --git a/drivers/media/pci/saa7164/Kconfig b/drivers/media/pci/saa7164/Kconfig
new file mode 100644
index 0000000..9098ef5
--- /dev/null
+++ b/drivers/media/pci/saa7164/Kconfig
@@ -0,0 +1,17 @@
+config VIDEO_SAA7164
+	tristate "NXP SAA7164 support"
+	depends on DVB_CORE && VIDEO_DEV && PCI && I2C
+	select I2C_ALGOBIT
+	select FW_LOADER
+	select VIDEO_TUNER
+	select VIDEO_TVEEPROM
+	select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	---help---
+	  This is a video4linux driver for NXP SAA7164 based
+	  TV cards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called saa7164
+
diff --git a/drivers/media/pci/saa7164/Makefile b/drivers/media/pci/saa7164/Makefile
new file mode 100644
index 0000000..ba0e33a
--- /dev/null
+++ b/drivers/media/pci/saa7164/Makefile
@@ -0,0 +1,12 @@
+saa7164-objs	:= saa7164-cards.o saa7164-core.o saa7164-i2c.o saa7164-dvb.o \
+			saa7164-fw.o saa7164-bus.o saa7164-cmd.o saa7164-api.o \
+			saa7164-buffer.o saa7164-encoder.o saa7164-vbi.o
+
+obj-$(CONFIG_VIDEO_SAA7164) += saa7164.o
+
+ccflags-y += -I$(srctree)/drivers/media/i2c
+ccflags-y += -I$(srctree)/drivers/media/tuners
+ccflags-y += -I$(srctree)/drivers/media/dvb-core
+ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
+
+ccflags-y += $(extra-cflags-y) $(extra-cflags-m)
diff --git a/drivers/media/pci/saa7164/saa7164-api.c b/drivers/media/pci/saa7164/saa7164-api.c
new file mode 100644
index 0000000..e7e586c
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-api.c
@@ -0,0 +1,1528 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/wait.h>
+#include <linux/slab.h>
+
+#include "saa7164.h"
+
+int saa7164_api_get_load_info(struct saa7164_dev *dev, struct tmFwInfoStruct *i)
+{
+	int ret;
+
+	if (!(saa_debug & DBGLVL_CPU))
+		return 0;
+
+	dprintk(DBGLVL_API, "%s()\n", __func__);
+
+	i->deviceinst = 0;
+	i->devicespec = 0;
+	i->mode = 0;
+	i->status = 0;
+
+	ret = saa7164_cmd_send(dev, 0, GET_CUR,
+		GET_FW_STATUS_CONTROL, sizeof(struct tmFwInfoStruct), i);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	printk(KERN_INFO "saa7164[%d]-CPU: %d percent", dev->nr, i->CPULoad);
+
+	return ret;
+}
+
+int saa7164_api_collect_debug(struct saa7164_dev *dev)
+{
+	struct tmComResDebugGetData d;
+	u8 more = 255;
+	int ret;
+
+	dprintk(DBGLVL_API, "%s()\n", __func__);
+
+	while (more--) {
+
+		memset(&d, 0, sizeof(d));
+
+		ret = saa7164_cmd_send(dev, 0, GET_CUR,
+			GET_DEBUG_DATA_CONTROL, sizeof(d), &d);
+		if (ret != SAA_OK)
+			printk(KERN_ERR "%s() error, ret = 0x%x\n",
+				__func__, ret);
+
+		if (d.dwResult != SAA_OK)
+			break;
+
+		printk(KERN_INFO "saa7164[%d]-FWMSG: %s", dev->nr,
+			d.ucDebugData);
+	}
+
+	return 0;
+}
+
+int saa7164_api_set_debug(struct saa7164_dev *dev, u8 level)
+{
+	struct tmComResDebugSetLevel lvl;
+	int ret;
+
+	dprintk(DBGLVL_API, "%s(level=%d)\n", __func__, level);
+
+	/* Retrieve current state */
+	ret = saa7164_cmd_send(dev, 0, GET_CUR,
+		SET_DEBUG_LEVEL_CONTROL, sizeof(lvl), &lvl);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	dprintk(DBGLVL_API, "%s() Was %d\n", __func__, lvl.dwDebugLevel);
+
+	lvl.dwDebugLevel = level;
+
+	/* set new state */
+	ret = saa7164_cmd_send(dev, 0, SET_CUR,
+		SET_DEBUG_LEVEL_CONTROL, sizeof(lvl), &lvl);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_set_vbi_format(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct tmComResProbeCommit fmt, rsp;
+	int ret;
+
+	dprintk(DBGLVL_API, "%s(nr=%d, unitid=0x%x)\n", __func__,
+		port->nr, port->hwcfg.unitid);
+
+	fmt.bmHint = 0;
+	fmt.bFormatIndex = 1;
+	fmt.bFrameIndex = 1;
+
+	/* Probe, see if it can support this format */
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid,
+		SET_CUR, SAA_PROBE_CONTROL, sizeof(fmt), &fmt);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() set error, ret = 0x%x\n", __func__, ret);
+
+	/* See of the format change was successful */
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid,
+		GET_CUR, SAA_PROBE_CONTROL, sizeof(rsp), &rsp);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() get error, ret = 0x%x\n", __func__, ret);
+	} else {
+		/* Compare requested vs received, should be same */
+		if (memcmp(&fmt, &rsp, sizeof(rsp)) == 0) {
+			dprintk(DBGLVL_API, "SET/PROBE Verified\n");
+
+			/* Ask the device to select the negotiated format */
+			ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid,
+				SET_CUR, SAA_COMMIT_CONTROL, sizeof(fmt), &fmt);
+			if (ret != SAA_OK)
+				printk(KERN_ERR "%s() commit error, ret = 0x%x\n",
+					__func__, ret);
+
+			ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid,
+				GET_CUR, SAA_COMMIT_CONTROL, sizeof(rsp), &rsp);
+			if (ret != SAA_OK)
+				printk(KERN_ERR "%s() GET commit error, ret = 0x%x\n",
+					__func__, ret);
+
+			if (memcmp(&fmt, &rsp, sizeof(rsp)) != 0) {
+				printk(KERN_ERR "%s() memcmp error, ret = 0x%x\n",
+					__func__, ret);
+			} else
+				dprintk(DBGLVL_API, "SET/COMMIT Verified\n");
+
+			dprintk(DBGLVL_API, "rsp.bmHint = 0x%x\n", rsp.bmHint);
+			dprintk(DBGLVL_API, "rsp.bFormatIndex = 0x%x\n",
+				rsp.bFormatIndex);
+			dprintk(DBGLVL_API, "rsp.bFrameIndex = 0x%x\n",
+				rsp.bFrameIndex);
+		} else
+			printk(KERN_ERR "%s() compare failed\n", __func__);
+	}
+
+	if (ret == SAA_OK)
+		dprintk(DBGLVL_API, "%s(nr=%d) Success\n", __func__, port->nr);
+
+	return ret;
+}
+
+static int saa7164_api_set_gop_size(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct tmComResEncVideoGopStructure gs;
+	int ret;
+
+	dprintk(DBGLVL_ENC, "%s()\n", __func__);
+
+	gs.ucRefFrameDist = port->encoder_params.refdist;
+	gs.ucGOPSize = port->encoder_params.gop_size;
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR,
+		EU_VIDEO_GOP_STRUCTURE_CONTROL,
+		sizeof(gs), &gs);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_set_encoder(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct tmComResEncVideoBitRate vb;
+	struct tmComResEncAudioBitRate ab;
+	int ret;
+
+	dprintk(DBGLVL_ENC, "%s() unitid=0x%x\n", __func__,
+		port->hwcfg.sourceid);
+
+	if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS)
+		port->encoder_profile = EU_PROFILE_PS_DVD;
+	else
+		port->encoder_profile = EU_PROFILE_TS_HQ;
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR,
+		EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Resolution */
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR,
+		EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Establish video bitrates */
+	if (port->encoder_params.bitrate_mode ==
+		V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)
+		vb.ucVideoBitRateMode = EU_VIDEO_BIT_RATE_MODE_CONSTANT;
+	else
+		vb.ucVideoBitRateMode = EU_VIDEO_BIT_RATE_MODE_VARIABLE_PEAK;
+	vb.dwVideoBitRate = port->encoder_params.bitrate;
+	vb.dwVideoBitRatePeak = port->encoder_params.bitrate_peak;
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR,
+		EU_VIDEO_BIT_RATE_CONTROL,
+		sizeof(struct tmComResEncVideoBitRate),
+		&vb);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Establish audio bitrates */
+	ab.ucAudioBitRateMode = 0;
+	ab.dwAudioBitRate = 384000;
+	ab.dwAudioBitRatePeak = ab.dwAudioBitRate;
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR,
+		EU_AUDIO_BIT_RATE_CONTROL,
+		sizeof(struct tmComResEncAudioBitRate),
+		&ab);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__,
+			ret);
+
+	saa7164_api_set_aspect_ratio(port);
+	saa7164_api_set_gop_size(port);
+
+	return ret;
+}
+
+int saa7164_api_get_encoder(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct tmComResEncVideoBitRate v;
+	struct tmComResEncAudioBitRate a;
+	struct tmComResEncVideoInputAspectRatio ar;
+	int ret;
+
+	dprintk(DBGLVL_ENC, "%s() unitid=0x%x\n", __func__,
+		port->hwcfg.sourceid);
+
+	port->encoder_profile = 0;
+	port->video_format = 0;
+	port->video_resolution = 0;
+	port->audio_format = 0;
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR,
+		EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR,
+		EU_VIDEO_RESOLUTION_CONTROL, sizeof(u8),
+		&port->video_resolution);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR,
+		EU_VIDEO_FORMAT_CONTROL, sizeof(u8), &port->video_format);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR,
+		EU_VIDEO_BIT_RATE_CONTROL, sizeof(v), &v);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR,
+		EU_AUDIO_FORMAT_CONTROL, sizeof(u8), &port->audio_format);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR,
+		EU_AUDIO_BIT_RATE_CONTROL, sizeof(a), &a);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Aspect Ratio */
+	ar.width = 0;
+	ar.height = 0;
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR,
+		EU_VIDEO_INPUT_ASPECT_CONTROL,
+		sizeof(struct tmComResEncVideoInputAspectRatio), &ar);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	dprintk(DBGLVL_ENC, "encoder_profile = %d\n", port->encoder_profile);
+	dprintk(DBGLVL_ENC, "video_format    = %d\n", port->video_format);
+	dprintk(DBGLVL_ENC, "audio_format    = %d\n", port->audio_format);
+	dprintk(DBGLVL_ENC, "video_resolution= %d\n", port->video_resolution);
+	dprintk(DBGLVL_ENC, "v.ucVideoBitRateMode = %d\n",
+		v.ucVideoBitRateMode);
+	dprintk(DBGLVL_ENC, "v.dwVideoBitRate     = %d\n",
+		v.dwVideoBitRate);
+	dprintk(DBGLVL_ENC, "v.dwVideoBitRatePeak = %d\n",
+		v.dwVideoBitRatePeak);
+	dprintk(DBGLVL_ENC, "a.ucVideoBitRateMode = %d\n",
+		a.ucAudioBitRateMode);
+	dprintk(DBGLVL_ENC, "a.dwVideoBitRate     = %d\n",
+		a.dwAudioBitRate);
+	dprintk(DBGLVL_ENC, "a.dwVideoBitRatePeak = %d\n",
+		a.dwAudioBitRatePeak);
+	dprintk(DBGLVL_ENC, "aspect.width / height = %d:%d\n",
+		ar.width, ar.height);
+
+	return ret;
+}
+
+int saa7164_api_set_aspect_ratio(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct tmComResEncVideoInputAspectRatio ar;
+	int ret;
+
+	dprintk(DBGLVL_ENC, "%s(%d)\n", __func__,
+		port->encoder_params.ctl_aspect);
+
+	switch (port->encoder_params.ctl_aspect) {
+	case V4L2_MPEG_VIDEO_ASPECT_1x1:
+		ar.width = 1;
+		ar.height = 1;
+		break;
+	case V4L2_MPEG_VIDEO_ASPECT_4x3:
+		ar.width = 4;
+		ar.height = 3;
+		break;
+	case V4L2_MPEG_VIDEO_ASPECT_16x9:
+		ar.width = 16;
+		ar.height = 9;
+		break;
+	case V4L2_MPEG_VIDEO_ASPECT_221x100:
+		ar.width = 221;
+		ar.height = 100;
+		break;
+	default:
+		BUG();
+	}
+
+	dprintk(DBGLVL_ENC, "%s(%d) now %d:%d\n", __func__,
+		port->encoder_params.ctl_aspect,
+		ar.width, ar.height);
+
+	/* Aspect Ratio */
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR,
+		EU_VIDEO_INPUT_ASPECT_CONTROL,
+		sizeof(struct tmComResEncVideoInputAspectRatio), &ar);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_set_usercontrol(struct saa7164_port *port, u8 ctl)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+	u16 val;
+
+	if (ctl == PU_BRIGHTNESS_CONTROL)
+		val = port->ctl_brightness;
+	else
+	if (ctl == PU_CONTRAST_CONTROL)
+		val = port->ctl_contrast;
+	else
+	if (ctl == PU_HUE_CONTROL)
+		val = port->ctl_hue;
+	else
+	if (ctl == PU_SATURATION_CONTROL)
+		val = port->ctl_saturation;
+	else
+	if (ctl == PU_SHARPNESS_CONTROL)
+		val = port->ctl_sharpness;
+	else
+		return -EINVAL;
+
+	dprintk(DBGLVL_ENC, "%s() unitid=0x%x ctl=%d, val=%d\n",
+		__func__, port->encunit.vsourceid, ctl, val);
+
+	ret = saa7164_cmd_send(port->dev, port->encunit.vsourceid, SET_CUR,
+		ctl, sizeof(u16), &val);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_get_usercontrol(struct saa7164_port *port, u8 ctl)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+	u16 val;
+
+	ret = saa7164_cmd_send(port->dev, port->encunit.vsourceid, GET_CUR,
+		ctl, sizeof(u16), &val);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+		return ret;
+	}
+
+	dprintk(DBGLVL_ENC, "%s() ctl=%d, val=%d\n",
+		__func__, ctl, val);
+
+	if (ctl == PU_BRIGHTNESS_CONTROL)
+		port->ctl_brightness = val;
+	else
+	if (ctl == PU_CONTRAST_CONTROL)
+		port->ctl_contrast = val;
+	else
+	if (ctl == PU_HUE_CONTROL)
+		port->ctl_hue = val;
+	else
+	if (ctl == PU_SATURATION_CONTROL)
+		port->ctl_saturation = val;
+	else
+	if (ctl == PU_SHARPNESS_CONTROL)
+		port->ctl_sharpness = val;
+
+	return ret;
+}
+
+int saa7164_api_set_videomux(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	u8 inputs[] = { 1, 2, 2, 2, 5, 5, 5 };
+	int ret;
+
+	dprintk(DBGLVL_ENC, "%s() v_mux=%d a_mux=%d\n",
+		__func__, port->mux_input, inputs[port->mux_input - 1]);
+
+	/* Audio Mute */
+	ret = saa7164_api_audio_mute(port, 1);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Video Mux */
+	ret = saa7164_cmd_send(port->dev, port->vidproc.sourceid, SET_CUR,
+		SU_INPUT_SELECT_CONTROL, sizeof(u8), &port->mux_input);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Audio Mux */
+	ret = saa7164_cmd_send(port->dev, port->audfeat.sourceid, SET_CUR,
+		SU_INPUT_SELECT_CONTROL, sizeof(u8),
+		&inputs[port->mux_input - 1]);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Audio UnMute */
+	ret = saa7164_api_audio_mute(port, 0);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_audio_mute(struct saa7164_port *port, int mute)
+{
+	struct saa7164_dev *dev = port->dev;
+	u8 v = mute;
+	int ret;
+
+	dprintk(DBGLVL_API, "%s(%d)\n", __func__, mute);
+
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR,
+		MUTE_CONTROL, sizeof(u8), &v);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	return ret;
+}
+
+/* 0 = silence, 0xff = full */
+int saa7164_api_set_audio_volume(struct saa7164_port *port, s8 level)
+{
+	struct saa7164_dev *dev = port->dev;
+	s16 v, min, max;
+	int ret;
+
+	dprintk(DBGLVL_API, "%s(%d)\n", __func__, level);
+
+	/* Obtain the min/max ranges */
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_MIN,
+		VOLUME_CONTROL, sizeof(u16), &min);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_MAX,
+		VOLUME_CONTROL, sizeof(u16), &max);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_CUR,
+		(0x01 << 8) | VOLUME_CONTROL, sizeof(u16), &v);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	dprintk(DBGLVL_API, "%s(%d) min=%d max=%d cur=%d\n", __func__,
+		level, min, max, v);
+
+	v = level;
+	if (v < min)
+		v = min;
+	if (v > max)
+		v = max;
+
+	/* Left */
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR,
+		(0x01 << 8) | VOLUME_CONTROL, sizeof(s16), &v);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Right */
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR,
+		(0x02 << 8) | VOLUME_CONTROL, sizeof(s16), &v);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_CUR,
+		(0x01 << 8) | VOLUME_CONTROL, sizeof(u16), &v);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	dprintk(DBGLVL_API, "%s(%d) min=%d max=%d cur=%d\n", __func__,
+		level, min, max, v);
+
+	return ret;
+}
+
+int saa7164_api_set_audio_std(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct tmComResAudioDefaults lvl;
+	struct tmComResTunerStandard tvaudio;
+	int ret;
+
+	dprintk(DBGLVL_API, "%s()\n", __func__);
+
+	/* Establish default levels */
+	lvl.ucDecoderLevel = TMHW_LEV_ADJ_DECLEV_DEFAULT;
+	lvl.ucDecoderFM_Level = TMHW_LEV_ADJ_DECLEV_DEFAULT;
+	lvl.ucMonoLevel = TMHW_LEV_ADJ_MONOLEV_DEFAULT;
+	lvl.ucNICAM_Level = TMHW_LEV_ADJ_NICLEV_DEFAULT;
+	lvl.ucSAP_Level = TMHW_LEV_ADJ_SAPLEV_DEFAULT;
+	lvl.ucADC_Level = TMHW_LEV_ADJ_ADCLEV_DEFAULT;
+	ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR,
+		AUDIO_DEFAULT_CONTROL, sizeof(struct tmComResAudioDefaults),
+		&lvl);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	/* Manually select the appropriate TV audio standard */
+	if (port->encodernorm.id & V4L2_STD_NTSC) {
+		tvaudio.std = TU_STANDARD_NTSC_M;
+		tvaudio.country = 1;
+	} else {
+		tvaudio.std = TU_STANDARD_PAL_I;
+		tvaudio.country = 44;
+	}
+
+	ret = saa7164_cmd_send(port->dev, port->tunerunit.unitid, SET_CUR,
+		TU_STANDARD_CONTROL, sizeof(tvaudio), &tvaudio);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() TU_STANDARD_CONTROL error, ret = 0x%x\n",
+			__func__, ret);
+	return ret;
+}
+
+int saa7164_api_set_audio_detection(struct saa7164_port *port, int autodetect)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct tmComResTunerStandardAuto p;
+	int ret;
+
+	dprintk(DBGLVL_API, "%s(%d)\n", __func__, autodetect);
+
+	/* Disable TV Audio autodetect if not already set (buggy) */
+	if (autodetect)
+		p.mode = TU_STANDARD_AUTO;
+	else
+		p.mode = TU_STANDARD_MANUAL;
+	ret = saa7164_cmd_send(port->dev, port->tunerunit.unitid, SET_CUR,
+		TU_STANDARD_AUTO_CONTROL, sizeof(p), &p);
+	if (ret != SAA_OK)
+		printk(KERN_ERR
+			"%s() TU_STANDARD_AUTO_CONTROL error, ret = 0x%x\n",
+			__func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_get_videomux(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_cmd_send(port->dev, port->vidproc.sourceid, GET_CUR,
+		SU_INPUT_SELECT_CONTROL, sizeof(u8), &port->mux_input);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	dprintk(DBGLVL_ENC, "%s() v_mux=%d\n",
+		__func__, port->mux_input);
+
+	return ret;
+}
+
+static int saa7164_api_set_dif(struct saa7164_port *port, u8 reg, u8 val)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	u16 len = 0;
+	u8 buf[256];
+	int ret;
+	u8 mas;
+
+	dprintk(DBGLVL_API, "%s(nr=%d type=%d val=%x)\n", __func__,
+		port->nr, port->type, val);
+
+	if (port->nr == 0)
+		mas = 0xd0;
+	else
+		mas = 0xe0;
+
+	memset(buf, 0, sizeof(buf));
+
+	buf[0x00] = 0x04;
+	buf[0x01] = 0x00;
+	buf[0x02] = 0x00;
+	buf[0x03] = 0x00;
+
+	buf[0x04] = 0x04;
+	buf[0x05] = 0x00;
+	buf[0x06] = 0x00;
+	buf[0x07] = 0x00;
+
+	buf[0x08] = reg;
+	buf[0x09] = 0x26;
+	buf[0x0a] = mas;
+	buf[0x0b] = 0xb0;
+
+	buf[0x0c] = val;
+	buf[0x0d] = 0x00;
+	buf[0x0e] = 0x00;
+	buf[0x0f] = 0x00;
+
+	ret = saa7164_cmd_send(dev, port->ifunit.unitid, GET_LEN,
+		EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret);
+		return -EIO;
+	}
+
+	ret = saa7164_cmd_send(dev, port->ifunit.unitid, SET_CUR,
+		EXU_REGISTER_ACCESS_CONTROL, len, &buf);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret);
+#if 0
+	print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf, 16,
+		       false);
+#endif
+	return ret == SAA_OK ? 0 : -EIO;
+}
+
+/* Disable the IF block AGC controls */
+int saa7164_api_configure_dif(struct saa7164_port *port, u32 std)
+{
+	struct saa7164_dev *dev = port->dev;
+	u8 agc_disable;
+
+	dprintk(DBGLVL_API, "%s(nr=%d, 0x%x)\n", __func__, port->nr, std);
+
+	if (std & V4L2_STD_NTSC) {
+		dprintk(DBGLVL_API, " NTSC\n");
+		saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */
+		agc_disable = 0;
+	} else if (std & V4L2_STD_PAL_I) {
+		dprintk(DBGLVL_API, " PAL-I\n");
+		saa7164_api_set_dif(port, 0x00, 0x08); /* Video Standard */
+		agc_disable = 0;
+	} else if (std & V4L2_STD_PAL_M) {
+		dprintk(DBGLVL_API, " PAL-M\n");
+		saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */
+		agc_disable = 0;
+	} else if (std & V4L2_STD_PAL_N) {
+		dprintk(DBGLVL_API, " PAL-N\n");
+		saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */
+		agc_disable = 0;
+	} else if (std & V4L2_STD_PAL_Nc) {
+		dprintk(DBGLVL_API, " PAL-Nc\n");
+		saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */
+		agc_disable = 0;
+	} else if (std & V4L2_STD_PAL_B) {
+		dprintk(DBGLVL_API, " PAL-B\n");
+		saa7164_api_set_dif(port, 0x00, 0x02); /* Video Standard */
+		agc_disable = 0;
+	} else if (std & V4L2_STD_PAL_DK) {
+		dprintk(DBGLVL_API, " PAL-DK\n");
+		saa7164_api_set_dif(port, 0x00, 0x10); /* Video Standard */
+		agc_disable = 0;
+	} else if (std & V4L2_STD_SECAM_L) {
+		dprintk(DBGLVL_API, " SECAM-L\n");
+		saa7164_api_set_dif(port, 0x00, 0x20); /* Video Standard */
+		agc_disable = 0;
+	} else {
+		/* Unknown standard, assume DTV */
+		dprintk(DBGLVL_API, " Unknown (assuming DTV)\n");
+		/* Undefinded Video Standard */
+		saa7164_api_set_dif(port, 0x00, 0x80);
+		agc_disable = 1;
+	}
+
+	saa7164_api_set_dif(port, 0x48, 0xa0); /* AGC Functions 1 */
+	saa7164_api_set_dif(port, 0xc0, agc_disable); /* AGC Output Disable */
+	saa7164_api_set_dif(port, 0x7c, 0x04); /* CVBS EQ */
+	saa7164_api_set_dif(port, 0x04, 0x01); /* Active */
+	msleep(100);
+	saa7164_api_set_dif(port, 0x04, 0x00); /* Active (again) */
+	msleep(100);
+
+	return 0;
+}
+
+/* Ensure the dif is in the correct state for the operating mode
+ * (analog / dtv). We only configure the diff through the analog encoder
+ * so when we're in digital mode we need to find the appropriate encoder
+ * and use it to configure the DIF.
+ */
+int saa7164_api_initialize_dif(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_port *p = NULL;
+	int ret = -EINVAL;
+	u32 std = 0;
+
+	dprintk(DBGLVL_API, "%s(nr=%d type=%d)\n", __func__,
+		port->nr, port->type);
+
+	if (port->type == SAA7164_MPEG_ENCODER) {
+		/* Pick any analog standard to init the diff.
+		 * we'll come back during encoder_init'
+		 * and set the correct standard if requried.
+		 */
+		std = V4L2_STD_NTSC;
+	} else
+	if (port->type == SAA7164_MPEG_DVB) {
+		if (port->nr == SAA7164_PORT_TS1)
+			p = &dev->ports[SAA7164_PORT_ENC1];
+		else
+			p = &dev->ports[SAA7164_PORT_ENC2];
+	} else
+	if (port->type == SAA7164_MPEG_VBI) {
+		std = V4L2_STD_NTSC;
+		if (port->nr == SAA7164_PORT_VBI1)
+			p = &dev->ports[SAA7164_PORT_ENC1];
+		else
+			p = &dev->ports[SAA7164_PORT_ENC2];
+	} else
+		BUG();
+
+	if (p)
+		ret = saa7164_api_configure_dif(p, std);
+
+	return ret;
+}
+
+int saa7164_api_transition_port(struct saa7164_port *port, u8 mode)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	int ret;
+
+	dprintk(DBGLVL_API, "%s(nr=%d unitid=0x%x,%d)\n",
+		__func__, port->nr, port->hwcfg.unitid, mode);
+
+	ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, SET_CUR,
+		SAA_STATE_CONTROL, sizeof(mode), &mode);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s(portnr %d unitid 0x%x) error, ret = 0x%x\n",
+			__func__, port->nr, port->hwcfg.unitid, ret);
+
+	return ret;
+}
+
+int saa7164_api_get_fw_version(struct saa7164_dev *dev, u32 *version)
+{
+	int ret;
+
+	ret = saa7164_cmd_send(dev, 0, GET_CUR,
+		GET_FW_VERSION_CONTROL, sizeof(u32), version);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_read_eeprom(struct saa7164_dev *dev, u8 *buf, int buflen)
+{
+	u8 reg[] = { 0x0f, 0x00 };
+
+	if (buflen < 128)
+		return -ENOMEM;
+
+	/* Assumption: Hauppauge eeprom is at 0xa0 on on bus 0 */
+	/* TODO: Pull the details from the boards struct */
+	return saa7164_api_i2c_read(&dev->i2c_bus[0], 0xa0 >> 1, sizeof(reg),
+		&reg[0], 128, buf);
+}
+
+static int saa7164_api_configure_port_vbi(struct saa7164_dev *dev,
+					  struct saa7164_port *port)
+{
+	struct tmComResVBIFormatDescrHeader *fmt = &port->vbi_fmt_ntsc;
+
+	dprintk(DBGLVL_API, "    bFormatIndex  = 0x%x\n", fmt->bFormatIndex);
+	dprintk(DBGLVL_API, "    VideoStandard = 0x%x\n", fmt->VideoStandard);
+	dprintk(DBGLVL_API, "    StartLine     = %d\n", fmt->StartLine);
+	dprintk(DBGLVL_API, "    EndLine       = %d\n", fmt->EndLine);
+	dprintk(DBGLVL_API, "    FieldRate     = %d\n", fmt->FieldRate);
+	dprintk(DBGLVL_API, "    bNumLines     = %d\n", fmt->bNumLines);
+
+	/* Cache the hardware configuration in the port */
+
+	port->bufcounter = port->hwcfg.BARLocation;
+	port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32));
+	port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32));
+	port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32));
+	port->bufptr32l = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32);
+	port->bufptr32h = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount);
+	port->bufptr64 = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount);
+	dprintk(DBGLVL_API, "   = port->hwcfg.BARLocation = 0x%x\n",
+		port->hwcfg.BARLocation);
+
+	dprintk(DBGLVL_API, "   = VS_FORMAT_VBI (becomes dev->en[%d])\n",
+		port->nr);
+
+	return 0;
+}
+
+static int
+saa7164_api_configure_port_mpeg2ts(struct saa7164_dev *dev,
+				   struct saa7164_port *port,
+				   struct tmComResTSFormatDescrHeader *tsfmt)
+{
+	dprintk(DBGLVL_API, "    bFormatIndex = 0x%x\n", tsfmt->bFormatIndex);
+	dprintk(DBGLVL_API, "    bDataOffset  = 0x%x\n", tsfmt->bDataOffset);
+	dprintk(DBGLVL_API, "    bPacketLength= 0x%x\n", tsfmt->bPacketLength);
+	dprintk(DBGLVL_API, "    bStrideLength= 0x%x\n", tsfmt->bStrideLength);
+	dprintk(DBGLVL_API, "    bguid        = (....)\n");
+
+	/* Cache the hardware configuration in the port */
+
+	port->bufcounter = port->hwcfg.BARLocation;
+	port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32));
+	port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32));
+	port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32));
+	port->bufptr32l = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32);
+	port->bufptr32h = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount);
+	port->bufptr64 = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount);
+	dprintk(DBGLVL_API, "   = port->hwcfg.BARLocation = 0x%x\n",
+		port->hwcfg.BARLocation);
+
+	dprintk(DBGLVL_API, "   = VS_FORMAT_MPEGTS (becomes dev->ts[%d])\n",
+		port->nr);
+
+	return 0;
+}
+
+static int
+saa7164_api_configure_port_mpeg2ps(struct saa7164_dev *dev,
+				   struct saa7164_port *port,
+				   struct tmComResPSFormatDescrHeader *fmt)
+{
+	dprintk(DBGLVL_API, "    bFormatIndex = 0x%x\n", fmt->bFormatIndex);
+	dprintk(DBGLVL_API, "    wPacketLength= 0x%x\n", fmt->wPacketLength);
+	dprintk(DBGLVL_API, "    wPackLength=   0x%x\n", fmt->wPackLength);
+	dprintk(DBGLVL_API, "    bPackDataType= 0x%x\n", fmt->bPackDataType);
+
+	/* Cache the hardware configuration in the port */
+	/* TODO: CHECK THIS in the port config */
+	port->bufcounter = port->hwcfg.BARLocation;
+	port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32));
+	port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32));
+	port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32));
+	port->bufptr32l = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32);
+	port->bufptr32h = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount);
+	port->bufptr64 = port->hwcfg.BARLocation +
+		(4 * sizeof(u32)) +
+		(sizeof(u32) * port->hwcfg.buffercount);
+	dprintk(DBGLVL_API, "   = port->hwcfg.BARLocation = 0x%x\n",
+		port->hwcfg.BARLocation);
+
+	dprintk(DBGLVL_API, "   = VS_FORMAT_MPEGPS (becomes dev->enc[%d])\n",
+		port->nr);
+
+	return 0;
+}
+
+static int saa7164_api_dump_subdevs(struct saa7164_dev *dev, u8 *buf, int len)
+{
+	struct saa7164_port *tsport = NULL;
+	struct saa7164_port *encport = NULL;
+	struct saa7164_port *vbiport = NULL;
+	u32 idx, next_offset;
+	int i;
+	struct tmComResDescrHeader *hdr, *t;
+	struct tmComResExtDevDescrHeader *exthdr;
+	struct tmComResPathDescrHeader *pathhdr;
+	struct tmComResAntTermDescrHeader *anttermhdr;
+	struct tmComResTunerDescrHeader *tunerunithdr;
+	struct tmComResDMATermDescrHeader *vcoutputtermhdr;
+	struct tmComResTSFormatDescrHeader *tsfmt;
+	struct tmComResPSFormatDescrHeader *psfmt;
+	struct tmComResSelDescrHeader *psel;
+	struct tmComResProcDescrHeader *pdh;
+	struct tmComResAFeatureDescrHeader *afd;
+	struct tmComResEncoderDescrHeader *edh;
+	struct tmComResVBIFormatDescrHeader *vbifmt;
+	u32 currpath = 0;
+
+	dprintk(DBGLVL_API,
+		"%s(?,?,%d) sizeof(struct tmComResDescrHeader) = %d bytes\n",
+		__func__, len, (u32)sizeof(struct tmComResDescrHeader));
+
+	for (idx = 0; idx < (len - sizeof(struct tmComResDescrHeader));) {
+
+		hdr = (struct tmComResDescrHeader *)(buf + idx);
+
+		if (hdr->type != CS_INTERFACE)
+			return SAA_ERR_NOT_SUPPORTED;
+
+		dprintk(DBGLVL_API, "@ 0x%x =\n", idx);
+		switch (hdr->subtype) {
+		case GENERAL_REQUEST:
+			dprintk(DBGLVL_API, " GENERAL_REQUEST\n");
+			break;
+		case VC_TUNER_PATH:
+			dprintk(DBGLVL_API, " VC_TUNER_PATH\n");
+			pathhdr = (struct tmComResPathDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, "  pathid = 0x%x\n",
+				pathhdr->pathid);
+			currpath = pathhdr->pathid;
+			break;
+		case VC_INPUT_TERMINAL:
+			dprintk(DBGLVL_API, " VC_INPUT_TERMINAL\n");
+			anttermhdr =
+				(struct tmComResAntTermDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, "  terminalid   = 0x%x\n",
+				anttermhdr->terminalid);
+			dprintk(DBGLVL_API, "  terminaltype = 0x%x\n",
+				anttermhdr->terminaltype);
+			switch (anttermhdr->terminaltype) {
+			case ITT_ANTENNA:
+				dprintk(DBGLVL_API, "   = ITT_ANTENNA\n");
+				break;
+			case LINE_CONNECTOR:
+				dprintk(DBGLVL_API, "   = LINE_CONNECTOR\n");
+				break;
+			case SPDIF_CONNECTOR:
+				dprintk(DBGLVL_API, "   = SPDIF_CONNECTOR\n");
+				break;
+			case COMPOSITE_CONNECTOR:
+				dprintk(DBGLVL_API,
+					"   = COMPOSITE_CONNECTOR\n");
+				break;
+			case SVIDEO_CONNECTOR:
+				dprintk(DBGLVL_API, "   = SVIDEO_CONNECTOR\n");
+				break;
+			case COMPONENT_CONNECTOR:
+				dprintk(DBGLVL_API,
+					"   = COMPONENT_CONNECTOR\n");
+				break;
+			case STANDARD_DMA:
+				dprintk(DBGLVL_API, "   = STANDARD_DMA\n");
+				break;
+			default:
+				dprintk(DBGLVL_API, "   = undefined (0x%x)\n",
+					anttermhdr->terminaltype);
+			}
+			dprintk(DBGLVL_API, "  assocterminal= 0x%x\n",
+				anttermhdr->assocterminal);
+			dprintk(DBGLVL_API, "  iterminal    = 0x%x\n",
+				anttermhdr->iterminal);
+			dprintk(DBGLVL_API, "  controlsize  = 0x%x\n",
+				anttermhdr->controlsize);
+			break;
+		case VC_OUTPUT_TERMINAL:
+			dprintk(DBGLVL_API, " VC_OUTPUT_TERMINAL\n");
+			vcoutputtermhdr =
+				(struct tmComResDMATermDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, "  unitid = 0x%x\n",
+				vcoutputtermhdr->unitid);
+			dprintk(DBGLVL_API, "  terminaltype = 0x%x\n",
+				vcoutputtermhdr->terminaltype);
+			switch (vcoutputtermhdr->terminaltype) {
+			case ITT_ANTENNA:
+				dprintk(DBGLVL_API, "   = ITT_ANTENNA\n");
+				break;
+			case LINE_CONNECTOR:
+				dprintk(DBGLVL_API, "   = LINE_CONNECTOR\n");
+				break;
+			case SPDIF_CONNECTOR:
+				dprintk(DBGLVL_API, "   = SPDIF_CONNECTOR\n");
+				break;
+			case COMPOSITE_CONNECTOR:
+				dprintk(DBGLVL_API,
+					"   = COMPOSITE_CONNECTOR\n");
+				break;
+			case SVIDEO_CONNECTOR:
+				dprintk(DBGLVL_API, "   = SVIDEO_CONNECTOR\n");
+				break;
+			case COMPONENT_CONNECTOR:
+				dprintk(DBGLVL_API,
+					"   = COMPONENT_CONNECTOR\n");
+				break;
+			case STANDARD_DMA:
+				dprintk(DBGLVL_API, "   = STANDARD_DMA\n");
+				break;
+			default:
+				dprintk(DBGLVL_API, "   = undefined (0x%x)\n",
+					vcoutputtermhdr->terminaltype);
+			}
+			dprintk(DBGLVL_API, "  assocterminal= 0x%x\n",
+				vcoutputtermhdr->assocterminal);
+			dprintk(DBGLVL_API, "  sourceid     = 0x%x\n",
+				vcoutputtermhdr->sourceid);
+			dprintk(DBGLVL_API, "  iterminal    = 0x%x\n",
+				vcoutputtermhdr->iterminal);
+			dprintk(DBGLVL_API, "  BARLocation  = 0x%x\n",
+				vcoutputtermhdr->BARLocation);
+			dprintk(DBGLVL_API, "  flags        = 0x%x\n",
+				vcoutputtermhdr->flags);
+			dprintk(DBGLVL_API, "  interruptid  = 0x%x\n",
+				vcoutputtermhdr->interruptid);
+			dprintk(DBGLVL_API, "  buffercount  = 0x%x\n",
+				vcoutputtermhdr->buffercount);
+			dprintk(DBGLVL_API, "  metadatasize = 0x%x\n",
+				vcoutputtermhdr->metadatasize);
+			dprintk(DBGLVL_API, "  controlsize  = 0x%x\n",
+				vcoutputtermhdr->controlsize);
+			dprintk(DBGLVL_API, "  numformats   = 0x%x\n",
+				vcoutputtermhdr->numformats);
+
+			t = (struct tmComResDescrHeader *)
+				((struct tmComResDMATermDescrHeader *)(buf + idx));
+			next_offset = idx + (vcoutputtermhdr->len);
+			for (i = 0; i < vcoutputtermhdr->numformats; i++) {
+				t = (struct tmComResDescrHeader *)
+					(buf + next_offset);
+				switch (t->subtype) {
+				case VS_FORMAT_MPEG2TS:
+					tsfmt =
+					(struct tmComResTSFormatDescrHeader *)t;
+					if (currpath == 1)
+						tsport = &dev->ports[SAA7164_PORT_TS1];
+					else
+						tsport = &dev->ports[SAA7164_PORT_TS2];
+					memcpy(&tsport->hwcfg, vcoutputtermhdr,
+						sizeof(*vcoutputtermhdr));
+					saa7164_api_configure_port_mpeg2ts(dev,
+						tsport, tsfmt);
+					break;
+				case VS_FORMAT_MPEG2PS:
+					psfmt =
+					(struct tmComResPSFormatDescrHeader *)t;
+					if (currpath == 1)
+						encport = &dev->ports[SAA7164_PORT_ENC1];
+					else
+						encport = &dev->ports[SAA7164_PORT_ENC2];
+					memcpy(&encport->hwcfg, vcoutputtermhdr,
+						sizeof(*vcoutputtermhdr));
+					saa7164_api_configure_port_mpeg2ps(dev,
+						encport, psfmt);
+					break;
+				case VS_FORMAT_VBI:
+					vbifmt =
+					(struct tmComResVBIFormatDescrHeader *)t;
+					if (currpath == 1)
+						vbiport = &dev->ports[SAA7164_PORT_VBI1];
+					else
+						vbiport = &dev->ports[SAA7164_PORT_VBI2];
+					memcpy(&vbiport->hwcfg, vcoutputtermhdr,
+						sizeof(*vcoutputtermhdr));
+					memcpy(&vbiport->vbi_fmt_ntsc, vbifmt,
+						sizeof(*vbifmt));
+					saa7164_api_configure_port_vbi(dev,
+						vbiport);
+					break;
+				case VS_FORMAT_RDS:
+					dprintk(DBGLVL_API,
+						"   = VS_FORMAT_RDS\n");
+					break;
+				case VS_FORMAT_UNCOMPRESSED:
+					dprintk(DBGLVL_API,
+					"   = VS_FORMAT_UNCOMPRESSED\n");
+					break;
+				case VS_FORMAT_TYPE:
+					dprintk(DBGLVL_API,
+						"   = VS_FORMAT_TYPE\n");
+					break;
+				default:
+					dprintk(DBGLVL_API,
+						"   = undefined (0x%x)\n",
+						t->subtype);
+				}
+				next_offset += t->len;
+			}
+
+			break;
+		case TUNER_UNIT:
+			dprintk(DBGLVL_API, " TUNER_UNIT\n");
+			tunerunithdr =
+				(struct tmComResTunerDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, "  unitid = 0x%x\n",
+				tunerunithdr->unitid);
+			dprintk(DBGLVL_API, "  sourceid = 0x%x\n",
+				tunerunithdr->sourceid);
+			dprintk(DBGLVL_API, "  iunit = 0x%x\n",
+				tunerunithdr->iunit);
+			dprintk(DBGLVL_API, "  tuningstandards = 0x%x\n",
+				tunerunithdr->tuningstandards);
+			dprintk(DBGLVL_API, "  controlsize = 0x%x\n",
+				tunerunithdr->controlsize);
+			dprintk(DBGLVL_API, "  controls = 0x%x\n",
+				tunerunithdr->controls);
+
+			if (tunerunithdr->unitid == tunerunithdr->iunit) {
+				if (currpath == 1)
+					encport = &dev->ports[SAA7164_PORT_ENC1];
+				else
+					encport = &dev->ports[SAA7164_PORT_ENC2];
+				memcpy(&encport->tunerunit, tunerunithdr,
+					sizeof(struct tmComResTunerDescrHeader));
+				dprintk(DBGLVL_API,
+					"  (becomes dev->enc[%d] tuner)\n",
+					encport->nr);
+			}
+			break;
+		case VC_SELECTOR_UNIT:
+			psel = (struct tmComResSelDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, " VC_SELECTOR_UNIT\n");
+			dprintk(DBGLVL_API, "  unitid = 0x%x\n",
+				psel->unitid);
+			dprintk(DBGLVL_API, "  nrinpins = 0x%x\n",
+				psel->nrinpins);
+			dprintk(DBGLVL_API, "  sourceid = 0x%x\n",
+				psel->sourceid);
+			break;
+		case VC_PROCESSING_UNIT:
+			pdh = (struct tmComResProcDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, " VC_PROCESSING_UNIT\n");
+			dprintk(DBGLVL_API, "  unitid = 0x%x\n",
+				pdh->unitid);
+			dprintk(DBGLVL_API, "  sourceid = 0x%x\n",
+				pdh->sourceid);
+			dprintk(DBGLVL_API, "  controlsize = 0x%x\n",
+				pdh->controlsize);
+			if (pdh->controlsize == 0x04) {
+				if (currpath == 1)
+					encport = &dev->ports[SAA7164_PORT_ENC1];
+				else
+					encport = &dev->ports[SAA7164_PORT_ENC2];
+				memcpy(&encport->vidproc, pdh,
+					sizeof(struct tmComResProcDescrHeader));
+				dprintk(DBGLVL_API, "  (becomes dev->enc[%d])\n",
+					encport->nr);
+			}
+			break;
+		case FEATURE_UNIT:
+			afd = (struct tmComResAFeatureDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, " FEATURE_UNIT\n");
+			dprintk(DBGLVL_API, "  unitid = 0x%x\n",
+				afd->unitid);
+			dprintk(DBGLVL_API, "  sourceid = 0x%x\n",
+				afd->sourceid);
+			dprintk(DBGLVL_API, "  controlsize = 0x%x\n",
+				afd->controlsize);
+			if (currpath == 1)
+				encport = &dev->ports[SAA7164_PORT_ENC1];
+			else
+				encport = &dev->ports[SAA7164_PORT_ENC2];
+			memcpy(&encport->audfeat, afd,
+				sizeof(struct tmComResAFeatureDescrHeader));
+			dprintk(DBGLVL_API, "  (becomes dev->enc[%d])\n",
+				encport->nr);
+			break;
+		case ENCODER_UNIT:
+			edh = (struct tmComResEncoderDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, " ENCODER_UNIT\n");
+			dprintk(DBGLVL_API, "  subtype = 0x%x\n", edh->subtype);
+			dprintk(DBGLVL_API, "  unitid = 0x%x\n", edh->unitid);
+			dprintk(DBGLVL_API, "  vsourceid = 0x%x\n",
+			edh->vsourceid);
+			dprintk(DBGLVL_API, "  asourceid = 0x%x\n",
+				edh->asourceid);
+			dprintk(DBGLVL_API, "  iunit = 0x%x\n", edh->iunit);
+			if (edh->iunit == edh->unitid) {
+				if (currpath == 1)
+					encport = &dev->ports[SAA7164_PORT_ENC1];
+				else
+					encport = &dev->ports[SAA7164_PORT_ENC2];
+				memcpy(&encport->encunit, edh,
+					sizeof(struct tmComResEncoderDescrHeader));
+				dprintk(DBGLVL_API,
+					"  (becomes dev->enc[%d])\n",
+					encport->nr);
+			}
+			break;
+		case EXTENSION_UNIT:
+			dprintk(DBGLVL_API, " EXTENSION_UNIT\n");
+			exthdr = (struct tmComResExtDevDescrHeader *)(buf + idx);
+			dprintk(DBGLVL_API, "  unitid = 0x%x\n",
+				exthdr->unitid);
+			dprintk(DBGLVL_API, "  deviceid = 0x%x\n",
+				exthdr->deviceid);
+			dprintk(DBGLVL_API, "  devicetype = 0x%x\n",
+				exthdr->devicetype);
+			if (exthdr->devicetype & 0x1)
+				dprintk(DBGLVL_API, "   = Decoder Device\n");
+			if (exthdr->devicetype & 0x2)
+				dprintk(DBGLVL_API, "   = GPIO Source\n");
+			if (exthdr->devicetype & 0x4)
+				dprintk(DBGLVL_API, "   = Video Decoder\n");
+			if (exthdr->devicetype & 0x8)
+				dprintk(DBGLVL_API, "   = Audio Decoder\n");
+			if (exthdr->devicetype & 0x20)
+				dprintk(DBGLVL_API, "   = Crossbar\n");
+			if (exthdr->devicetype & 0x40)
+				dprintk(DBGLVL_API, "   = Tuner\n");
+			if (exthdr->devicetype & 0x80)
+				dprintk(DBGLVL_API, "   = IF PLL\n");
+			if (exthdr->devicetype & 0x100)
+				dprintk(DBGLVL_API, "   = Demodulator\n");
+			if (exthdr->devicetype & 0x200)
+				dprintk(DBGLVL_API, "   = RDS Decoder\n");
+			if (exthdr->devicetype & 0x400)
+				dprintk(DBGLVL_API, "   = Encoder\n");
+			if (exthdr->devicetype & 0x800)
+				dprintk(DBGLVL_API, "   = IR Decoder\n");
+			if (exthdr->devicetype & 0x1000)
+				dprintk(DBGLVL_API, "   = EEPROM\n");
+			if (exthdr->devicetype & 0x2000)
+				dprintk(DBGLVL_API,
+					"   = VBI Decoder\n");
+			if (exthdr->devicetype & 0x10000)
+				dprintk(DBGLVL_API,
+					"   = Streaming Device\n");
+			if (exthdr->devicetype & 0x20000)
+				dprintk(DBGLVL_API,
+					"   = DRM Device\n");
+			if (exthdr->devicetype & 0x40000000)
+				dprintk(DBGLVL_API,
+					"   = Generic Device\n");
+			if (exthdr->devicetype & 0x80000000)
+				dprintk(DBGLVL_API,
+					"   = Config Space Device\n");
+			dprintk(DBGLVL_API, "  numgpiopins = 0x%x\n",
+				exthdr->numgpiopins);
+			dprintk(DBGLVL_API, "  numgpiogroups = 0x%x\n",
+				exthdr->numgpiogroups);
+			dprintk(DBGLVL_API, "  controlsize = 0x%x\n",
+				exthdr->controlsize);
+			if (exthdr->devicetype & 0x80) {
+				if (currpath == 1)
+					encport = &dev->ports[SAA7164_PORT_ENC1];
+				else
+					encport = &dev->ports[SAA7164_PORT_ENC2];
+				memcpy(&encport->ifunit, exthdr,
+					sizeof(struct tmComResExtDevDescrHeader));
+				dprintk(DBGLVL_API,
+					"  (becomes dev->enc[%d])\n",
+					encport->nr);
+			}
+			break;
+		case PVC_INFRARED_UNIT:
+			dprintk(DBGLVL_API, " PVC_INFRARED_UNIT\n");
+			break;
+		case DRM_UNIT:
+			dprintk(DBGLVL_API, " DRM_UNIT\n");
+			break;
+		default:
+			dprintk(DBGLVL_API, "default %d\n", hdr->subtype);
+		}
+
+		dprintk(DBGLVL_API, " 1.%x\n", hdr->len);
+		dprintk(DBGLVL_API, " 2.%x\n", hdr->type);
+		dprintk(DBGLVL_API, " 3.%x\n", hdr->subtype);
+		dprintk(DBGLVL_API, " 4.%x\n", hdr->unitid);
+
+		idx += hdr->len;
+	}
+
+	return 0;
+}
+
+int saa7164_api_enum_subdevs(struct saa7164_dev *dev)
+{
+	int ret;
+	u32 buflen = 0;
+	u8 *buf;
+
+	dprintk(DBGLVL_API, "%s()\n", __func__);
+
+	/* Get the total descriptor length */
+	ret = saa7164_cmd_send(dev, 0, GET_LEN,
+		GET_DESCRIPTORS_CONTROL, sizeof(buflen), &buflen);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+
+	dprintk(DBGLVL_API, "%s() total descriptor size = %d bytes.\n",
+		__func__, buflen);
+
+	/* Allocate enough storage for all of the descs */
+	buf = kzalloc(buflen, GFP_KERNEL);
+	if (!buf)
+		return SAA_ERR_NO_RESOURCES;
+
+	/* Retrieve them */
+	ret = saa7164_cmd_send(dev, 0, GET_CUR,
+		GET_DESCRIPTORS_CONTROL, buflen, buf);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret);
+		goto out;
+	}
+
+	if (saa_debug & DBGLVL_API)
+		print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf,
+			       buflen & ~15, false);
+
+	saa7164_api_dump_subdevs(dev, buf, buflen);
+
+out:
+	kfree(buf);
+	return ret;
+}
+
+int saa7164_api_i2c_read(struct saa7164_i2c *bus, u8 addr, u32 reglen, u8 *reg,
+	u32 datalen, u8 *data)
+{
+	struct saa7164_dev *dev = bus->dev;
+	u16 len = 0;
+	int unitid;
+	u8 buf[256];
+	int ret;
+
+	dprintk(DBGLVL_API, "%s() addr=%x reglen=%d datalen=%d\n",
+		__func__, addr, reglen, datalen);
+
+	if (reglen > 4)
+		return -EIO;
+
+	/* Prepare the send buffer */
+	/* Bytes 00-03 source register length
+	 *       04-07 source bytes to read
+	 *       08... register address
+	 */
+	memset(buf, 0, sizeof(buf));
+	memcpy((buf + 2 * sizeof(u32) + 0), reg, reglen);
+	*((u32 *)(buf + 0 * sizeof(u32))) = reglen;
+	*((u32 *)(buf + 1 * sizeof(u32))) = datalen;
+
+	unitid = saa7164_i2caddr_to_unitid(bus, addr);
+	if (unitid < 0) {
+		printk(KERN_ERR
+			"%s() error, cannot translate regaddr 0x%x to unitid\n",
+			__func__, addr);
+		return -EIO;
+	}
+
+	ret = saa7164_cmd_send(bus->dev, unitid, GET_LEN,
+		EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret);
+		return -EIO;
+	}
+
+	dprintk(DBGLVL_API, "%s() len = %d bytes\n", __func__, len);
+
+	if (saa_debug & DBGLVL_I2C)
+		print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf,
+			       32, false);
+
+	ret = saa7164_cmd_send(bus->dev, unitid, GET_CUR,
+		EXU_REGISTER_ACCESS_CONTROL, len, &buf);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret);
+	else {
+		if (saa_debug & DBGLVL_I2C)
+			print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
+				       buf, sizeof(buf), false);
+		memcpy(data, (buf + 2 * sizeof(u32) + reglen), datalen);
+	}
+
+	return ret == SAA_OK ? 0 : -EIO;
+}
+
+/* For a given 8 bit i2c address device, write the buffer */
+int saa7164_api_i2c_write(struct saa7164_i2c *bus, u8 addr, u32 datalen,
+	u8 *data)
+{
+	struct saa7164_dev *dev = bus->dev;
+	u16 len = 0;
+	int unitid;
+	int reglen;
+	u8 buf[256];
+	int ret;
+
+	dprintk(DBGLVL_API, "%s() addr=0x%2x len=0x%x\n",
+		__func__, addr, datalen);
+
+	if ((datalen == 0) || (datalen > 232))
+		return -EIO;
+
+	memset(buf, 0, sizeof(buf));
+
+	unitid = saa7164_i2caddr_to_unitid(bus, addr);
+	if (unitid < 0) {
+		printk(KERN_ERR
+			"%s() error, cannot translate regaddr 0x%x to unitid\n",
+			__func__, addr);
+		return -EIO;
+	}
+
+	reglen = saa7164_i2caddr_to_reglen(bus, addr);
+	if (reglen < 0) {
+		printk(KERN_ERR
+			"%s() error, cannot translate regaddr to reglen\n",
+			__func__);
+		return -EIO;
+	}
+
+	ret = saa7164_cmd_send(bus->dev, unitid, GET_LEN,
+		EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret);
+		return -EIO;
+	}
+
+	dprintk(DBGLVL_API, "%s() len = %d bytes unitid=0x%x\n", __func__,
+		len, unitid);
+
+	/* Prepare the send buffer */
+	/* Bytes 00-03 dest register length
+	 *       04-07 dest bytes to write
+	 *       08... register address
+	 */
+	*((u32 *)(buf + 0 * sizeof(u32))) = reglen;
+	*((u32 *)(buf + 1 * sizeof(u32))) = datalen - reglen;
+	memcpy((buf + 2 * sizeof(u32)), data, datalen);
+
+	if (saa_debug & DBGLVL_I2C)
+		print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
+			       buf, sizeof(buf), false);
+
+	ret = saa7164_cmd_send(bus->dev, unitid, SET_CUR,
+		EXU_REGISTER_ACCESS_CONTROL, len, &buf);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret);
+
+	return ret == SAA_OK ? 0 : -EIO;
+}
+
+static int saa7164_api_modify_gpio(struct saa7164_dev *dev, u8 unitid,
+	u8 pin, u8 state)
+{
+	int ret;
+	struct tmComResGPIO t;
+
+	dprintk(DBGLVL_API, "%s(0x%x, %d, %d)\n",
+		__func__, unitid, pin, state);
+
+	if ((pin > 7) || (state > 2))
+		return SAA_ERR_BAD_PARAMETER;
+
+	t.pin = pin;
+	t.state = state;
+
+	ret = saa7164_cmd_send(dev, unitid, SET_CUR,
+		EXU_GPIO_CONTROL, sizeof(t), &t);
+	if (ret != SAA_OK)
+		printk(KERN_ERR "%s() error, ret = 0x%x\n",
+			__func__, ret);
+
+	return ret;
+}
+
+int saa7164_api_set_gpiobit(struct saa7164_dev *dev, u8 unitid,
+	u8 pin)
+{
+	return saa7164_api_modify_gpio(dev, unitid, pin, 1);
+}
+
+int saa7164_api_clear_gpiobit(struct saa7164_dev *dev, u8 unitid,
+	u8 pin)
+{
+	return saa7164_api_modify_gpio(dev, unitid, pin, 0);
+}
+
diff --git a/drivers/media/pci/saa7164/saa7164-buffer.c b/drivers/media/pci/saa7164/saa7164-buffer.c
new file mode 100644
index 0000000..f30758e
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-buffer.c
@@ -0,0 +1,322 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/slab.h>
+
+#include "saa7164.h"
+
+/* The PCI address space for buffer handling looks like this:
+ *
+ * +-u32 wide-------------+
+ * |                      +
+ * +-u64 wide------------------------------------+
+ * +                                             +
+ * +----------------------+
+ * | CurrentBufferPtr     + Pointer to current PCI buffer >-+
+ * +----------------------+                                 |
+ * | Unused               +                                 |
+ * +----------------------+                                 |
+ * | Pitch                + = 188 (bytes)                   |
+ * +----------------------+                                 |
+ * | PCI buffer size      + = pitch * number of lines (312) |
+ * +----------------------+                                 |
+ * |0| Buf0 Write Offset  +                                 |
+ * +----------------------+                                 v
+ * |1| Buf1 Write Offset  +                                 |
+ * +----------------------+                                 |
+ * |2| Buf2 Write Offset  +                                 |
+ * +----------------------+                                 |
+ * |3| Buf3 Write Offset  +                                 |
+ * +----------------------+                                 |
+ * ... More write offsets                                   |
+ * +---------------------------------------------+          |
+ * +0| set of ptrs to PCI pagetables             +          |
+ * +---------------------------------------------+          |
+ * +1| set of ptrs to PCI pagetables             + <--------+
+ * +---------------------------------------------+
+ * +2| set of ptrs to PCI pagetables             +
+ * +---------------------------------------------+
+ * +3| set of ptrs to PCI pagetables             + >--+
+ * +---------------------------------------------+    |
+ * ... More buffer pointers                           |  +----------------+
+ *						    +->| pt[0] TS data  |
+ *						    |  +----------------+
+ *						    |
+ *						    |  +----------------+
+ *						    +->| pt[1] TS data  |
+ *						    |  +----------------+
+ *						    | etc
+ */
+
+void saa7164_buffer_display(struct saa7164_buffer *buf)
+{
+	struct saa7164_dev *dev = buf->port->dev;
+	int i;
+
+	dprintk(DBGLVL_BUF, "%s()   buffer @ 0x%p nr=%d\n",
+		__func__, buf, buf->idx);
+	dprintk(DBGLVL_BUF, "  pci_cpu @ 0x%p    dma @ 0x%08llx len = 0x%x\n",
+		buf->cpu, (long long)buf->dma, buf->pci_size);
+	dprintk(DBGLVL_BUF, "   pt_cpu @ 0x%p pt_dma @ 0x%08llx len = 0x%x\n",
+		buf->pt_cpu, (long long)buf->pt_dma, buf->pt_size);
+
+	/* Format the Page Table Entries to point into the data buffer */
+	for (i = 0 ; i < SAA7164_PT_ENTRIES; i++) {
+
+		dprintk(DBGLVL_BUF, "    pt[%02d] = 0x%p -> 0x%llx\n",
+			i, buf->pt_cpu, (u64)*(buf->pt_cpu));
+
+	}
+}
+/* Allocate a new buffer structure and associated PCI space in bytes.
+ * len must be a multiple of sizeof(u64)
+ */
+struct saa7164_buffer *saa7164_buffer_alloc(struct saa7164_port *port,
+	u32 len)
+{
+	struct tmHWStreamParameters *params = &port->hw_streamingparams;
+	struct saa7164_buffer *buf = NULL;
+	struct saa7164_dev *dev = port->dev;
+	int i;
+
+	if ((len == 0) || (len >= 65536) || (len % sizeof(u64))) {
+		log_warn("%s() SAA_ERR_BAD_PARAMETER\n", __func__);
+		goto ret;
+	}
+
+	buf = kzalloc(sizeof(struct saa7164_buffer), GFP_KERNEL);
+	if (!buf) {
+		log_warn("%s() SAA_ERR_NO_RESOURCES\n", __func__);
+		goto ret;
+	}
+
+	buf->idx = -1;
+	buf->port = port;
+	buf->flags = SAA7164_BUFFER_FREE;
+	buf->pos = 0;
+	buf->actual_size = params->pitch * params->numberoflines;
+	buf->crc = 0;
+	/* TODO: arg len is being ignored */
+	buf->pci_size = SAA7164_PT_ENTRIES * 0x1000;
+	buf->pt_size = (SAA7164_PT_ENTRIES * sizeof(u64)) + 0x1000;
+
+	/* Allocate contiguous memory */
+	buf->cpu = pci_alloc_consistent(port->dev->pci, buf->pci_size,
+		&buf->dma);
+	if (!buf->cpu)
+		goto fail1;
+
+	buf->pt_cpu = pci_alloc_consistent(port->dev->pci, buf->pt_size,
+		&buf->pt_dma);
+	if (!buf->pt_cpu)
+		goto fail2;
+
+	/* init the buffers to a known pattern, easier during debugging */
+	memset(buf->cpu, 0xff, buf->pci_size);
+	buf->crc = crc32(0, buf->cpu, buf->actual_size);
+	memset(buf->pt_cpu, 0xff, buf->pt_size);
+
+	dprintk(DBGLVL_BUF, "%s()   allocated buffer @ 0x%p (%d pageptrs)\n",
+		__func__, buf, params->numpagetables);
+	dprintk(DBGLVL_BUF, "  pci_cpu @ 0x%p    dma @ 0x%08lx len = 0x%x\n",
+		buf->cpu, (long)buf->dma, buf->pci_size);
+	dprintk(DBGLVL_BUF, "   pt_cpu @ 0x%p pt_dma @ 0x%08lx len = 0x%x\n",
+		buf->pt_cpu, (long)buf->pt_dma, buf->pt_size);
+
+	/* Format the Page Table Entries to point into the data buffer */
+	for (i = 0 ; i < params->numpagetables; i++) {
+
+		*(buf->pt_cpu + i) = buf->dma + (i * 0x1000); /* TODO */
+		dprintk(DBGLVL_BUF, "    pt[%02d] = 0x%p -> 0x%llx\n",
+			i, buf->pt_cpu, (u64)*(buf->pt_cpu));
+
+	}
+
+	goto ret;
+
+fail2:
+	pci_free_consistent(port->dev->pci, buf->pci_size, buf->cpu, buf->dma);
+fail1:
+	kfree(buf);
+
+	buf = NULL;
+ret:
+	return buf;
+}
+
+int saa7164_buffer_dealloc(struct saa7164_buffer *buf)
+{
+	struct saa7164_dev *dev;
+
+	if (!buf || !buf->port)
+		return SAA_ERR_BAD_PARAMETER;
+	dev = buf->port->dev;
+
+	dprintk(DBGLVL_BUF, "%s() deallocating buffer @ 0x%p\n",
+		__func__, buf);
+
+	if (buf->flags != SAA7164_BUFFER_FREE)
+		log_warn(" freeing a non-free buffer\n");
+
+	pci_free_consistent(dev->pci, buf->pci_size, buf->cpu, buf->dma);
+	pci_free_consistent(dev->pci, buf->pt_size, buf->pt_cpu, buf->pt_dma);
+
+	kfree(buf);
+
+	return SAA_OK;
+}
+
+int saa7164_buffer_zero_offsets(struct saa7164_port *port, int i)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	if ((i < 0) || (i >= port->hwcfg.buffercount))
+		return -EINVAL;
+
+	dprintk(DBGLVL_BUF, "%s(idx = %d)\n", __func__, i);
+
+	saa7164_writel(port->bufoffset + (sizeof(u32) * i), 0);
+
+	return 0;
+}
+
+/* Write a buffer into the hardware */
+int saa7164_buffer_activate(struct saa7164_buffer *buf, int i)
+{
+	struct saa7164_port *port = buf->port;
+	struct saa7164_dev *dev = port->dev;
+
+	if ((i < 0) || (i >= port->hwcfg.buffercount))
+		return -EINVAL;
+
+	dprintk(DBGLVL_BUF, "%s(idx = %d)\n", __func__, i);
+
+	buf->idx = i; /* Note of which buffer list index position we occupy */
+	buf->flags = SAA7164_BUFFER_BUSY;
+	buf->pos = 0;
+
+	/* TODO: Review this in light of 32v64 assignments */
+	saa7164_writel(port->bufoffset + (sizeof(u32) * i), 0);
+	saa7164_writel(port->bufptr32h + ((sizeof(u32) * 2) * i), buf->pt_dma);
+	saa7164_writel(port->bufptr32l + ((sizeof(u32) * 2) * i), 0);
+
+	dprintk(DBGLVL_BUF, "   buf[%d] offset 0x%llx (0x%x) "
+		"buf 0x%llx/%llx (0x%x/%x) nr=%d\n",
+		buf->idx,
+		(u64)port->bufoffset + (i * sizeof(u32)),
+		saa7164_readl(port->bufoffset + (sizeof(u32) * i)),
+		(u64)port->bufptr32h + ((sizeof(u32) * 2) * i),
+		(u64)port->bufptr32l + ((sizeof(u32) * 2) * i),
+		saa7164_readl(port->bufptr32h + ((sizeof(u32) * i) * 2)),
+		saa7164_readl(port->bufptr32l + ((sizeof(u32) * i) * 2)),
+		buf->idx);
+
+	return 0;
+}
+
+int saa7164_buffer_cfg_port(struct saa7164_port *port)
+{
+	struct tmHWStreamParameters *params = &port->hw_streamingparams;
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct list_head *c, *n;
+	int i = 0;
+
+	dprintk(DBGLVL_BUF, "%s(port=%d)\n", __func__, port->nr);
+
+	saa7164_writel(port->bufcounter, 0);
+	saa7164_writel(port->pitch, params->pitch);
+	saa7164_writel(port->bufsize, params->pitch * params->numberoflines);
+
+	dprintk(DBGLVL_BUF, " configured:\n");
+	dprintk(DBGLVL_BUF, "   lmmio       0x%p\n", dev->lmmio);
+	dprintk(DBGLVL_BUF, "   bufcounter  0x%x = 0x%x\n", port->bufcounter,
+		saa7164_readl(port->bufcounter));
+
+	dprintk(DBGLVL_BUF, "   pitch       0x%x = %d\n", port->pitch,
+		saa7164_readl(port->pitch));
+
+	dprintk(DBGLVL_BUF, "   bufsize     0x%x = %d\n", port->bufsize,
+		saa7164_readl(port->bufsize));
+
+	dprintk(DBGLVL_BUF, "   buffercount = %d\n", port->hwcfg.buffercount);
+	dprintk(DBGLVL_BUF, "   bufoffset = 0x%x\n", port->bufoffset);
+	dprintk(DBGLVL_BUF, "   bufptr32h = 0x%x\n", port->bufptr32h);
+	dprintk(DBGLVL_BUF, "   bufptr32l = 0x%x\n", port->bufptr32l);
+
+	/* Poke the buffers and offsets into PCI space */
+	mutex_lock(&port->dmaqueue_lock);
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+		buf = list_entry(c, struct saa7164_buffer, list);
+
+		if (buf->flags != SAA7164_BUFFER_FREE)
+			BUG();
+
+		/* Place the buffer in the h/w queue */
+		saa7164_buffer_activate(buf, i);
+
+		/* Don't exceed the device maximum # bufs */
+		if (i++ > port->hwcfg.buffercount)
+			BUG();
+
+	}
+	mutex_unlock(&port->dmaqueue_lock);
+
+	return 0;
+}
+
+struct saa7164_user_buffer *saa7164_buffer_alloc_user(struct saa7164_dev *dev,
+	u32 len)
+{
+	struct saa7164_user_buffer *buf;
+
+	buf = kzalloc(sizeof(struct saa7164_user_buffer), GFP_KERNEL);
+	if (!buf)
+		return NULL;
+
+	buf->data = kzalloc(len, GFP_KERNEL);
+
+	if (!buf->data) {
+		kfree(buf);
+		return NULL;
+	}
+
+	buf->actual_size = len;
+	buf->pos = 0;
+	buf->crc = 0;
+
+	dprintk(DBGLVL_BUF, "%s()   allocated user buffer @ 0x%p\n",
+		__func__, buf);
+
+	return buf;
+}
+
+void saa7164_buffer_dealloc_user(struct saa7164_user_buffer *buf)
+{
+	if (!buf)
+		return;
+
+	kfree(buf->data);
+	buf->data = NULL;
+
+	kfree(buf);
+}
+
diff --git a/drivers/media/pci/saa7164/saa7164-bus.c b/drivers/media/pci/saa7164/saa7164-bus.c
new file mode 100644
index 0000000..b4857cd
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-bus.c
@@ -0,0 +1,481 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "saa7164.h"
+
+/* The message bus to/from the firmware is a ring buffer in PCI address
+ * space. Establish the defaults.
+ */
+int saa7164_bus_setup(struct saa7164_dev *dev)
+{
+	struct tmComResBusInfo *b	= &dev->bus;
+
+	mutex_init(&b->lock);
+
+	b->Type			= TYPE_BUS_PCIe;
+	b->m_wMaxReqSize	= SAA_DEVICE_MAXREQUESTSIZE;
+
+	b->m_pdwSetRing		= (u8 __iomem *)(dev->bmmio +
+		((u32)dev->busdesc.CommandRing));
+
+	b->m_dwSizeSetRing	= SAA_DEVICE_BUFFERBLOCKSIZE;
+
+	b->m_pdwGetRing		= (u8 __iomem *)(dev->bmmio +
+		((u32)dev->busdesc.ResponseRing));
+
+	b->m_dwSizeGetRing	= SAA_DEVICE_BUFFERBLOCKSIZE;
+
+	b->m_dwSetWritePos	= ((u32)dev->intfdesc.BARLocation) +
+		(2 * sizeof(u64));
+	b->m_dwSetReadPos	= b->m_dwSetWritePos + (1 * sizeof(u32));
+
+	b->m_dwGetWritePos	= b->m_dwSetWritePos + (2 * sizeof(u32));
+	b->m_dwGetReadPos	= b->m_dwSetWritePos + (3 * sizeof(u32));
+
+	return 0;
+}
+
+void saa7164_bus_dump(struct saa7164_dev *dev)
+{
+	struct tmComResBusInfo *b = &dev->bus;
+
+	dprintk(DBGLVL_BUS, "Dumping the bus structure:\n");
+	dprintk(DBGLVL_BUS, " .type             = %d\n", b->Type);
+	dprintk(DBGLVL_BUS, " .dev->bmmio       = 0x%p\n", dev->bmmio);
+	dprintk(DBGLVL_BUS, " .m_wMaxReqSize    = 0x%x\n", b->m_wMaxReqSize);
+	dprintk(DBGLVL_BUS, " .m_pdwSetRing     = 0x%p\n", b->m_pdwSetRing);
+	dprintk(DBGLVL_BUS, " .m_dwSizeSetRing  = 0x%x\n", b->m_dwSizeSetRing);
+	dprintk(DBGLVL_BUS, " .m_pdwGetRing     = 0x%p\n", b->m_pdwGetRing);
+	dprintk(DBGLVL_BUS, " .m_dwSizeGetRing  = 0x%x\n", b->m_dwSizeGetRing);
+
+	dprintk(DBGLVL_BUS, " .m_dwSetReadPos   = 0x%x (0x%08x)\n",
+		b->m_dwSetReadPos, saa7164_readl(b->m_dwSetReadPos));
+
+	dprintk(DBGLVL_BUS, " .m_dwSetWritePos  = 0x%x (0x%08x)\n",
+		b->m_dwSetWritePos, saa7164_readl(b->m_dwSetWritePos));
+
+	dprintk(DBGLVL_BUS, " .m_dwGetReadPos   = 0x%x (0x%08x)\n",
+		b->m_dwGetReadPos, saa7164_readl(b->m_dwGetReadPos));
+
+	dprintk(DBGLVL_BUS, " .m_dwGetWritePos  = 0x%x (0x%08x)\n",
+		b->m_dwGetWritePos, saa7164_readl(b->m_dwGetWritePos));
+
+}
+
+/* Intensionally throw a BUG() if the state of the message bus looks corrupt */
+static void saa7164_bus_verify(struct saa7164_dev *dev)
+{
+	struct tmComResBusInfo *b = &dev->bus;
+	int bug = 0;
+
+	if (saa7164_readl(b->m_dwSetReadPos) > b->m_dwSizeSetRing)
+		bug++;
+
+	if (saa7164_readl(b->m_dwSetWritePos) > b->m_dwSizeSetRing)
+		bug++;
+
+	if (saa7164_readl(b->m_dwGetReadPos) > b->m_dwSizeGetRing)
+		bug++;
+
+	if (saa7164_readl(b->m_dwGetWritePos) > b->m_dwSizeGetRing)
+		bug++;
+
+	if (bug) {
+		saa_debug = 0xffff; /* Ensure we get the bus dump */
+		saa7164_bus_dump(dev);
+		saa_debug = 1024; /* Ensure we get the bus dump */
+		BUG();
+	}
+}
+
+static void saa7164_bus_dumpmsg(struct saa7164_dev *dev, struct tmComResInfo *m,
+				void *buf)
+{
+	dprintk(DBGLVL_BUS, "Dumping msg structure:\n");
+	dprintk(DBGLVL_BUS, " .id               = %d\n",   m->id);
+	dprintk(DBGLVL_BUS, " .flags            = 0x%x\n", m->flags);
+	dprintk(DBGLVL_BUS, " .size             = 0x%x\n", m->size);
+	dprintk(DBGLVL_BUS, " .command          = 0x%x\n", m->command);
+	dprintk(DBGLVL_BUS, " .controlselector  = 0x%x\n", m->controlselector);
+	dprintk(DBGLVL_BUS, " .seqno            = %d\n",   m->seqno);
+	if (buf)
+		dprintk(DBGLVL_BUS, " .buffer (ignored)\n");
+}
+
+/*
+ * Places a command or a response on the bus. The implementation does not
+ * know if it is a command or a response it just places the data on the
+ * bus depending on the bus information given in the struct tmComResBusInfo
+ * structure. If the command or response does not fit into the bus ring
+ * buffer it will be refused.
+ *
+ * Return Value:
+ *  SAA_OK     The function executed successfully.
+ *  < 0        One or more members are not initialized.
+ */
+int saa7164_bus_set(struct saa7164_dev *dev, struct tmComResInfo* msg,
+	void *buf)
+{
+	struct tmComResBusInfo *bus = &dev->bus;
+	u32 bytes_to_write, free_write_space, timeout, curr_srp, curr_swp;
+	u32 new_swp, space_rem;
+	int ret = SAA_ERR_BAD_PARAMETER;
+	u16 size;
+
+	if (!msg) {
+		printk(KERN_ERR "%s() !msg\n", __func__);
+		return SAA_ERR_BAD_PARAMETER;
+	}
+
+	dprintk(DBGLVL_BUS, "%s()\n", __func__);
+
+	saa7164_bus_verify(dev);
+
+	if (msg->size > dev->bus.m_wMaxReqSize) {
+		printk(KERN_ERR "%s() Exceeded dev->bus.m_wMaxReqSize\n",
+			__func__);
+		return SAA_ERR_BAD_PARAMETER;
+	}
+
+	if ((msg->size > 0) && (buf == NULL)) {
+		printk(KERN_ERR "%s() Missing message buffer\n", __func__);
+		return SAA_ERR_BAD_PARAMETER;
+	}
+
+	/* Lock the bus from any other access */
+	mutex_lock(&bus->lock);
+
+	bytes_to_write = sizeof(*msg) + msg->size;
+	free_write_space = 0;
+	timeout = SAA_BUS_TIMEOUT;
+	curr_srp = saa7164_readl(bus->m_dwSetReadPos);
+	curr_swp = saa7164_readl(bus->m_dwSetWritePos);
+
+	/* Deal with ring wrapping issues */
+	if (curr_srp > curr_swp)
+		/* Deal with the wrapped ring */
+		free_write_space = curr_srp - curr_swp;
+	else
+		/* The ring has not wrapped yet */
+		free_write_space = (curr_srp + bus->m_dwSizeSetRing) - curr_swp;
+
+	dprintk(DBGLVL_BUS, "%s() bytes_to_write = %d\n", __func__,
+		bytes_to_write);
+
+	dprintk(DBGLVL_BUS, "%s() free_write_space = %d\n", __func__,
+		free_write_space);
+
+	dprintk(DBGLVL_BUS, "%s() curr_srp = %x\n", __func__, curr_srp);
+	dprintk(DBGLVL_BUS, "%s() curr_swp = %x\n", __func__, curr_swp);
+
+	/* Process the msg and write the content onto the bus */
+	while (bytes_to_write >= free_write_space) {
+
+		if (timeout-- == 0) {
+			printk(KERN_ERR "%s() bus timeout\n", __func__);
+			ret = SAA_ERR_NO_RESOURCES;
+			goto out;
+		}
+
+		/* TODO: Review this delay, efficient? */
+		/* Wait, allowing the hardware fetch time */
+		mdelay(1);
+
+		/* Check the space usage again */
+		curr_srp = saa7164_readl(bus->m_dwSetReadPos);
+
+		/* Deal with ring wrapping issues */
+		if (curr_srp > curr_swp)
+			/* Deal with the wrapped ring */
+			free_write_space = curr_srp - curr_swp;
+		else
+			/* Read didn't wrap around the buffer */
+			free_write_space = (curr_srp + bus->m_dwSizeSetRing) -
+				curr_swp;
+
+	}
+
+	/* Calculate the new write position */
+	new_swp = curr_swp + bytes_to_write;
+
+	dprintk(DBGLVL_BUS, "%s() new_swp = %x\n", __func__, new_swp);
+	dprintk(DBGLVL_BUS, "%s() bus->m_dwSizeSetRing = %x\n", __func__,
+		bus->m_dwSizeSetRing);
+
+	/*
+	 * Make a copy of msg->size before it is converted to le16 since it is
+	 * used in the code below.
+	 */
+	size = msg->size;
+	/* Convert to le16/le32 */
+	msg->size = (__force u16)cpu_to_le16(msg->size);
+	msg->command = (__force u32)cpu_to_le32(msg->command);
+	msg->controlselector = (__force u16)cpu_to_le16(msg->controlselector);
+
+	/* Mental Note: line 462 tmmhComResBusPCIe.cpp */
+
+	/* Check if we're going to wrap again */
+	if (new_swp > bus->m_dwSizeSetRing) {
+
+		/* Ring wraps */
+		new_swp -= bus->m_dwSizeSetRing;
+
+		space_rem = bus->m_dwSizeSetRing - curr_swp;
+
+		dprintk(DBGLVL_BUS, "%s() space_rem = %x\n", __func__,
+			space_rem);
+
+		dprintk(DBGLVL_BUS, "%s() sizeof(*msg) = %d\n", __func__,
+			(u32)sizeof(*msg));
+
+		if (space_rem < sizeof(*msg)) {
+			dprintk(DBGLVL_BUS, "%s() tr4\n", __func__);
+
+			/* Split the msg into pieces as the ring wraps */
+			memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, space_rem);
+			memcpy_toio(bus->m_pdwSetRing, (u8 *)msg + space_rem,
+				sizeof(*msg) - space_rem);
+
+			memcpy_toio(bus->m_pdwSetRing + sizeof(*msg) - space_rem,
+				buf, size);
+
+		} else if (space_rem == sizeof(*msg)) {
+			dprintk(DBGLVL_BUS, "%s() tr5\n", __func__);
+
+			/* Additional data at the beginning of the ring */
+			memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, sizeof(*msg));
+			memcpy_toio(bus->m_pdwSetRing, buf, size);
+
+		} else {
+			/* Additional data wraps around the ring */
+			memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, sizeof(*msg));
+			if (size > 0) {
+				memcpy_toio(bus->m_pdwSetRing + curr_swp +
+					sizeof(*msg), buf, space_rem -
+					sizeof(*msg));
+				memcpy_toio(bus->m_pdwSetRing, (u8 *)buf +
+					space_rem - sizeof(*msg),
+					bytes_to_write - space_rem);
+			}
+
+		}
+
+	} /* (new_swp > bus->m_dwSizeSetRing) */
+	else {
+		dprintk(DBGLVL_BUS, "%s() tr6\n", __func__);
+
+		/* The ring buffer doesn't wrap, two simple copies */
+		memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, sizeof(*msg));
+		memcpy_toio(bus->m_pdwSetRing + curr_swp + sizeof(*msg), buf,
+			size);
+	}
+
+	dprintk(DBGLVL_BUS, "%s() new_swp = %x\n", __func__, new_swp);
+
+	/* Update the bus write position */
+	saa7164_writel(bus->m_dwSetWritePos, new_swp);
+
+	/* Convert back to cpu after writing the msg to the ringbuffer. */
+	msg->size = le16_to_cpu((__force __le16)msg->size);
+	msg->command = le32_to_cpu((__force __le32)msg->command);
+	msg->controlselector = le16_to_cpu((__force __le16)msg->controlselector);
+	ret = SAA_OK;
+
+out:
+	saa7164_bus_dump(dev);
+	mutex_unlock(&bus->lock);
+	saa7164_bus_verify(dev);
+	return ret;
+}
+
+/*
+ * Receive a command or a response from the bus. The implementation does not
+ * know if it is a command or a response it simply dequeues the data,
+ * depending on the bus information given in the struct tmComResBusInfo
+ * structure.
+ *
+ * Return Value:
+ *  0          The function executed successfully.
+ *  < 0        One or more members are not initialized.
+ */
+int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg,
+	void *buf, int peekonly)
+{
+	struct tmComResBusInfo *bus = &dev->bus;
+	u32 bytes_to_read, write_distance, curr_grp, curr_gwp,
+		new_grp, buf_size, space_rem;
+	struct tmComResInfo msg_tmp;
+	int ret = SAA_ERR_BAD_PARAMETER;
+
+	saa7164_bus_verify(dev);
+
+	if (msg == NULL)
+		return ret;
+
+	if (msg->size > dev->bus.m_wMaxReqSize) {
+		printk(KERN_ERR "%s() Exceeded dev->bus.m_wMaxReqSize\n",
+			__func__);
+		return ret;
+	}
+
+	if ((peekonly == 0) && (msg->size > 0) && (buf == NULL)) {
+		printk(KERN_ERR
+			"%s() Missing msg buf, size should be %d bytes\n",
+			__func__, msg->size);
+		return ret;
+	}
+
+	mutex_lock(&bus->lock);
+
+	/* Peek the bus to see if a msg exists, if it's not what we're expecting
+	 * then return cleanly else read the message from the bus.
+	 */
+	curr_gwp = saa7164_readl(bus->m_dwGetWritePos);
+	curr_grp = saa7164_readl(bus->m_dwGetReadPos);
+
+	if (curr_gwp == curr_grp) {
+		ret = SAA_ERR_EMPTY;
+		goto out;
+	}
+
+	bytes_to_read = sizeof(*msg);
+
+	/* Calculate write distance to current read position */
+	write_distance = 0;
+	if (curr_gwp >= curr_grp)
+		/* Write doesn't wrap around the ring */
+		write_distance = curr_gwp - curr_grp;
+	else
+		/* Write wraps around the ring */
+		write_distance = curr_gwp + bus->m_dwSizeGetRing - curr_grp;
+
+	if (bytes_to_read > write_distance) {
+		printk(KERN_ERR "%s() No message/response found\n", __func__);
+		ret = SAA_ERR_INVALID_COMMAND;
+		goto out;
+	}
+
+	/* Calculate the new read position */
+	new_grp = curr_grp + bytes_to_read;
+	if (new_grp > bus->m_dwSizeGetRing) {
+
+		/* Ring wraps */
+		new_grp -= bus->m_dwSizeGetRing;
+		space_rem = bus->m_dwSizeGetRing - curr_grp;
+
+		memcpy_fromio(&msg_tmp, bus->m_pdwGetRing + curr_grp, space_rem);
+		memcpy_fromio((u8 *)&msg_tmp + space_rem, bus->m_pdwGetRing,
+			bytes_to_read - space_rem);
+
+	} else {
+		/* No wrapping */
+		memcpy_fromio(&msg_tmp, bus->m_pdwGetRing + curr_grp, bytes_to_read);
+	}
+	/* Convert from little endian to CPU */
+	msg_tmp.size = le16_to_cpu((__force __le16)msg_tmp.size);
+	msg_tmp.command = le32_to_cpu((__force __le32)msg_tmp.command);
+	msg_tmp.controlselector = le16_to_cpu((__force __le16)msg_tmp.controlselector);
+	memcpy(msg, &msg_tmp, sizeof(*msg));
+
+	/* No need to update the read positions, because this was a peek */
+	/* If the caller specifically want to peek, return */
+	if (peekonly) {
+		goto peekout;
+	}
+
+	/* Check if the command/response matches what is expected */
+	if ((msg_tmp.id != msg->id) || (msg_tmp.command != msg->command) ||
+		(msg_tmp.controlselector != msg->controlselector) ||
+		(msg_tmp.seqno != msg->seqno) || (msg_tmp.size != msg->size)) {
+
+		printk(KERN_ERR "%s() Unexpected msg miss-match\n", __func__);
+		saa7164_bus_dumpmsg(dev, msg, buf);
+		saa7164_bus_dumpmsg(dev, &msg_tmp, NULL);
+		ret = SAA_ERR_INVALID_COMMAND;
+		goto out;
+	}
+
+	/* Get the actual command and response from the bus */
+	buf_size = msg->size;
+
+	bytes_to_read = sizeof(*msg) + msg->size;
+	/* Calculate write distance to current read position */
+	write_distance = 0;
+	if (curr_gwp >= curr_grp)
+		/* Write doesn't wrap around the ring */
+		write_distance = curr_gwp - curr_grp;
+	else
+		/* Write wraps around the ring */
+		write_distance = curr_gwp + bus->m_dwSizeGetRing - curr_grp;
+
+	if (bytes_to_read > write_distance) {
+		printk(KERN_ERR "%s() Invalid bus state, missing msg "
+			"or mangled ring, faulty H/W / bad code?\n", __func__);
+		ret = SAA_ERR_INVALID_COMMAND;
+		goto out;
+	}
+
+	/* Calculate the new read position */
+	new_grp = curr_grp + bytes_to_read;
+	if (new_grp > bus->m_dwSizeGetRing) {
+
+		/* Ring wraps */
+		new_grp -= bus->m_dwSizeGetRing;
+		space_rem = bus->m_dwSizeGetRing - curr_grp;
+
+		if (space_rem < sizeof(*msg)) {
+			if (buf)
+				memcpy_fromio(buf, bus->m_pdwGetRing + sizeof(*msg) -
+					space_rem, buf_size);
+
+		} else if (space_rem == sizeof(*msg)) {
+			if (buf)
+				memcpy_fromio(buf, bus->m_pdwGetRing, buf_size);
+		} else {
+			/* Additional data wraps around the ring */
+			if (buf) {
+				memcpy_fromio(buf, bus->m_pdwGetRing + curr_grp +
+					sizeof(*msg), space_rem - sizeof(*msg));
+				memcpy_fromio(buf + space_rem - sizeof(*msg),
+					bus->m_pdwGetRing, bytes_to_read -
+					space_rem);
+			}
+
+		}
+
+	} else {
+		/* No wrapping */
+		if (buf)
+			memcpy_fromio(buf, bus->m_pdwGetRing + curr_grp + sizeof(*msg),
+				buf_size);
+	}
+
+	/* Update the read positions, adjusting the ring */
+	saa7164_writel(bus->m_dwGetReadPos, new_grp);
+
+peekout:
+	ret = SAA_OK;
+out:
+	mutex_unlock(&bus->lock);
+	saa7164_bus_verify(dev);
+	return ret;
+}
+
diff --git a/drivers/media/pci/saa7164/saa7164-cards.c b/drivers/media/pci/saa7164/saa7164-cards.c
new file mode 100644
index 0000000..c2b7382
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-cards.c
@@ -0,0 +1,959 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+
+#include "saa7164.h"
+
+/* The Bridge API needs to understand register widths (in bytes) for the
+ * attached I2C devices, so we can simplify the virtual i2c mechansms
+ * and keep the -i2c.c implementation clean.
+ */
+#define REGLEN_0bit	0
+#define REGLEN_8bit	1
+#define REGLEN_16bit	2
+
+struct saa7164_board saa7164_boards[] = {
+	[SAA7164_BOARD_UNKNOWN] = {
+		/* Bridge will not load any firmware, without knowing
+		 * the rev this would be fatal. */
+		.name		= "Unknown",
+	},
+	[SAA7164_BOARD_UNKNOWN_REV2] = {
+		/* Bridge will load the v2 f/w and dump descriptors */
+		/* Required during new board bringup */
+		.name		= "Generic Rev2",
+		.chiprev	= SAA7164_CHIP_REV2,
+	},
+	[SAA7164_BOARD_UNKNOWN_REV3] = {
+		/* Bridge will load the v2 f/w and dump descriptors */
+		/* Required during new board bringup */
+		.name		= "Generic Rev3",
+		.chiprev	= SAA7164_CHIP_REV3,
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2200] = {
+		.name		= "Hauppauge WinTV-HVR2200",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x1d,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1b,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1e,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x10 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1f,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x12 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2200_2] = {
+		.name		= "Hauppauge WinTV-HVR2200",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV2,
+		.unit		= {{
+			.id		= 0x06,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x05,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x10 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1e,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1f,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x12 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2200_3] = {
+		.name		= "Hauppauge WinTV-HVR2200",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV2,
+		.unit		= {{
+			.id		= 0x1d,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x05,
+			.type		= SAA7164_UNIT_ANALOG_DEMODULATOR,
+			.name		= "TDA8290-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x84 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1b,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1c,
+			.type		= SAA7164_UNIT_ANALOG_DEMODULATOR,
+			.name		= "TDA8290-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x84 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1e,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x10 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1f,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x12 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2200_4] = {
+		.name		= "Hauppauge WinTV-HVR2200",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x1d,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x05,
+			.type		= SAA7164_UNIT_ANALOG_DEMODULATOR,
+			.name		= "TDA8290-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x84 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1b,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1c,
+			.type		= SAA7164_UNIT_ANALOG_DEMODULATOR,
+			.name		= "TDA8290-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x84 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1e,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x10 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1f,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x12 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2250] = {
+		.name		= "Hauppauge WinTV-HVR2250",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x22,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x07,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-1 (TOP)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x32 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x08,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-1 (QAM)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x34 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x1e,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x20,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-2 (TOP)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x32 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x23,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-2 (QAM)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x34 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2250_2] = {
+		.name		= "Hauppauge WinTV-HVR2250",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x28,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x07,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-1 (TOP)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x32 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x08,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-1 (QAM)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x34 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x24,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x26,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-2 (TOP)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x32 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x29,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-2 (QAM)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x34 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2250_3] = {
+		.name		= "Hauppauge WinTV-HVR2250",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x26,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x07,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-1 (TOP)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x32 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x08,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-1 (QAM)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x34 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x22,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x24,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-2 (TOP)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x32 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x27,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "CX24228/S5H1411-2 (QAM)",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x34 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2200_5] = {
+		.name		= "Hauppauge WinTV-HVR2200",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x23,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x05,
+			.type		= SAA7164_UNIT_ANALOG_DEMODULATOR,
+			.name		= "TDA8290-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x84 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x21,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "TDA18271-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x22,
+			.type		= SAA7164_UNIT_ANALOG_DEMODULATOR,
+			.name		= "TDA8290-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x84 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x24,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0x10 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x25,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "TDA10048-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x12 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2255proto] = {
+		.name		= "Hauppauge WinTV-HVR2255(proto)",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x27,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "SI2157-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		}, {
+			.id		= 0x06,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "LGDT3306",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xb2 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x24,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "SI2157-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		}, {
+			.id		= 0x26,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "LGDT3306-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x1c >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2255] = {
+		.name		= "Hauppauge WinTV-HVR2255",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x28,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "SI2157-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		}, {
+			.id		= 0x06,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "LGDT3306-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xb2 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x25,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "SI2157-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		}, {
+			.id		= 0x27,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "LGDT3306-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0x1c >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		} },
+	},
+	[SAA7164_BOARD_HAUPPAUGE_HVR2205] = {
+		.name		= "Hauppauge WinTV-HVR2205",
+		.porta		= SAA7164_MPEG_DVB,
+		.portb		= SAA7164_MPEG_DVB,
+		.portc		= SAA7164_MPEG_ENCODER,
+		.portd		= SAA7164_MPEG_ENCODER,
+		.porte		= SAA7164_MPEG_VBI,
+		.portf		= SAA7164_MPEG_VBI,
+		.chiprev	= SAA7164_CHIP_REV3,
+		.unit		= {{
+			.id		= 0x28,
+			.type		= SAA7164_UNIT_EEPROM,
+			.name		= "4K EEPROM",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xa0 >> 1,
+			.i2c_reg_len	= REGLEN_8bit,
+		}, {
+			.id		= 0x04,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "SI2157-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_0,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		}, {
+			.id		= 0x06,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "SI2168-1",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xc8 >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		}, {
+			.id		= 0x25,
+			.type		= SAA7164_UNIT_TUNER,
+			.name		= "SI2157-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_1,
+			.i2c_bus_addr	= 0xc0 >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		}, {
+			.id		= 0x27,
+			.type		= SAA7164_UNIT_DIGITAL_DEMODULATOR,
+			.name		= "SI2168-2",
+			.i2c_bus_nr	= SAA7164_I2C_BUS_2,
+			.i2c_bus_addr	= 0xcc >> 1,
+			.i2c_reg_len	= REGLEN_0bit,
+		} },
+	},
+};
+const unsigned int saa7164_bcount = ARRAY_SIZE(saa7164_boards);
+
+/* ------------------------------------------------------------------ */
+/* PCI subsystem IDs                                                  */
+
+struct saa7164_subid saa7164_subids[] = {
+	{
+		.subvendor = 0x0070,
+		.subdevice = 0x8880,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2250,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8810,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2250,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8980,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2200,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8900,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2200_2,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8901,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2200_3,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x88A1,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2250_3,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8891,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2250_2,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8851,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2250_2,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8940,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2200_4,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0x8953,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2200_5,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0xf111,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2255,
+		/* Prototype card left here for documenation purposes.
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2255proto,
+		*/
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0xf123,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2205,
+	}, {
+		.subvendor = 0x0070,
+		.subdevice = 0xf120,
+		.card      = SAA7164_BOARD_HAUPPAUGE_HVR2205,
+	},
+};
+const unsigned int saa7164_idcount = ARRAY_SIZE(saa7164_subids);
+
+void saa7164_card_list(struct saa7164_dev *dev)
+{
+	int i;
+
+	if (0 == dev->pci->subsystem_vendor &&
+	    0 == dev->pci->subsystem_device) {
+		printk(KERN_ERR
+			"%s: Board has no valid PCIe Subsystem ID and can't\n"
+			"%s: be autodetected. Pass card=<n> insmod option to\n"
+			"%s: workaround that. Send complaints to the vendor\n"
+			"%s: of the TV card. Best regards,\n"
+			"%s:         -- tux\n",
+			dev->name, dev->name, dev->name, dev->name, dev->name);
+	} else {
+		printk(KERN_ERR
+			"%s: Your board isn't known (yet) to the driver.\n"
+			"%s: Try to pick one of the existing card configs via\n"
+			"%s: card=<n> insmod option.  Updating to the latest\n"
+			"%s: version might help as well.\n",
+			dev->name, dev->name, dev->name, dev->name);
+	}
+
+	printk(KERN_ERR "%s: Here are valid choices for the card=<n> insmod "
+		"option:\n", dev->name);
+
+	for (i = 0; i < saa7164_bcount; i++)
+		printk(KERN_ERR "%s:    card=%d -> %s\n",
+		       dev->name, i, saa7164_boards[i].name);
+}
+
+/* TODO: clean this define up into the -cards.c structs */
+#define PCIEBRIDGE_UNITID 2
+
+void saa7164_gpio_setup(struct saa7164_dev *dev)
+{
+	switch (dev->board) {
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_2:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_3:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_4:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_5:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250_2:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250_3:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2255proto:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2255:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2205:
+		/*
+		HVR2200 / HVR2250
+		GPIO 2: s5h1411 / tda10048-1 demod reset
+		GPIO 3: s5h1411 / tda10048-2 demod reset
+		GPIO 7: IRBlaster Zilog reset
+		 */
+
+		/* HVR2255
+		 * GPIO 2: lgdg3306-1 demod reset
+		 * GPIO 3: lgdt3306-2 demod reset
+		 */
+
+		/* HVR2205
+		 * GPIO 2: si2168-1 demod reset
+		 * GPIO 3: si2168-2 demod reset
+		 */
+
+		/* Reset parts by going in and out of reset */
+		saa7164_api_clear_gpiobit(dev, PCIEBRIDGE_UNITID, 2);
+		saa7164_api_clear_gpiobit(dev, PCIEBRIDGE_UNITID, 3);
+
+		msleep(20);
+
+		saa7164_api_set_gpiobit(dev, PCIEBRIDGE_UNITID, 2);
+		saa7164_api_set_gpiobit(dev, PCIEBRIDGE_UNITID, 3);
+		break;
+	}
+}
+
+static void hauppauge_eeprom(struct saa7164_dev *dev, u8 *eeprom_data)
+{
+	struct tveeprom tv;
+
+	/* TODO: Assumption: eeprom on bus 0 */
+	tveeprom_hauppauge_analog(&dev->i2c_bus[0].i2c_client, &tv,
+		eeprom_data);
+
+	/* Make sure we support the board model */
+	switch (tv.model) {
+	case 88001:
+		/* Development board - Limit circulation */
+		/* WinTV-HVR2250 (PCIe, Retail, full-height bracket)
+		 * ATSC/QAM (TDA18271/S5H1411) and basic analog, no IR, FM */
+	case 88021:
+		/* WinTV-HVR2250 (PCIe, Retail, full-height bracket)
+		 * ATSC/QAM (TDA18271/S5H1411) and basic analog, MCE CIR, FM */
+		break;
+	case 88041:
+		/* WinTV-HVR2250 (PCIe, Retail, full-height bracket)
+		 * ATSC/QAM (TDA18271/S5H1411) and basic analog, no IR, FM */
+		break;
+	case 88061:
+		/* WinTV-HVR2250 (PCIe, Retail, full-height bracket)
+		 * ATSC/QAM (TDA18271/S5H1411) and basic analog, FM */
+		break;
+	case 89519:
+	case 89609:
+		/* WinTV-HVR2200 (PCIe, Retail, full-height)
+		 * DVB-T (TDA18271/TDA10048) and basic analog, no IR */
+		break;
+	case 89619:
+		/* WinTV-HVR2200 (PCIe, Retail, half-height)
+		 * DVB-T (TDA18271/TDA10048) and basic analog, no IR */
+		break;
+	case 151009:
+		/* First production board rev B2I6 */
+		/* WinTV-HVR2205 (PCIe, Retail, full-height bracket)
+		 * DVB-T/T2/C (SI2157/SI2168) and basic analog, FM */
+		break;
+	case 151609:
+		/* First production board rev B2I6 */
+		/* WinTV-HVR2205 (PCIe, Retail, half-height bracket)
+		 * DVB-T/T2/C (SI2157/SI2168) and basic analog, FM */
+		break;
+	case 151061:
+		/* First production board rev B1I6 */
+		/* WinTV-HVR2255 (PCIe, Retail, full-height bracket)
+		 * ATSC/QAM (SI2157/LGDT3306) and basic analog, FM */
+		break;
+	default:
+		printk(KERN_ERR "%s: Warning: Unknown Hauppauge model #%d\n",
+			dev->name, tv.model);
+		break;
+	}
+
+	printk(KERN_INFO "%s: Hauppauge eeprom: model=%d\n", dev->name,
+		tv.model);
+}
+
+void saa7164_card_setup(struct saa7164_dev *dev)
+{
+	static u8 eeprom[256];
+
+	if (dev->i2c_bus[0].i2c_rc == 0) {
+		if (saa7164_api_read_eeprom(dev, &eeprom[0],
+			sizeof(eeprom)) < 0)
+			return;
+	}
+
+	switch (dev->board) {
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_2:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_3:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_4:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_5:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250_2:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250_3:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2255proto:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2255:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2205:
+		hauppauge_eeprom(dev, &eeprom[0]);
+		break;
+	}
+}
+
+/* With most other drivers, the kernel expects to communicate with subdrivers
+ * through i2c. This bridge does not allow that, it does not expose any direct
+ * access to I2C. Instead we have to communicate through the device f/w for
+ * register access to 'processing units'. Each unit has a unique
+ * id, regardless of how the physical implementation occurs across
+ * the three physical i2c busses. The being said if we want leverge of
+ * the existing kernel drivers for tuners and demods we have to 'speak i2c',
+ * to this bridge implements 3 virtual i2c buses. This is a helper function
+ * for those.
+ *
+ * Description: Translate the kernels notion of an i2c address and bus into
+ * the appropriate unitid.
+ */
+int saa7164_i2caddr_to_unitid(struct saa7164_i2c *bus, int addr)
+{
+	/* For a given bus and i2c device address, return the saa7164 unique
+	 * unitid. < 0 on error */
+
+	struct saa7164_dev *dev = bus->dev;
+	struct saa7164_unit *unit;
+	int i;
+
+	for (i = 0; i < SAA7164_MAX_UNITS; i++) {
+		unit = &saa7164_boards[dev->board].unit[i];
+
+		if (unit->type == SAA7164_UNIT_UNDEFINED)
+			continue;
+		if ((bus->nr == unit->i2c_bus_nr) &&
+			(addr == unit->i2c_bus_addr))
+			return unit->id;
+	}
+
+	return -1;
+}
+
+/* The 7164 API needs to know the i2c register length in advance.
+ * this is a helper function. Based on a specific chip addr and bus return the
+ * reg length.
+ */
+int saa7164_i2caddr_to_reglen(struct saa7164_i2c *bus, int addr)
+{
+	/* For a given bus and i2c device address, return the
+	 * saa7164 registry address width. < 0 on error
+	 */
+
+	struct saa7164_dev *dev = bus->dev;
+	struct saa7164_unit *unit;
+	int i;
+
+	for (i = 0; i < SAA7164_MAX_UNITS; i++) {
+		unit = &saa7164_boards[dev->board].unit[i];
+
+		if (unit->type == SAA7164_UNIT_UNDEFINED)
+			continue;
+
+		if ((bus->nr == unit->i2c_bus_nr) &&
+			(addr == unit->i2c_bus_addr))
+			return unit->i2c_reg_len;
+	}
+
+	return -1;
+}
+/* TODO: implement a 'findeeprom' functio like the above and fix any other
+ * eeprom related todo's in -api.c.
+ */
+
+/* Translate a unitid into a x readable device name, for display purposes.  */
+char *saa7164_unitid_name(struct saa7164_dev *dev, u8 unitid)
+{
+	char *undefed = "UNDEFINED";
+	char *bridge = "BRIDGE";
+	struct saa7164_unit *unit;
+	int i;
+
+	if (unitid == 0)
+		return bridge;
+
+	for (i = 0; i < SAA7164_MAX_UNITS; i++) {
+		unit = &saa7164_boards[dev->board].unit[i];
+
+		if (unit->type == SAA7164_UNIT_UNDEFINED)
+			continue;
+
+		if (unitid == unit->id)
+				return unit->name;
+	}
+
+	return undefed;
+}
+
diff --git a/drivers/media/pci/saa7164/saa7164-cmd.c b/drivers/media/pci/saa7164/saa7164-cmd.c
new file mode 100644
index 0000000..3285c37
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-cmd.c
@@ -0,0 +1,589 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/wait.h>
+
+#include "saa7164.h"
+
+static int saa7164_cmd_alloc_seqno(struct saa7164_dev *dev)
+{
+	int i, ret = -1;
+
+	mutex_lock(&dev->lock);
+	for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) {
+		if (dev->cmds[i].inuse == 0) {
+			dev->cmds[i].inuse = 1;
+			dev->cmds[i].signalled = 0;
+			dev->cmds[i].timeout = 0;
+			ret = dev->cmds[i].seqno;
+			break;
+		}
+	}
+	mutex_unlock(&dev->lock);
+
+	return ret;
+}
+
+static void saa7164_cmd_free_seqno(struct saa7164_dev *dev, u8 seqno)
+{
+	mutex_lock(&dev->lock);
+	if ((dev->cmds[seqno].inuse == 1) &&
+		(dev->cmds[seqno].seqno == seqno)) {
+		dev->cmds[seqno].inuse = 0;
+		dev->cmds[seqno].signalled = 0;
+		dev->cmds[seqno].timeout = 0;
+	}
+	mutex_unlock(&dev->lock);
+}
+
+static void saa7164_cmd_timeout_seqno(struct saa7164_dev *dev, u8 seqno)
+{
+	mutex_lock(&dev->lock);
+	if ((dev->cmds[seqno].inuse == 1) &&
+		(dev->cmds[seqno].seqno == seqno)) {
+		dev->cmds[seqno].timeout = 1;
+	}
+	mutex_unlock(&dev->lock);
+}
+
+static u32 saa7164_cmd_timeout_get(struct saa7164_dev *dev, u8 seqno)
+{
+	int ret = 0;
+
+	mutex_lock(&dev->lock);
+	if ((dev->cmds[seqno].inuse == 1) &&
+		(dev->cmds[seqno].seqno == seqno)) {
+		ret = dev->cmds[seqno].timeout;
+	}
+	mutex_unlock(&dev->lock);
+
+	return ret;
+}
+
+/* Commands to the f/w get marshelled to/from this code then onto the PCI
+ * -bus/c running buffer. */
+int saa7164_irq_dequeue(struct saa7164_dev *dev)
+{
+	int ret = SAA_OK, i = 0;
+	u32 timeout;
+	wait_queue_head_t *q = NULL;
+	u8 tmp[512];
+	dprintk(DBGLVL_CMD, "%s()\n", __func__);
+
+	/* While any outstand message on the bus exists... */
+	do {
+
+		/* Peek the msg bus */
+		struct tmComResInfo tRsp = { 0, 0, 0, 0, 0, 0 };
+		ret = saa7164_bus_get(dev, &tRsp, NULL, 1);
+		if (ret != SAA_OK)
+			break;
+
+		q = &dev->cmds[tRsp.seqno].wait;
+		timeout = saa7164_cmd_timeout_get(dev, tRsp.seqno);
+		dprintk(DBGLVL_CMD, "%s() timeout = %d\n", __func__, timeout);
+		if (!timeout) {
+			dprintk(DBGLVL_CMD,
+				"%s() signalled seqno(%d) (for dequeue)\n",
+				__func__, tRsp.seqno);
+			dev->cmds[tRsp.seqno].signalled = 1;
+			wake_up(q);
+		} else {
+			printk(KERN_ERR
+				"%s() found timed out command on the bus\n",
+					__func__);
+
+			/* Clean the bus */
+			ret = saa7164_bus_get(dev, &tRsp, &tmp, 0);
+			printk(KERN_ERR "%s() ret = %x\n", __func__, ret);
+			if (ret == SAA_ERR_EMPTY)
+				/* Someone else already fetched the response */
+				return SAA_OK;
+
+			if (ret != SAA_OK)
+				return ret;
+		}
+
+		/* It's unlikely to have more than 4 or 5 pending messages,
+		 * ensure we exit at some point regardless.
+		 */
+	} while (i++ < 32);
+
+	return ret;
+}
+
+/* Commands to the f/w get marshelled to/from this code then onto the PCI
+ * -bus/c running buffer. */
+static int saa7164_cmd_dequeue(struct saa7164_dev *dev)
+{
+	int loop = 1;
+	int ret;
+	u32 timeout;
+	wait_queue_head_t *q = NULL;
+	u8 tmp[512];
+	dprintk(DBGLVL_CMD, "%s()\n", __func__);
+
+	while (loop) {
+
+		struct tmComResInfo tRsp = { 0, 0, 0, 0, 0, 0 };
+		ret = saa7164_bus_get(dev, &tRsp, NULL, 1);
+		if (ret == SAA_ERR_EMPTY)
+			return SAA_OK;
+
+		if (ret != SAA_OK)
+			return ret;
+
+		q = &dev->cmds[tRsp.seqno].wait;
+		timeout = saa7164_cmd_timeout_get(dev, tRsp.seqno);
+		dprintk(DBGLVL_CMD, "%s() timeout = %d\n", __func__, timeout);
+		if (timeout) {
+			printk(KERN_ERR "found timed out command on the bus\n");
+
+			/* Clean the bus */
+			ret = saa7164_bus_get(dev, &tRsp, &tmp, 0);
+			printk(KERN_ERR "ret = %x\n", ret);
+			if (ret == SAA_ERR_EMPTY)
+				/* Someone else already fetched the response */
+				return SAA_OK;
+
+			if (ret != SAA_OK)
+				return ret;
+
+			if (tRsp.flags & PVC_CMDFLAG_CONTINUE)
+				printk(KERN_ERR "split response\n");
+			else
+				saa7164_cmd_free_seqno(dev, tRsp.seqno);
+
+			printk(KERN_ERR " timeout continue\n");
+			continue;
+		}
+
+		dprintk(DBGLVL_CMD, "%s() signalled seqno(%d) (for dequeue)\n",
+			__func__, tRsp.seqno);
+		dev->cmds[tRsp.seqno].signalled = 1;
+		wake_up(q);
+		return SAA_OK;
+	}
+
+	return SAA_OK;
+}
+
+static int saa7164_cmd_set(struct saa7164_dev *dev, struct tmComResInfo *msg,
+			   void *buf)
+{
+	struct tmComResBusInfo *bus = &dev->bus;
+	u8 cmd_sent;
+	u16 size, idx;
+	u32 cmds;
+	void *tmp;
+	int ret = -1;
+
+	if (!msg) {
+		printk(KERN_ERR "%s() !msg\n", __func__);
+		return SAA_ERR_BAD_PARAMETER;
+	}
+
+	mutex_lock(&dev->cmds[msg->id].lock);
+
+	size = msg->size;
+	idx = 0;
+	cmds = size / bus->m_wMaxReqSize;
+	if (size % bus->m_wMaxReqSize == 0)
+		cmds -= 1;
+
+	cmd_sent = 0;
+
+	/* Split the request into smaller chunks */
+	for (idx = 0; idx < cmds; idx++) {
+
+		msg->flags |= SAA_CMDFLAG_CONTINUE;
+		msg->size = bus->m_wMaxReqSize;
+		tmp = buf + idx * bus->m_wMaxReqSize;
+
+		ret = saa7164_bus_set(dev, msg, tmp);
+		if (ret != SAA_OK) {
+			printk(KERN_ERR "%s() set failed %d\n", __func__, ret);
+
+			if (cmd_sent) {
+				ret = SAA_ERR_BUSY;
+				goto out;
+			}
+			ret = SAA_ERR_OVERFLOW;
+			goto out;
+		}
+		cmd_sent = 1;
+	}
+
+	/* If not the last command... */
+	if (idx != 0)
+		msg->flags &= ~SAA_CMDFLAG_CONTINUE;
+
+	msg->size = size - idx * bus->m_wMaxReqSize;
+
+	ret = saa7164_bus_set(dev, msg, buf + idx * bus->m_wMaxReqSize);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() set last failed %d\n", __func__, ret);
+
+		if (cmd_sent) {
+			ret = SAA_ERR_BUSY;
+			goto out;
+		}
+		ret = SAA_ERR_OVERFLOW;
+		goto out;
+	}
+	ret = SAA_OK;
+
+out:
+	mutex_unlock(&dev->cmds[msg->id].lock);
+	return ret;
+}
+
+/* Wait for a signal event, without holding a mutex. Either return TIMEOUT if
+ * the event never occurred, or SAA_OK if it was signaled during the wait.
+ */
+static int saa7164_cmd_wait(struct saa7164_dev *dev, u8 seqno)
+{
+	wait_queue_head_t *q = NULL;
+	int ret = SAA_BUS_TIMEOUT;
+	unsigned long stamp;
+	int r;
+
+	if (saa_debug >= 4)
+		saa7164_bus_dump(dev);
+
+	dprintk(DBGLVL_CMD, "%s(seqno=%d)\n", __func__, seqno);
+
+	mutex_lock(&dev->lock);
+	if ((dev->cmds[seqno].inuse == 1) &&
+		(dev->cmds[seqno].seqno == seqno)) {
+		q = &dev->cmds[seqno].wait;
+	}
+	mutex_unlock(&dev->lock);
+
+	if (q) {
+		/* If we haven't been signalled we need to wait */
+		if (dev->cmds[seqno].signalled == 0) {
+			stamp = jiffies;
+			dprintk(DBGLVL_CMD,
+				"%s(seqno=%d) Waiting (signalled=%d)\n",
+				__func__, seqno, dev->cmds[seqno].signalled);
+
+			/* Wait for signalled to be flagged or timeout */
+			/* In a highly stressed system this can easily extend
+			 * into multiple seconds before the deferred worker
+			 * is scheduled, and we're woken up via signal.
+			 * We typically are signalled in < 50ms but it can
+			 * take MUCH longer.
+			 */
+			wait_event_timeout(*q, dev->cmds[seqno].signalled,
+				(HZ * waitsecs));
+			r = time_before(jiffies, stamp + (HZ * waitsecs));
+			if (r)
+				ret = SAA_OK;
+			else
+				saa7164_cmd_timeout_seqno(dev, seqno);
+
+			dprintk(DBGLVL_CMD, "%s(seqno=%d) Waiting res = %d "
+				"(signalled=%d)\n", __func__, seqno, r,
+				dev->cmds[seqno].signalled);
+		} else
+			ret = SAA_OK;
+	} else
+		printk(KERN_ERR "%s(seqno=%d) seqno is invalid\n",
+			__func__, seqno);
+
+	return ret;
+}
+
+void saa7164_cmd_signal(struct saa7164_dev *dev, u8 seqno)
+{
+	int i;
+	dprintk(DBGLVL_CMD, "%s()\n", __func__);
+
+	mutex_lock(&dev->lock);
+	for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) {
+		if (dev->cmds[i].inuse == 1) {
+			dprintk(DBGLVL_CMD,
+				"seqno %d inuse, sig = %d, t/out = %d\n",
+				dev->cmds[i].seqno,
+				dev->cmds[i].signalled,
+				dev->cmds[i].timeout);
+		}
+	}
+
+	for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) {
+		if ((dev->cmds[i].inuse == 1) && ((i == 0) ||
+			(dev->cmds[i].signalled) || (dev->cmds[i].timeout))) {
+			dprintk(DBGLVL_CMD, "%s(seqno=%d) calling wake_up\n",
+				__func__, i);
+			dev->cmds[i].signalled = 1;
+			wake_up(&dev->cmds[i].wait);
+		}
+	}
+	mutex_unlock(&dev->lock);
+}
+
+int saa7164_cmd_send(struct saa7164_dev *dev, u8 id, enum tmComResCmd command,
+	u16 controlselector, u16 size, void *buf)
+{
+	struct tmComResInfo command_t, *pcommand_t;
+	struct tmComResInfo response_t, *presponse_t;
+	u8 errdata[256];
+	u16 resp_dsize;
+	u16 data_recd;
+	u32 loop;
+	int ret;
+	int safety = 0;
+
+	dprintk(DBGLVL_CMD, "%s(unitid = %s (%d) , command = 0x%x, "
+		"sel = 0x%x)\n", __func__, saa7164_unitid_name(dev, id), id,
+		command, controlselector);
+
+	if ((size == 0) || (buf == NULL)) {
+		printk(KERN_ERR "%s() Invalid param\n", __func__);
+		return SAA_ERR_BAD_PARAMETER;
+	}
+
+	/* Prepare some basic command/response structures */
+	memset(&command_t, 0, sizeof(command_t));
+	memset(&response_t, 0, sizeof(response_t));
+	pcommand_t = &command_t;
+	presponse_t = &response_t;
+	command_t.id = id;
+	command_t.command = command;
+	command_t.controlselector = controlselector;
+	command_t.size = size;
+
+	/* Allocate a unique sequence number */
+	ret = saa7164_cmd_alloc_seqno(dev);
+	if (ret < 0) {
+		printk(KERN_ERR "%s() No free sequences\n", __func__);
+		ret = SAA_ERR_NO_RESOURCES;
+		goto out;
+	}
+
+	command_t.seqno = (u8)ret;
+
+	/* Send Command */
+	resp_dsize = size;
+	pcommand_t->size = size;
+
+	dprintk(DBGLVL_CMD, "%s() pcommand_t.seqno = %d\n",
+		__func__, pcommand_t->seqno);
+
+	dprintk(DBGLVL_CMD, "%s() pcommand_t.size = %d\n",
+		__func__, pcommand_t->size);
+
+	ret = saa7164_cmd_set(dev, pcommand_t, buf);
+	if (ret != SAA_OK) {
+		printk(KERN_ERR "%s() set command failed %d\n", __func__, ret);
+
+		if (ret != SAA_ERR_BUSY)
+			saa7164_cmd_free_seqno(dev, pcommand_t->seqno);
+		else
+			/* Flag a timeout, because at least one
+			 * command was sent */
+			saa7164_cmd_timeout_seqno(dev, pcommand_t->seqno);
+
+		goto out;
+	}
+
+	/* With split responses we have to collect the msgs piece by piece */
+	data_recd = 0;
+	loop = 1;
+	while (loop) {
+		dprintk(DBGLVL_CMD, "%s() loop\n", __func__);
+
+		ret = saa7164_cmd_wait(dev, pcommand_t->seqno);
+		dprintk(DBGLVL_CMD, "%s() loop ret = %d\n", __func__, ret);
+
+		/* if power is down and this is not a power command ... */
+
+		if (ret == SAA_BUS_TIMEOUT) {
+			printk(KERN_ERR "Event timed out\n");
+			saa7164_cmd_timeout_seqno(dev, pcommand_t->seqno);
+			return ret;
+		}
+
+		if (ret != SAA_OK) {
+			printk(KERN_ERR "spurious error\n");
+			return ret;
+		}
+
+		/* Peek response */
+		ret = saa7164_bus_get(dev, presponse_t, NULL, 1);
+		if (ret == SAA_ERR_EMPTY) {
+			dprintk(4, "%s() SAA_ERR_EMPTY\n", __func__);
+			continue;
+		}
+		if (ret != SAA_OK) {
+			printk(KERN_ERR "peek failed\n");
+			return ret;
+		}
+
+		dprintk(DBGLVL_CMD, "%s() presponse_t->seqno = %d\n",
+			__func__, presponse_t->seqno);
+
+		dprintk(DBGLVL_CMD, "%s() presponse_t->flags = 0x%x\n",
+			__func__, presponse_t->flags);
+
+		dprintk(DBGLVL_CMD, "%s() presponse_t->size = %d\n",
+			__func__, presponse_t->size);
+
+		/* Check if the response was for our command */
+		if (presponse_t->seqno != pcommand_t->seqno) {
+
+			dprintk(DBGLVL_CMD,
+				"wrong event: seqno = %d, "
+				"expected seqno = %d, "
+				"will dequeue regardless\n",
+				presponse_t->seqno, pcommand_t->seqno);
+
+			ret = saa7164_cmd_dequeue(dev);
+			if (ret != SAA_OK) {
+				printk(KERN_ERR "dequeue failed, ret = %d\n",
+					ret);
+				if (safety++ > 16) {
+					printk(KERN_ERR
+					"dequeue exceeded, safety exit\n");
+					return SAA_ERR_BUSY;
+				}
+			}
+
+			continue;
+		}
+
+		if ((presponse_t->flags & PVC_RESPONSEFLAG_ERROR) != 0) {
+
+			memset(&errdata[0], 0, sizeof(errdata));
+
+			ret = saa7164_bus_get(dev, presponse_t, &errdata[0], 0);
+			if (ret != SAA_OK) {
+				printk(KERN_ERR "get error(2)\n");
+				return ret;
+			}
+
+			saa7164_cmd_free_seqno(dev, pcommand_t->seqno);
+
+			dprintk(DBGLVL_CMD, "%s() errdata %02x%02x%02x%02x\n",
+				__func__, errdata[0], errdata[1], errdata[2],
+				errdata[3]);
+
+			/* Map error codes */
+			dprintk(DBGLVL_CMD, "%s() cmd, error code  = 0x%x\n",
+				__func__, errdata[0]);
+
+			switch (errdata[0]) {
+			case PVC_ERRORCODE_INVALID_COMMAND:
+				dprintk(DBGLVL_CMD, "%s() INVALID_COMMAND\n",
+					__func__);
+				ret = SAA_ERR_INVALID_COMMAND;
+				break;
+			case PVC_ERRORCODE_INVALID_DATA:
+				dprintk(DBGLVL_CMD, "%s() INVALID_DATA\n",
+					__func__);
+				ret = SAA_ERR_BAD_PARAMETER;
+				break;
+			case PVC_ERRORCODE_TIMEOUT:
+				dprintk(DBGLVL_CMD, "%s() TIMEOUT\n", __func__);
+				ret = SAA_ERR_TIMEOUT;
+				break;
+			case PVC_ERRORCODE_NAK:
+				dprintk(DBGLVL_CMD, "%s() NAK\n", __func__);
+				ret = SAA_ERR_NULL_PACKET;
+				break;
+			case PVC_ERRORCODE_UNKNOWN:
+			case PVC_ERRORCODE_INVALID_CONTROL:
+				dprintk(DBGLVL_CMD,
+					"%s() UNKNOWN OR INVALID CONTROL\n",
+					__func__);
+			default:
+				dprintk(DBGLVL_CMD, "%s() UNKNOWN\n", __func__);
+				ret = SAA_ERR_NOT_SUPPORTED;
+			}
+
+			/* See of other commands are on the bus */
+			if (saa7164_cmd_dequeue(dev) != SAA_OK)
+				printk(KERN_ERR "dequeue(2) failed\n");
+
+			return ret;
+		}
+
+		/* If response is invalid */
+		if ((presponse_t->id != pcommand_t->id) ||
+			(presponse_t->command != pcommand_t->command) ||
+			(presponse_t->controlselector !=
+				pcommand_t->controlselector) ||
+			(((resp_dsize - data_recd) != presponse_t->size) &&
+				!(presponse_t->flags & PVC_CMDFLAG_CONTINUE)) ||
+			((resp_dsize - data_recd) < presponse_t->size)) {
+
+			/* Invalid */
+			dprintk(DBGLVL_CMD, "%s() Invalid\n", __func__);
+			ret = saa7164_bus_get(dev, presponse_t, NULL, 0);
+			if (ret != SAA_OK) {
+				printk(KERN_ERR "get failed\n");
+				return ret;
+			}
+
+			/* See of other commands are on the bus */
+			if (saa7164_cmd_dequeue(dev) != SAA_OK)
+				printk(KERN_ERR "dequeue(3) failed\n");
+			continue;
+		}
+
+		/* OK, now we're actually getting out correct response */
+		ret = saa7164_bus_get(dev, presponse_t, buf + data_recd, 0);
+		if (ret != SAA_OK) {
+			printk(KERN_ERR "get failed\n");
+			return ret;
+		}
+
+		data_recd = presponse_t->size + data_recd;
+		if (resp_dsize == data_recd) {
+			dprintk(DBGLVL_CMD, "%s() Resp recd\n", __func__);
+			break;
+		}
+
+		/* See of other commands are on the bus */
+		if (saa7164_cmd_dequeue(dev) != SAA_OK)
+			printk(KERN_ERR "dequeue(3) failed\n");
+
+		continue;
+
+	} /* (loop) */
+
+	/* Release the sequence number allocation */
+	saa7164_cmd_free_seqno(dev, pcommand_t->seqno);
+
+	/* if powerdown signal all pending commands */
+
+	dprintk(DBGLVL_CMD, "%s() Calling dequeue then exit\n", __func__);
+
+	/* See of other commands are on the bus */
+	if (saa7164_cmd_dequeue(dev) != SAA_OK)
+		printk(KERN_ERR "dequeue(4) failed\n");
+
+	ret = SAA_OK;
+out:
+	return ret;
+}
+
diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c
new file mode 100644
index 0000000..8bbd092
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-core.c
@@ -0,0 +1,1550 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kmod.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <asm/div64.h>
+
+#ifdef CONFIG_PROC_FS
+#include <linux/proc_fs.h>
+#endif
+#include "saa7164.h"
+
+MODULE_DESCRIPTION("Driver for NXP SAA7164 based TV cards");
+MODULE_AUTHOR("Steven Toth <stoth@kernellabs.com>");
+MODULE_LICENSE("GPL");
+
+/*
+ *  1 Basic
+ *  2
+ *  4 i2c
+ *  8 api
+ * 16 cmd
+ * 32 bus
+ */
+
+unsigned int saa_debug;
+module_param_named(debug, saa_debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages");
+
+static unsigned int fw_debug;
+module_param(fw_debug, int, 0644);
+MODULE_PARM_DESC(fw_debug, "Firmware debug level def:2");
+
+unsigned int encoder_buffers = SAA7164_MAX_ENCODER_BUFFERS;
+module_param(encoder_buffers, int, 0644);
+MODULE_PARM_DESC(encoder_buffers, "Total buffers in read queue 16-512 def:64");
+
+unsigned int vbi_buffers = SAA7164_MAX_VBI_BUFFERS;
+module_param(vbi_buffers, int, 0644);
+MODULE_PARM_DESC(vbi_buffers, "Total buffers in read queue 16-512 def:64");
+
+unsigned int waitsecs = 10;
+module_param(waitsecs, int, 0644);
+MODULE_PARM_DESC(waitsecs, "timeout on firmware messages");
+
+static unsigned int card[]  = {[0 ... (SAA7164_MAXBOARDS - 1)] = UNSET };
+module_param_array(card,  int, NULL, 0444);
+MODULE_PARM_DESC(card, "card type");
+
+static unsigned int print_histogram = 64;
+module_param(print_histogram, int, 0644);
+MODULE_PARM_DESC(print_histogram, "print histogram values once");
+
+unsigned int crc_checking = 1;
+module_param(crc_checking, int, 0644);
+MODULE_PARM_DESC(crc_checking, "enable crc sanity checking on buffers");
+
+static unsigned int guard_checking = 1;
+module_param(guard_checking, int, 0644);
+MODULE_PARM_DESC(guard_checking,
+	"enable dma sanity checking for buffer overruns");
+
+static bool enable_msi = true;
+module_param(enable_msi, bool, 0444);
+MODULE_PARM_DESC(enable_msi,
+		"enable the use of an msi interrupt if available");
+
+static unsigned int saa7164_devcount;
+
+static DEFINE_MUTEX(devlist);
+LIST_HEAD(saa7164_devlist);
+
+#define INT_SIZE 16
+
+static void saa7164_pack_verifier(struct saa7164_buffer *buf)
+{
+	u8 *p = (u8 *)buf->cpu;
+	int i;
+
+	for (i = 0; i < buf->actual_size; i += 2048) {
+
+		if ((*(p + i + 0) != 0x00) || (*(p + i + 1) != 0x00) ||
+			(*(p + i + 2) != 0x01) || (*(p + i + 3) != 0xBA)) {
+			printk(KERN_ERR "No pack at 0x%x\n", i);
+#if 0
+			print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
+				       p + 1, 32, false);
+#endif
+		}
+	}
+}
+
+#define FIXED_VIDEO_PID 0xf1
+#define FIXED_AUDIO_PID 0xf2
+
+static void saa7164_ts_verifier(struct saa7164_buffer *buf)
+{
+	struct saa7164_port *port = buf->port;
+	u32 i;
+	u8 cc, a;
+	u16 pid;
+	u8 *bufcpu = (u8 *)buf->cpu;
+
+	port->sync_errors = 0;
+	port->v_cc_errors = 0;
+	port->a_cc_errors = 0;
+
+	for (i = 0; i < buf->actual_size; i += 188) {
+		if (*(bufcpu + i) != 0x47)
+			port->sync_errors++;
+
+		/* TODO: Query pid lower 8 bits, ignoring upper bits intensionally */
+		pid = ((*(bufcpu + i + 1) & 0x1f) << 8) | *(bufcpu + i + 2);
+		cc = *(bufcpu + i + 3) & 0x0f;
+
+		if (pid == FIXED_VIDEO_PID) {
+			a = ((port->last_v_cc + 1) & 0x0f);
+			if (a != cc) {
+				printk(KERN_ERR "video cc last = %x current = %x i = %d\n",
+					port->last_v_cc, cc, i);
+				port->v_cc_errors++;
+			}
+
+			port->last_v_cc = cc;
+		} else
+		if (pid == FIXED_AUDIO_PID) {
+			a = ((port->last_a_cc + 1) & 0x0f);
+			if (a != cc) {
+				printk(KERN_ERR "audio cc last = %x current = %x i = %d\n",
+					port->last_a_cc, cc, i);
+				port->a_cc_errors++;
+			}
+
+			port->last_a_cc = cc;
+		}
+
+	}
+
+	/* Only report errors if we've been through this function atleast
+	 * once already and the cached cc values are primed. First time through
+	 * always generates errors.
+	 */
+	if (port->v_cc_errors && (port->done_first_interrupt > 1))
+		printk(KERN_ERR "video pid cc, %d errors\n", port->v_cc_errors);
+
+	if (port->a_cc_errors && (port->done_first_interrupt > 1))
+		printk(KERN_ERR "audio pid cc, %d errors\n", port->a_cc_errors);
+
+	if (port->sync_errors && (port->done_first_interrupt > 1))
+		printk(KERN_ERR "sync_errors = %d\n", port->sync_errors);
+
+	if (port->done_first_interrupt == 1)
+		port->done_first_interrupt++;
+}
+
+static void saa7164_histogram_reset(struct saa7164_histogram *hg, char *name)
+{
+	int i;
+
+	memset(hg, 0, sizeof(struct saa7164_histogram));
+	strcpy(hg->name, name);
+
+	/* First 30ms x 1ms */
+	for (i = 0; i < 30; i++)
+		hg->counter1[0 + i].val = i;
+
+	/* 30 - 200ms x 10ms  */
+	for (i = 0; i < 18; i++)
+		hg->counter1[30 + i].val = 30 + (i * 10);
+
+	/* 200 - 2000ms x 100ms  */
+	for (i = 0; i < 15; i++)
+		hg->counter1[48 + i].val = 200 + (i * 200);
+
+	/* Catch all massive value (2secs) */
+	hg->counter1[55].val = 2000;
+
+	/* Catch all massive value (4secs) */
+	hg->counter1[56].val = 4000;
+
+	/* Catch all massive value (8secs) */
+	hg->counter1[57].val = 8000;
+
+	/* Catch all massive value (15secs) */
+	hg->counter1[58].val = 15000;
+
+	/* Catch all massive value (30secs) */
+	hg->counter1[59].val = 30000;
+
+	/* Catch all massive value (60secs) */
+	hg->counter1[60].val = 60000;
+
+	/* Catch all massive value (5mins) */
+	hg->counter1[61].val = 300000;
+
+	/* Catch all massive value (15mins) */
+	hg->counter1[62].val = 900000;
+
+	/* Catch all massive values (1hr) */
+	hg->counter1[63].val = 3600000;
+}
+
+void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val)
+{
+	int i;
+	for (i = 0; i < 64; i++) {
+		if (val <= hg->counter1[i].val) {
+			hg->counter1[i].count++;
+			hg->counter1[i].update_time = jiffies;
+			break;
+		}
+	}
+}
+
+static void saa7164_histogram_print(struct saa7164_port *port,
+	struct saa7164_histogram *hg)
+{
+	u32 entries = 0;
+	int i;
+
+	printk(KERN_ERR "Histogram named %s (ms, count, last_update_jiffy)\n", hg->name);
+	for (i = 0; i < 64; i++) {
+		if (hg->counter1[i].count == 0)
+			continue;
+
+		printk(KERN_ERR " %4d %12d %Ld\n",
+			hg->counter1[i].val,
+			hg->counter1[i].count,
+			hg->counter1[i].update_time);
+
+		entries++;
+	}
+	printk(KERN_ERR "Total: %d\n", entries);
+}
+
+static void saa7164_work_enchandler_helper(struct saa7164_port *port, int bufnr)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf = NULL;
+	struct saa7164_user_buffer *ubuf = NULL;
+	struct list_head *c, *n;
+	int i = 0;
+	u8 *p;
+
+	mutex_lock(&port->dmaqueue_lock);
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+
+		buf = list_entry(c, struct saa7164_buffer, list);
+		if (i++ > port->hwcfg.buffercount) {
+			printk(KERN_ERR "%s() illegal i count %d\n",
+				__func__, i);
+			break;
+		}
+
+		if (buf->idx == bufnr) {
+
+			/* Found the buffer, deal with it */
+			dprintk(DBGLVL_IRQ, "%s() bufnr: %d\n", __func__, bufnr);
+
+			if (crc_checking) {
+				/* Throw a new checksum on the dma buffer */
+				buf->crc = crc32(0, buf->cpu, buf->actual_size);
+			}
+
+			if (guard_checking) {
+				p = (u8 *)buf->cpu;
+				if ((*(p + buf->actual_size + 0) != 0xff) ||
+					(*(p + buf->actual_size + 1) != 0xff) ||
+					(*(p + buf->actual_size + 2) != 0xff) ||
+					(*(p + buf->actual_size + 3) != 0xff) ||
+					(*(p + buf->actual_size + 0x10) != 0xff) ||
+					(*(p + buf->actual_size + 0x11) != 0xff) ||
+					(*(p + buf->actual_size + 0x12) != 0xff) ||
+					(*(p + buf->actual_size + 0x13) != 0xff)) {
+						printk(KERN_ERR "%s() buf %p guard buffer breach\n",
+							__func__, buf);
+#if 0
+			print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
+				       p + buf->actual_size - 32, 64, false);
+#endif
+				}
+			}
+
+			if ((port->nr != SAA7164_PORT_VBI1) && (port->nr != SAA7164_PORT_VBI2)) {
+				/* Validate the incoming buffer content */
+				if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_TS)
+					saa7164_ts_verifier(buf);
+				else if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS)
+					saa7164_pack_verifier(buf);
+			}
+
+			/* find a free user buffer and clone to it */
+			if (!list_empty(&port->list_buf_free.list)) {
+
+				/* Pull the first buffer from the used list */
+				ubuf = list_first_entry(&port->list_buf_free.list,
+					struct saa7164_user_buffer, list);
+
+				if (buf->actual_size <= ubuf->actual_size) {
+
+					memcpy(ubuf->data, buf->cpu, ubuf->actual_size);
+
+					if (crc_checking) {
+						/* Throw a new checksum on the read buffer */
+						ubuf->crc = crc32(0, ubuf->data, ubuf->actual_size);
+					}
+
+					/* Requeue the buffer on the free list */
+					ubuf->pos = 0;
+
+					list_move_tail(&ubuf->list,
+						&port->list_buf_used.list);
+
+					/* Flag any userland waiters */
+					wake_up_interruptible(&port->wait_read);
+
+				} else {
+					printk(KERN_ERR "buf %p bufsize fails match\n", buf);
+				}
+
+			} else
+				printk(KERN_ERR "encirq no free buffers, increase param encoder_buffers\n");
+
+			/* Ensure offset into buffer remains 0, fill buffer
+			 * with known bad data. We check for this data at a later point
+			 * in time. */
+			saa7164_buffer_zero_offsets(port, bufnr);
+			memset(buf->cpu, 0xff, buf->pci_size);
+			if (crc_checking) {
+				/* Throw yet aanother new checksum on the dma buffer */
+				buf->crc = crc32(0, buf->cpu, buf->actual_size);
+			}
+
+			break;
+		}
+	}
+	mutex_unlock(&port->dmaqueue_lock);
+}
+
+static void saa7164_work_enchandler(struct work_struct *w)
+{
+	struct saa7164_port *port =
+		container_of(w, struct saa7164_port, workenc);
+	struct saa7164_dev *dev = port->dev;
+
+	u32 wp, mcb, rp, cnt = 0;
+
+	port->last_svc_msecs_diff = port->last_svc_msecs;
+	port->last_svc_msecs = jiffies_to_msecs(jiffies);
+
+	port->last_svc_msecs_diff = port->last_svc_msecs -
+		port->last_svc_msecs_diff;
+
+	saa7164_histogram_update(&port->svc_interval,
+		port->last_svc_msecs_diff);
+
+	port->last_irq_svc_msecs_diff = port->last_svc_msecs -
+		port->last_irq_msecs;
+
+	saa7164_histogram_update(&port->irq_svc_interval,
+		port->last_irq_svc_msecs_diff);
+
+	dprintk(DBGLVL_IRQ,
+		"%s() %Ldms elapsed irq->deferred %Ldms wp: %d rp: %d\n",
+		__func__,
+		port->last_svc_msecs_diff,
+		port->last_irq_svc_msecs_diff,
+		port->last_svc_wp,
+		port->last_svc_rp
+		);
+
+	/* Current write position */
+	wp = saa7164_readl(port->bufcounter);
+	if (wp > (port->hwcfg.buffercount - 1)) {
+		printk(KERN_ERR "%s() illegal buf count %d\n", __func__, wp);
+		return;
+	}
+
+	/* Most current complete buffer */
+	if (wp == 0)
+		mcb = (port->hwcfg.buffercount - 1);
+	else
+		mcb = wp - 1;
+
+	while (1) {
+		if (port->done_first_interrupt == 0) {
+			port->done_first_interrupt++;
+			rp = mcb;
+		} else
+			rp = (port->last_svc_rp + 1) % 8;
+
+		if (rp > (port->hwcfg.buffercount - 1)) {
+			printk(KERN_ERR "%s() illegal rp count %d\n", __func__, rp);
+			break;
+		}
+
+		saa7164_work_enchandler_helper(port, rp);
+		port->last_svc_rp = rp;
+		cnt++;
+
+		if (rp == mcb)
+			break;
+	}
+
+	/* TODO: Convert this into a /proc/saa7164 style readable file */
+	if (print_histogram == port->nr) {
+		saa7164_histogram_print(port, &port->irq_interval);
+		saa7164_histogram_print(port, &port->svc_interval);
+		saa7164_histogram_print(port, &port->irq_svc_interval);
+		saa7164_histogram_print(port, &port->read_interval);
+		saa7164_histogram_print(port, &port->poll_interval);
+		/* TODO: fix this to preserve any previous state */
+		print_histogram = 64 + port->nr;
+	}
+}
+
+static void saa7164_work_vbihandler(struct work_struct *w)
+{
+	struct saa7164_port *port =
+		container_of(w, struct saa7164_port, workenc);
+	struct saa7164_dev *dev = port->dev;
+
+	u32 wp, mcb, rp, cnt = 0;
+
+	port->last_svc_msecs_diff = port->last_svc_msecs;
+	port->last_svc_msecs = jiffies_to_msecs(jiffies);
+	port->last_svc_msecs_diff = port->last_svc_msecs -
+		port->last_svc_msecs_diff;
+
+	saa7164_histogram_update(&port->svc_interval,
+		port->last_svc_msecs_diff);
+
+	port->last_irq_svc_msecs_diff = port->last_svc_msecs -
+		port->last_irq_msecs;
+
+	saa7164_histogram_update(&port->irq_svc_interval,
+		port->last_irq_svc_msecs_diff);
+
+	dprintk(DBGLVL_IRQ,
+		"%s() %Ldms elapsed irq->deferred %Ldms wp: %d rp: %d\n",
+		__func__,
+		port->last_svc_msecs_diff,
+		port->last_irq_svc_msecs_diff,
+		port->last_svc_wp,
+		port->last_svc_rp
+		);
+
+	/* Current write position */
+	wp = saa7164_readl(port->bufcounter);
+	if (wp > (port->hwcfg.buffercount - 1)) {
+		printk(KERN_ERR "%s() illegal buf count %d\n", __func__, wp);
+		return;
+	}
+
+	/* Most current complete buffer */
+	if (wp == 0)
+		mcb = (port->hwcfg.buffercount - 1);
+	else
+		mcb = wp - 1;
+
+	while (1) {
+		if (port->done_first_interrupt == 0) {
+			port->done_first_interrupt++;
+			rp = mcb;
+		} else
+			rp = (port->last_svc_rp + 1) % 8;
+
+		if (rp > (port->hwcfg.buffercount - 1)) {
+			printk(KERN_ERR "%s() illegal rp count %d\n", __func__, rp);
+			break;
+		}
+
+		saa7164_work_enchandler_helper(port, rp);
+		port->last_svc_rp = rp;
+		cnt++;
+
+		if (rp == mcb)
+			break;
+	}
+
+	/* TODO: Convert this into a /proc/saa7164 style readable file */
+	if (print_histogram == port->nr) {
+		saa7164_histogram_print(port, &port->irq_interval);
+		saa7164_histogram_print(port, &port->svc_interval);
+		saa7164_histogram_print(port, &port->irq_svc_interval);
+		saa7164_histogram_print(port, &port->read_interval);
+		saa7164_histogram_print(port, &port->poll_interval);
+		/* TODO: fix this to preserve any previous state */
+		print_histogram = 64 + port->nr;
+	}
+}
+
+static void saa7164_work_cmdhandler(struct work_struct *w)
+{
+	struct saa7164_dev *dev = container_of(w, struct saa7164_dev, workcmd);
+
+	/* Wake up any complete commands */
+	saa7164_irq_dequeue(dev);
+}
+
+static void saa7164_buffer_deliver(struct saa7164_buffer *buf)
+{
+	struct saa7164_port *port = buf->port;
+
+	/* Feed the transport payload into the kernel demux */
+	dvb_dmx_swfilter_packets(&port->dvb.demux, (u8 *)buf->cpu,
+		SAA7164_TS_NUMBER_OF_LINES);
+
+}
+
+static irqreturn_t saa7164_irq_vbi(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	/* Store old time */
+	port->last_irq_msecs_diff = port->last_irq_msecs;
+
+	/* Collect new stats */
+	port->last_irq_msecs = jiffies_to_msecs(jiffies);
+
+	/* Calculate stats */
+	port->last_irq_msecs_diff = port->last_irq_msecs -
+		port->last_irq_msecs_diff;
+
+	saa7164_histogram_update(&port->irq_interval,
+		port->last_irq_msecs_diff);
+
+	dprintk(DBGLVL_IRQ, "%s() %Ldms elapsed\n", __func__,
+		port->last_irq_msecs_diff);
+
+	/* Tis calls the vbi irq handler */
+	schedule_work(&port->workenc);
+	return 0;
+}
+
+static irqreturn_t saa7164_irq_encoder(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	/* Store old time */
+	port->last_irq_msecs_diff = port->last_irq_msecs;
+
+	/* Collect new stats */
+	port->last_irq_msecs = jiffies_to_msecs(jiffies);
+
+	/* Calculate stats */
+	port->last_irq_msecs_diff = port->last_irq_msecs -
+		port->last_irq_msecs_diff;
+
+	saa7164_histogram_update(&port->irq_interval,
+		port->last_irq_msecs_diff);
+
+	dprintk(DBGLVL_IRQ, "%s() %Ldms elapsed\n", __func__,
+		port->last_irq_msecs_diff);
+
+	schedule_work(&port->workenc);
+	return 0;
+}
+
+static irqreturn_t saa7164_irq_ts(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct list_head *c, *n;
+	int wp, i = 0, rp;
+
+	/* Find the current write point from the hardware */
+	wp = saa7164_readl(port->bufcounter);
+	if (wp > (port->hwcfg.buffercount - 1))
+		BUG();
+
+	/* Find the previous buffer to the current write point */
+	if (wp == 0)
+		rp = (port->hwcfg.buffercount - 1);
+	else
+		rp = wp - 1;
+
+	/* Lookup the WP in the buffer list */
+	/* TODO: turn this into a worker thread */
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+		buf = list_entry(c, struct saa7164_buffer, list);
+		if (i++ > port->hwcfg.buffercount)
+			BUG();
+
+		if (buf->idx == rp) {
+			/* Found the buffer, deal with it */
+			dprintk(DBGLVL_IRQ, "%s() wp: %d processing: %d\n",
+				__func__, wp, rp);
+			saa7164_buffer_deliver(buf);
+			break;
+		}
+
+	}
+	return 0;
+}
+
+/* Primary IRQ handler and dispatch mechanism */
+static irqreturn_t saa7164_irq(int irq, void *dev_id)
+{
+	struct saa7164_dev *dev = dev_id;
+	struct saa7164_port *porta, *portb, *portc, *portd, *porte, *portf;
+
+	u32 intid, intstat[INT_SIZE/4];
+	int i, handled = 0, bit;
+
+	if (dev == NULL) {
+		printk(KERN_ERR "%s() No device specified\n", __func__);
+		handled = 0;
+		goto out;
+	}
+
+	porta = &dev->ports[SAA7164_PORT_TS1];
+	portb = &dev->ports[SAA7164_PORT_TS2];
+	portc = &dev->ports[SAA7164_PORT_ENC1];
+	portd = &dev->ports[SAA7164_PORT_ENC2];
+	porte = &dev->ports[SAA7164_PORT_VBI1];
+	portf = &dev->ports[SAA7164_PORT_VBI2];
+
+	/* Check that the hardware is accessible. If the status bytes are
+	 * 0xFF then the device is not accessible, the the IRQ belongs
+	 * to another driver.
+	 * 4 x u32 interrupt registers.
+	 */
+	for (i = 0; i < INT_SIZE/4; i++) {
+
+		/* TODO: Convert into saa7164_readl() */
+		/* Read the 4 hardware interrupt registers */
+		intstat[i] = saa7164_readl(dev->int_status + (i * 4));
+
+		if (intstat[i])
+			handled = 1;
+	}
+	if (handled == 0)
+		goto out;
+
+	/* For each of the HW interrupt registers */
+	for (i = 0; i < INT_SIZE/4; i++) {
+
+		if (intstat[i]) {
+			/* Each function of the board has it's own interruptid.
+			 * Find the function that triggered then call
+			 * it's handler.
+			 */
+			for (bit = 0; bit < 32; bit++) {
+
+				if (((intstat[i] >> bit) & 0x00000001) == 0)
+					continue;
+
+				/* Calculate the interrupt id (0x00 to 0x7f) */
+
+				intid = (i * 32) + bit;
+				if (intid == dev->intfdesc.bInterruptId) {
+					/* A response to an cmd/api call */
+					schedule_work(&dev->workcmd);
+				} else if (intid == porta->hwcfg.interruptid) {
+
+					/* Transport path 1 */
+					saa7164_irq_ts(porta);
+
+				} else if (intid == portb->hwcfg.interruptid) {
+
+					/* Transport path 2 */
+					saa7164_irq_ts(portb);
+
+				} else if (intid == portc->hwcfg.interruptid) {
+
+					/* Encoder path 1 */
+					saa7164_irq_encoder(portc);
+
+				} else if (intid == portd->hwcfg.interruptid) {
+
+					/* Encoder path 2 */
+					saa7164_irq_encoder(portd);
+
+				} else if (intid == porte->hwcfg.interruptid) {
+
+					/* VBI path 1 */
+					saa7164_irq_vbi(porte);
+
+				} else if (intid == portf->hwcfg.interruptid) {
+
+					/* VBI path 2 */
+					saa7164_irq_vbi(portf);
+
+				} else {
+					/* Find the function */
+					dprintk(DBGLVL_IRQ,
+						"%s() unhandled interrupt "
+						"reg 0x%x bit 0x%x "
+						"intid = 0x%x\n",
+						__func__, i, bit, intid);
+				}
+			}
+
+			/* Ack it */
+			saa7164_writel(dev->int_ack + (i * 4), intstat[i]);
+
+		}
+	}
+out:
+	return IRQ_RETVAL(handled);
+}
+
+void saa7164_getfirmwarestatus(struct saa7164_dev *dev)
+{
+	struct saa7164_fw_status *s = &dev->fw_status;
+
+	dev->fw_status.status = saa7164_readl(SAA_DEVICE_SYSINIT_STATUS);
+	dev->fw_status.mode = saa7164_readl(SAA_DEVICE_SYSINIT_MODE);
+	dev->fw_status.spec = saa7164_readl(SAA_DEVICE_SYSINIT_SPEC);
+	dev->fw_status.inst = saa7164_readl(SAA_DEVICE_SYSINIT_INST);
+	dev->fw_status.cpuload = saa7164_readl(SAA_DEVICE_SYSINIT_CPULOAD);
+	dev->fw_status.remainheap =
+		saa7164_readl(SAA_DEVICE_SYSINIT_REMAINHEAP);
+
+	dprintk(1, "Firmware status:\n");
+	dprintk(1, " .status     = 0x%08x\n", s->status);
+	dprintk(1, " .mode       = 0x%08x\n", s->mode);
+	dprintk(1, " .spec       = 0x%08x\n", s->spec);
+	dprintk(1, " .inst       = 0x%08x\n", s->inst);
+	dprintk(1, " .cpuload    = 0x%08x\n", s->cpuload);
+	dprintk(1, " .remainheap = 0x%08x\n", s->remainheap);
+}
+
+u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev)
+{
+	u32 reg;
+
+	reg = saa7164_readl(SAA_DEVICE_VERSION);
+	dprintk(1, "Device running firmware version %d.%d.%d.%d (0x%x)\n",
+		(reg & 0x0000fc00) >> 10,
+		(reg & 0x000003e0) >> 5,
+		(reg & 0x0000001f),
+		(reg & 0xffff0000) >> 16,
+		reg);
+
+	return reg;
+}
+
+/* TODO: Debugging func, remove */
+void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr)
+{
+	int i;
+
+	dprintk(1, "--------------------> "
+		"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
+
+	for (i = 0; i < 0x100; i += 16)
+		dprintk(1, "region0[0x%08x] = "
+			"%02x %02x %02x %02x %02x %02x %02x %02x"
+			" %02x %02x %02x %02x %02x %02x %02x %02x\n", i,
+			(u8)saa7164_readb(addr + i + 0),
+			(u8)saa7164_readb(addr + i + 1),
+			(u8)saa7164_readb(addr + i + 2),
+			(u8)saa7164_readb(addr + i + 3),
+			(u8)saa7164_readb(addr + i + 4),
+			(u8)saa7164_readb(addr + i + 5),
+			(u8)saa7164_readb(addr + i + 6),
+			(u8)saa7164_readb(addr + i + 7),
+			(u8)saa7164_readb(addr + i + 8),
+			(u8)saa7164_readb(addr + i + 9),
+			(u8)saa7164_readb(addr + i + 10),
+			(u8)saa7164_readb(addr + i + 11),
+			(u8)saa7164_readb(addr + i + 12),
+			(u8)saa7164_readb(addr + i + 13),
+			(u8)saa7164_readb(addr + i + 14),
+			(u8)saa7164_readb(addr + i + 15)
+			);
+}
+
+static void saa7164_dump_hwdesc(struct saa7164_dev *dev)
+{
+	dprintk(1, "@0x%p hwdesc sizeof(struct tmComResHWDescr) = %d bytes\n",
+		&dev->hwdesc, (u32)sizeof(struct tmComResHWDescr));
+
+	dprintk(1, " .bLength = 0x%x\n", dev->hwdesc.bLength);
+	dprintk(1, " .bDescriptorType = 0x%x\n", dev->hwdesc.bDescriptorType);
+	dprintk(1, " .bDescriptorSubtype = 0x%x\n",
+		dev->hwdesc.bDescriptorSubtype);
+
+	dprintk(1, " .bcdSpecVersion = 0x%x\n", dev->hwdesc.bcdSpecVersion);
+	dprintk(1, " .dwClockFrequency = 0x%x\n", dev->hwdesc.dwClockFrequency);
+	dprintk(1, " .dwClockUpdateRes = 0x%x\n", dev->hwdesc.dwClockUpdateRes);
+	dprintk(1, " .bCapabilities = 0x%x\n", dev->hwdesc.bCapabilities);
+	dprintk(1, " .dwDeviceRegistersLocation = 0x%x\n",
+		dev->hwdesc.dwDeviceRegistersLocation);
+
+	dprintk(1, " .dwHostMemoryRegion = 0x%x\n",
+		dev->hwdesc.dwHostMemoryRegion);
+
+	dprintk(1, " .dwHostMemoryRegionSize = 0x%x\n",
+		dev->hwdesc.dwHostMemoryRegionSize);
+
+	dprintk(1, " .dwHostHibernatMemRegion = 0x%x\n",
+		dev->hwdesc.dwHostHibernatMemRegion);
+
+	dprintk(1, " .dwHostHibernatMemRegionSize = 0x%x\n",
+		dev->hwdesc.dwHostHibernatMemRegionSize);
+}
+
+static void saa7164_dump_intfdesc(struct saa7164_dev *dev)
+{
+	dprintk(1, "@0x%p intfdesc "
+		"sizeof(struct tmComResInterfaceDescr) = %d bytes\n",
+		&dev->intfdesc, (u32)sizeof(struct tmComResInterfaceDescr));
+
+	dprintk(1, " .bLength = 0x%x\n", dev->intfdesc.bLength);
+	dprintk(1, " .bDescriptorType = 0x%x\n", dev->intfdesc.bDescriptorType);
+	dprintk(1, " .bDescriptorSubtype = 0x%x\n",
+		dev->intfdesc.bDescriptorSubtype);
+
+	dprintk(1, " .bFlags = 0x%x\n", dev->intfdesc.bFlags);
+	dprintk(1, " .bInterfaceType = 0x%x\n", dev->intfdesc.bInterfaceType);
+	dprintk(1, " .bInterfaceId = 0x%x\n", dev->intfdesc.bInterfaceId);
+	dprintk(1, " .bBaseInterface = 0x%x\n", dev->intfdesc.bBaseInterface);
+	dprintk(1, " .bInterruptId = 0x%x\n", dev->intfdesc.bInterruptId);
+	dprintk(1, " .bDebugInterruptId = 0x%x\n",
+		dev->intfdesc.bDebugInterruptId);
+
+	dprintk(1, " .BARLocation = 0x%x\n", dev->intfdesc.BARLocation);
+}
+
+static void saa7164_dump_busdesc(struct saa7164_dev *dev)
+{
+	dprintk(1, "@0x%p busdesc sizeof(struct tmComResBusDescr) = %d bytes\n",
+		&dev->busdesc, (u32)sizeof(struct tmComResBusDescr));
+
+	dprintk(1, " .CommandRing   = 0x%016Lx\n", dev->busdesc.CommandRing);
+	dprintk(1, " .ResponseRing  = 0x%016Lx\n", dev->busdesc.ResponseRing);
+	dprintk(1, " .CommandWrite  = 0x%x\n", dev->busdesc.CommandWrite);
+	dprintk(1, " .CommandRead   = 0x%x\n", dev->busdesc.CommandRead);
+	dprintk(1, " .ResponseWrite = 0x%x\n", dev->busdesc.ResponseWrite);
+	dprintk(1, " .ResponseRead  = 0x%x\n", dev->busdesc.ResponseRead);
+}
+
+/* Much of the hardware configuration and PCI registers are configured
+ * dynamically depending on firmware. We have to cache some initial
+ * structures then use these to locate other important structures
+ * from PCI space.
+ */
+static void saa7164_get_descriptors(struct saa7164_dev *dev)
+{
+	memcpy_fromio(&dev->hwdesc, dev->bmmio, sizeof(struct tmComResHWDescr));
+	memcpy_fromio(&dev->intfdesc, dev->bmmio + sizeof(struct tmComResHWDescr),
+		sizeof(struct tmComResInterfaceDescr));
+	memcpy_fromio(&dev->busdesc, dev->bmmio + dev->intfdesc.BARLocation,
+		sizeof(struct tmComResBusDescr));
+
+	if (dev->hwdesc.bLength != sizeof(struct tmComResHWDescr)) {
+		printk(KERN_ERR "Structure struct tmComResHWDescr is mangled\n");
+		printk(KERN_ERR "Need %x got %d\n", dev->hwdesc.bLength,
+			(u32)sizeof(struct tmComResHWDescr));
+	} else
+		saa7164_dump_hwdesc(dev);
+
+	if (dev->intfdesc.bLength != sizeof(struct tmComResInterfaceDescr)) {
+		printk(KERN_ERR "struct struct tmComResInterfaceDescr is mangled\n");
+		printk(KERN_ERR "Need %x got %d\n", dev->intfdesc.bLength,
+			(u32)sizeof(struct tmComResInterfaceDescr));
+	} else
+		saa7164_dump_intfdesc(dev);
+
+	saa7164_dump_busdesc(dev);
+}
+
+static int saa7164_pci_quirks(struct saa7164_dev *dev)
+{
+	return 0;
+}
+
+static int get_resources(struct saa7164_dev *dev)
+{
+	if (request_mem_region(pci_resource_start(dev->pci, 0),
+		pci_resource_len(dev->pci, 0), dev->name)) {
+
+		if (request_mem_region(pci_resource_start(dev->pci, 2),
+			pci_resource_len(dev->pci, 2), dev->name))
+			return 0;
+	}
+
+	printk(KERN_ERR "%s: can't get MMIO memory @ 0x%llx or 0x%llx\n",
+		dev->name,
+		(u64)pci_resource_start(dev->pci, 0),
+		(u64)pci_resource_start(dev->pci, 2));
+
+	return -EBUSY;
+}
+
+static int saa7164_port_init(struct saa7164_dev *dev, int portnr)
+{
+	struct saa7164_port *port = NULL;
+
+	if ((portnr < 0) || (portnr >= SAA7164_MAX_PORTS))
+		BUG();
+
+	port = &dev->ports[portnr];
+
+	port->dev = dev;
+	port->nr = portnr;
+
+	if ((portnr == SAA7164_PORT_TS1) || (portnr == SAA7164_PORT_TS2))
+		port->type = SAA7164_MPEG_DVB;
+	else
+	if ((portnr == SAA7164_PORT_ENC1) || (portnr == SAA7164_PORT_ENC2)) {
+		port->type = SAA7164_MPEG_ENCODER;
+
+		/* We need a deferred interrupt handler for cmd handling */
+		INIT_WORK(&port->workenc, saa7164_work_enchandler);
+	} else if ((portnr == SAA7164_PORT_VBI1) || (portnr == SAA7164_PORT_VBI2)) {
+		port->type = SAA7164_MPEG_VBI;
+
+		/* We need a deferred interrupt handler for cmd handling */
+		INIT_WORK(&port->workenc, saa7164_work_vbihandler);
+	} else
+		BUG();
+
+	/* Init all the critical resources */
+	mutex_init(&port->dvb.lock);
+	INIT_LIST_HEAD(&port->dmaqueue.list);
+	mutex_init(&port->dmaqueue_lock);
+
+	INIT_LIST_HEAD(&port->list_buf_used.list);
+	INIT_LIST_HEAD(&port->list_buf_free.list);
+	init_waitqueue_head(&port->wait_read);
+
+
+	saa7164_histogram_reset(&port->irq_interval, "irq intervals");
+	saa7164_histogram_reset(&port->svc_interval, "deferred intervals");
+	saa7164_histogram_reset(&port->irq_svc_interval,
+		"irq to deferred intervals");
+	saa7164_histogram_reset(&port->read_interval,
+		"encoder/vbi read() intervals");
+	saa7164_histogram_reset(&port->poll_interval,
+		"encoder/vbi poll() intervals");
+
+	return 0;
+}
+
+static int saa7164_dev_setup(struct saa7164_dev *dev)
+{
+	int i;
+
+	mutex_init(&dev->lock);
+	atomic_inc(&dev->refcount);
+	dev->nr = saa7164_devcount++;
+
+	snprintf(dev->name, sizeof(dev->name), "saa7164[%d]", dev->nr);
+
+	mutex_lock(&devlist);
+	list_add_tail(&dev->devlist, &saa7164_devlist);
+	mutex_unlock(&devlist);
+
+	/* board config */
+	dev->board = UNSET;
+	if (card[dev->nr] < saa7164_bcount)
+		dev->board = card[dev->nr];
+
+	for (i = 0; UNSET == dev->board  &&  i < saa7164_idcount; i++)
+		if (dev->pci->subsystem_vendor == saa7164_subids[i].subvendor &&
+			dev->pci->subsystem_device ==
+				saa7164_subids[i].subdevice)
+				dev->board = saa7164_subids[i].card;
+
+	if (UNSET == dev->board) {
+		dev->board = SAA7164_BOARD_UNKNOWN;
+		saa7164_card_list(dev);
+	}
+
+	dev->pci_bus  = dev->pci->bus->number;
+	dev->pci_slot = PCI_SLOT(dev->pci->devfn);
+
+	/* I2C Defaults / setup */
+	dev->i2c_bus[0].dev = dev;
+	dev->i2c_bus[0].nr = 0;
+	dev->i2c_bus[1].dev = dev;
+	dev->i2c_bus[1].nr = 1;
+	dev->i2c_bus[2].dev = dev;
+	dev->i2c_bus[2].nr = 2;
+
+	/* Transport + Encoder ports 1, 2, 3, 4 - Defaults / setup */
+	saa7164_port_init(dev, SAA7164_PORT_TS1);
+	saa7164_port_init(dev, SAA7164_PORT_TS2);
+	saa7164_port_init(dev, SAA7164_PORT_ENC1);
+	saa7164_port_init(dev, SAA7164_PORT_ENC2);
+	saa7164_port_init(dev, SAA7164_PORT_VBI1);
+	saa7164_port_init(dev, SAA7164_PORT_VBI2);
+
+	if (get_resources(dev) < 0) {
+		printk(KERN_ERR "CORE %s No more PCIe resources for "
+		       "subsystem: %04x:%04x\n",
+		       dev->name, dev->pci->subsystem_vendor,
+		       dev->pci->subsystem_device);
+
+		saa7164_devcount--;
+		return -ENODEV;
+	}
+
+	/* PCI/e allocations */
+	dev->lmmio = ioremap(pci_resource_start(dev->pci, 0),
+			     pci_resource_len(dev->pci, 0));
+
+	dev->lmmio2 = ioremap(pci_resource_start(dev->pci, 2),
+			     pci_resource_len(dev->pci, 2));
+
+	dev->bmmio = (u8 __iomem *)dev->lmmio;
+	dev->bmmio2 = (u8 __iomem *)dev->lmmio2;
+
+	/* Inerrupt and ack register locations offset of bmmio */
+	dev->int_status = 0x183000 + 0xf80;
+	dev->int_ack = 0x183000 + 0xf90;
+
+	printk(KERN_INFO
+		"CORE %s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n",
+	       dev->name, dev->pci->subsystem_vendor,
+	       dev->pci->subsystem_device, saa7164_boards[dev->board].name,
+	       dev->board, card[dev->nr] == dev->board ?
+	       "insmod option" : "autodetected");
+
+	saa7164_pci_quirks(dev);
+
+	return 0;
+}
+
+static void saa7164_dev_unregister(struct saa7164_dev *dev)
+{
+	dprintk(1, "%s()\n", __func__);
+
+	release_mem_region(pci_resource_start(dev->pci, 0),
+		pci_resource_len(dev->pci, 0));
+
+	release_mem_region(pci_resource_start(dev->pci, 2),
+		pci_resource_len(dev->pci, 2));
+
+	if (!atomic_dec_and_test(&dev->refcount))
+		return;
+
+	iounmap(dev->lmmio);
+	iounmap(dev->lmmio2);
+
+	return;
+}
+
+#ifdef CONFIG_PROC_FS
+static int saa7164_proc_show(struct seq_file *m, void *v)
+{
+	struct saa7164_dev *dev;
+	struct tmComResBusInfo *b;
+	struct list_head *list;
+	int i, c;
+
+	if (saa7164_devcount == 0)
+		return 0;
+
+	list_for_each(list, &saa7164_devlist) {
+		dev = list_entry(list, struct saa7164_dev, devlist);
+		seq_printf(m, "%s = %p\n", dev->name, dev);
+
+		/* Lock the bus from any other access */
+		b = &dev->bus;
+		mutex_lock(&b->lock);
+
+		seq_printf(m, " .m_pdwSetWritePos = 0x%x (0x%08x)\n",
+			b->m_dwSetReadPos, saa7164_readl(b->m_dwSetReadPos));
+
+		seq_printf(m, " .m_pdwSetReadPos  = 0x%x (0x%08x)\n",
+			b->m_dwSetWritePos, saa7164_readl(b->m_dwSetWritePos));
+
+		seq_printf(m, " .m_pdwGetWritePos = 0x%x (0x%08x)\n",
+			b->m_dwGetReadPos, saa7164_readl(b->m_dwGetReadPos));
+
+		seq_printf(m, " .m_pdwGetReadPos  = 0x%x (0x%08x)\n",
+			b->m_dwGetWritePos, saa7164_readl(b->m_dwGetWritePos));
+		c = 0;
+		seq_printf(m, "\n  Set Ring:\n");
+		seq_printf(m, "\n addr  00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
+		for (i = 0; i < b->m_dwSizeSetRing; i++) {
+			if (c == 0)
+				seq_printf(m, " %04x:", i);
+
+			seq_printf(m, " %02x", readb(b->m_pdwSetRing + i));
+
+			if (++c == 16) {
+				seq_printf(m, "\n");
+				c = 0;
+			}
+		}
+
+		c = 0;
+		seq_printf(m, "\n  Get Ring:\n");
+		seq_printf(m, "\n addr  00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
+		for (i = 0; i < b->m_dwSizeGetRing; i++) {
+			if (c == 0)
+				seq_printf(m, " %04x:", i);
+
+			seq_printf(m, " %02x", readb(b->m_pdwGetRing + i));
+
+			if (++c == 16) {
+				seq_printf(m, "\n");
+				c = 0;
+			}
+		}
+
+		mutex_unlock(&b->lock);
+
+	}
+
+	return 0;
+}
+
+static int saa7164_proc_open(struct inode *inode, struct file *filp)
+{
+	return single_open(filp, saa7164_proc_show, NULL);
+}
+
+static const struct file_operations saa7164_proc_fops = {
+	.open		= saa7164_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int saa7164_proc_create(void)
+{
+	struct proc_dir_entry *pe;
+
+	pe = proc_create("saa7164", S_IRUGO, NULL, &saa7164_proc_fops);
+	if (!pe)
+		return -ENOMEM;
+
+	return 0;
+}
+#endif
+
+static int saa7164_thread_function(void *data)
+{
+	struct saa7164_dev *dev = data;
+	struct tmFwInfoStruct fwinfo;
+	u64 last_poll_time = 0;
+
+	dprintk(DBGLVL_THR, "thread started\n");
+
+	set_freezable();
+
+	while (1) {
+		msleep_interruptible(100);
+		if (kthread_should_stop())
+			break;
+		try_to_freeze();
+
+		dprintk(DBGLVL_THR, "thread running\n");
+
+		/* Dump the firmware debug message to console */
+		/* Polling this costs us 1-2% of the arm CPU */
+		/* convert this into a respnde to interrupt 0x7a */
+		saa7164_api_collect_debug(dev);
+
+		/* Monitor CPU load every 1 second */
+		if ((last_poll_time + 1000 /* ms */) < jiffies_to_msecs(jiffies)) {
+			saa7164_api_get_load_info(dev, &fwinfo);
+			last_poll_time = jiffies_to_msecs(jiffies);
+		}
+
+	}
+
+	dprintk(DBGLVL_THR, "thread exiting\n");
+	return 0;
+}
+
+static bool saa7164_enable_msi(struct pci_dev *pci_dev, struct saa7164_dev *dev)
+{
+	int err;
+
+	if (!enable_msi) {
+		printk(KERN_WARNING "%s() MSI disabled by module parameter 'enable_msi'"
+		       , __func__);
+		return false;
+	}
+
+	err = pci_enable_msi(pci_dev);
+
+	if (err) {
+		printk(KERN_ERR "%s() Failed to enable MSI interrupt."
+			" Falling back to a shared IRQ\n", __func__);
+		return false;
+	}
+
+	/* no error - so request an msi interrupt */
+	err = request_irq(pci_dev->irq, saa7164_irq, 0,
+						dev->name, dev);
+
+	if (err) {
+		/* fall back to legacy interrupt */
+		printk(KERN_ERR "%s() Failed to get an MSI interrupt."
+		       " Falling back to a shared IRQ\n", __func__);
+		pci_disable_msi(pci_dev);
+		return false;
+	}
+
+	return true;
+}
+
+static int saa7164_initdev(struct pci_dev *pci_dev,
+			   const struct pci_device_id *pci_id)
+{
+	struct saa7164_dev *dev;
+	int err, i;
+	u32 version;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (NULL == dev)
+		return -ENOMEM;
+
+	err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
+	if (err < 0) {
+		dev_err(&pci_dev->dev, "v4l2_device_register failed\n");
+		goto fail_free;
+	}
+
+	/* pci init */
+	dev->pci = pci_dev;
+	if (pci_enable_device(pci_dev)) {
+		err = -EIO;
+		goto fail_free;
+	}
+
+	if (saa7164_dev_setup(dev) < 0) {
+		err = -EINVAL;
+		goto fail_free;
+	}
+
+	/* print pci info */
+	dev->pci_rev = pci_dev->revision;
+	pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER,  &dev->pci_lat);
+	printk(KERN_INFO "%s/0: found at %s, rev: %d, irq: %d, "
+	       "latency: %d, mmio: 0x%llx\n", dev->name,
+	       pci_name(pci_dev), dev->pci_rev, pci_dev->irq,
+	       dev->pci_lat,
+		(unsigned long long)pci_resource_start(pci_dev, 0));
+
+	pci_set_master(pci_dev);
+	/* TODO */
+	err = pci_set_dma_mask(pci_dev, 0xffffffff);
+	if (err) {
+		printk("%s/0: Oops: no 32bit PCI DMA ???\n", dev->name);
+		goto fail_irq;
+	}
+
+	/* irq bit */
+	if (saa7164_enable_msi(pci_dev, dev)) {
+		dev->msi = true;
+	} else {
+		/* if we have an error (i.e. we don't have an interrupt)
+			 or msi is not enabled - fallback to shared interrupt */
+
+		err = request_irq(pci_dev->irq, saa7164_irq,
+				IRQF_SHARED, dev->name, dev);
+
+		if (err < 0) {
+			printk(KERN_ERR "%s: can't get IRQ %d\n", dev->name,
+			       pci_dev->irq);
+			err = -EIO;
+			goto fail_irq;
+		}
+	}
+
+	pci_set_drvdata(pci_dev, dev);
+
+	/* Init the internal command list */
+	for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) {
+		dev->cmds[i].seqno = i;
+		dev->cmds[i].inuse = 0;
+		mutex_init(&dev->cmds[i].lock);
+		init_waitqueue_head(&dev->cmds[i].wait);
+	}
+
+	/* We need a deferred interrupt handler for cmd handling */
+	INIT_WORK(&dev->workcmd, saa7164_work_cmdhandler);
+
+	/* Only load the firmware if we know the board */
+	if (dev->board != SAA7164_BOARD_UNKNOWN) {
+
+		err = saa7164_downloadfirmware(dev);
+		if (err < 0) {
+			printk(KERN_ERR
+				"Failed to boot firmware, no features "
+				"registered\n");
+			goto fail_fw;
+		}
+
+		saa7164_get_descriptors(dev);
+		saa7164_dumpregs(dev, 0);
+		saa7164_getcurrentfirmwareversion(dev);
+		saa7164_getfirmwarestatus(dev);
+		err = saa7164_bus_setup(dev);
+		if (err < 0)
+			printk(KERN_ERR
+				"Failed to setup the bus, will continue\n");
+		saa7164_bus_dump(dev);
+
+		/* Ping the running firmware via the command bus and get the
+		 * firmware version, this checks the bus is running OK.
+		 */
+		version = 0;
+		if (saa7164_api_get_fw_version(dev, &version) == SAA_OK)
+			dprintk(1, "Bus is operating correctly using "
+				"version %d.%d.%d.%d (0x%x)\n",
+				(version & 0x0000fc00) >> 10,
+				(version & 0x000003e0) >> 5,
+				(version & 0x0000001f),
+				(version & 0xffff0000) >> 16,
+				version);
+		else
+			printk(KERN_ERR
+				"Failed to communicate with the firmware\n");
+
+		/* Bring up the I2C buses */
+		saa7164_i2c_register(&dev->i2c_bus[0]);
+		saa7164_i2c_register(&dev->i2c_bus[1]);
+		saa7164_i2c_register(&dev->i2c_bus[2]);
+		saa7164_gpio_setup(dev);
+		saa7164_card_setup(dev);
+
+		/* Parse the dynamic device configuration, find various
+		 * media endpoints (MPEG, WMV, PS, TS) and cache their
+		 * configuration details into the driver, so we can
+		 * reference them later during simething_register() func,
+		 * interrupt handlers, deferred work handlers etc.
+		 */
+		saa7164_api_enum_subdevs(dev);
+
+		/* Begin to create the video sub-systems and register funcs */
+		if (saa7164_boards[dev->board].porta == SAA7164_MPEG_DVB) {
+			if (saa7164_dvb_register(&dev->ports[SAA7164_PORT_TS1]) < 0) {
+				printk(KERN_ERR "%s() Failed to register "
+					"dvb adapters on porta\n",
+					__func__);
+			}
+		}
+
+		if (saa7164_boards[dev->board].portb == SAA7164_MPEG_DVB) {
+			if (saa7164_dvb_register(&dev->ports[SAA7164_PORT_TS2]) < 0) {
+				printk(KERN_ERR"%s() Failed to register "
+					"dvb adapters on portb\n",
+					__func__);
+			}
+		}
+
+		if (saa7164_boards[dev->board].portc == SAA7164_MPEG_ENCODER) {
+			if (saa7164_encoder_register(&dev->ports[SAA7164_PORT_ENC1]) < 0) {
+				printk(KERN_ERR"%s() Failed to register "
+					"mpeg encoder\n", __func__);
+			}
+		}
+
+		if (saa7164_boards[dev->board].portd == SAA7164_MPEG_ENCODER) {
+			if (saa7164_encoder_register(&dev->ports[SAA7164_PORT_ENC2]) < 0) {
+				printk(KERN_ERR"%s() Failed to register "
+					"mpeg encoder\n", __func__);
+			}
+		}
+
+		if (saa7164_boards[dev->board].porte == SAA7164_MPEG_VBI) {
+			if (saa7164_vbi_register(&dev->ports[SAA7164_PORT_VBI1]) < 0) {
+				printk(KERN_ERR"%s() Failed to register "
+					"vbi device\n", __func__);
+			}
+		}
+
+		if (saa7164_boards[dev->board].portf == SAA7164_MPEG_VBI) {
+			if (saa7164_vbi_register(&dev->ports[SAA7164_PORT_VBI2]) < 0) {
+				printk(KERN_ERR"%s() Failed to register "
+					"vbi device\n", __func__);
+			}
+		}
+		saa7164_api_set_debug(dev, fw_debug);
+
+		if (fw_debug) {
+			dev->kthread = kthread_run(saa7164_thread_function, dev,
+				"saa7164 debug");
+			if (IS_ERR(dev->kthread)) {
+				dev->kthread = NULL;
+				printk(KERN_ERR "%s() Failed to create "
+					"debug kernel thread\n", __func__);
+			}
+		}
+
+	} /* != BOARD_UNKNOWN */
+	else
+		printk(KERN_ERR "%s() Unsupported board detected, "
+			"registering without firmware\n", __func__);
+
+	dprintk(1, "%s() parameter debug = %d\n", __func__, saa_debug);
+	dprintk(1, "%s() parameter waitsecs = %d\n", __func__, waitsecs);
+
+fail_fw:
+	return 0;
+
+fail_irq:
+	saa7164_dev_unregister(dev);
+fail_free:
+	v4l2_device_unregister(&dev->v4l2_dev);
+	kfree(dev);
+	return err;
+}
+
+static void saa7164_shutdown(struct saa7164_dev *dev)
+{
+	dprintk(1, "%s()\n", __func__);
+}
+
+static void saa7164_finidev(struct pci_dev *pci_dev)
+{
+	struct saa7164_dev *dev = pci_get_drvdata(pci_dev);
+
+	if (dev->board != SAA7164_BOARD_UNKNOWN) {
+		if (fw_debug && dev->kthread) {
+			kthread_stop(dev->kthread);
+			dev->kthread = NULL;
+		}
+		if (dev->firmwareloaded)
+			saa7164_api_set_debug(dev, 0x00);
+	}
+
+	saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1],
+		&dev->ports[SAA7164_PORT_ENC1].irq_interval);
+	saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1],
+		&dev->ports[SAA7164_PORT_ENC1].svc_interval);
+	saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1],
+		&dev->ports[SAA7164_PORT_ENC1].irq_svc_interval);
+	saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1],
+		&dev->ports[SAA7164_PORT_ENC1].read_interval);
+	saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1],
+		&dev->ports[SAA7164_PORT_ENC1].poll_interval);
+	saa7164_histogram_print(&dev->ports[SAA7164_PORT_VBI1],
+		&dev->ports[SAA7164_PORT_VBI1].read_interval);
+	saa7164_histogram_print(&dev->ports[SAA7164_PORT_VBI2],
+		&dev->ports[SAA7164_PORT_VBI2].poll_interval);
+
+	saa7164_shutdown(dev);
+
+	if (saa7164_boards[dev->board].porta == SAA7164_MPEG_DVB)
+		saa7164_dvb_unregister(&dev->ports[SAA7164_PORT_TS1]);
+
+	if (saa7164_boards[dev->board].portb == SAA7164_MPEG_DVB)
+		saa7164_dvb_unregister(&dev->ports[SAA7164_PORT_TS2]);
+
+	if (saa7164_boards[dev->board].portc == SAA7164_MPEG_ENCODER)
+		saa7164_encoder_unregister(&dev->ports[SAA7164_PORT_ENC1]);
+
+	if (saa7164_boards[dev->board].portd == SAA7164_MPEG_ENCODER)
+		saa7164_encoder_unregister(&dev->ports[SAA7164_PORT_ENC2]);
+
+	if (saa7164_boards[dev->board].porte == SAA7164_MPEG_VBI)
+		saa7164_vbi_unregister(&dev->ports[SAA7164_PORT_VBI1]);
+
+	if (saa7164_boards[dev->board].portf == SAA7164_MPEG_VBI)
+		saa7164_vbi_unregister(&dev->ports[SAA7164_PORT_VBI2]);
+
+	saa7164_i2c_unregister(&dev->i2c_bus[0]);
+	saa7164_i2c_unregister(&dev->i2c_bus[1]);
+	saa7164_i2c_unregister(&dev->i2c_bus[2]);
+
+	/* unregister stuff */
+	free_irq(pci_dev->irq, dev);
+
+	if (dev->msi) {
+		pci_disable_msi(pci_dev);
+		dev->msi = false;
+	}
+
+	pci_disable_device(pci_dev);
+
+	mutex_lock(&devlist);
+	list_del(&dev->devlist);
+	mutex_unlock(&devlist);
+
+	saa7164_dev_unregister(dev);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	kfree(dev);
+}
+
+static struct pci_device_id saa7164_pci_tbl[] = {
+	{
+		/* SAA7164 */
+		.vendor       = 0x1131,
+		.device       = 0x7164,
+		.subvendor    = PCI_ANY_ID,
+		.subdevice    = PCI_ANY_ID,
+	}, {
+		/* --- end of list --- */
+	}
+};
+MODULE_DEVICE_TABLE(pci, saa7164_pci_tbl);
+
+static struct pci_driver saa7164_pci_driver = {
+	.name     = "saa7164",
+	.id_table = saa7164_pci_tbl,
+	.probe    = saa7164_initdev,
+	.remove   = saa7164_finidev,
+	/* TODO */
+	.suspend  = NULL,
+	.resume   = NULL,
+};
+
+static int __init saa7164_init(void)
+{
+	printk(KERN_INFO "saa7164 driver loaded\n");
+
+#ifdef CONFIG_PROC_FS
+	saa7164_proc_create();
+#endif
+	return pci_register_driver(&saa7164_pci_driver);
+}
+
+static void __exit saa7164_fini(void)
+{
+#ifdef CONFIG_PROC_FS
+	remove_proc_entry("saa7164", NULL);
+#endif
+	pci_unregister_driver(&saa7164_pci_driver);
+}
+
+module_init(saa7164_init);
+module_exit(saa7164_fini);
+
diff --git a/drivers/media/pci/saa7164/saa7164-dvb.c b/drivers/media/pci/saa7164/saa7164-dvb.c
new file mode 100644
index 0000000..e9a783b
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-dvb.c
@@ -0,0 +1,763 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "saa7164.h"
+
+#include "tda10048.h"
+#include "tda18271.h"
+#include "s5h1411.h"
+#include "si2157.h"
+#include "si2168.h"
+#include "lgdt3306a.h"
+
+#define DRIVER_NAME "saa7164"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+/* addr is in the card struct, get it from there */
+static struct tda10048_config hauppauge_hvr2200_1_config = {
+	.demod_address    = 0x10 >> 1,
+	.output_mode      = TDA10048_SERIAL_OUTPUT,
+	.fwbulkwritelen   = TDA10048_BULKWRITE_200,
+	.inversion        = TDA10048_INVERSION_ON,
+	.dtv6_if_freq_khz = TDA10048_IF_3300,
+	.dtv7_if_freq_khz = TDA10048_IF_3500,
+	.dtv8_if_freq_khz = TDA10048_IF_4000,
+	.clk_freq_khz     = TDA10048_CLK_16000,
+};
+static struct tda10048_config hauppauge_hvr2200_2_config = {
+	.demod_address    = 0x12 >> 1,
+	.output_mode      = TDA10048_SERIAL_OUTPUT,
+	.fwbulkwritelen   = TDA10048_BULKWRITE_200,
+	.inversion        = TDA10048_INVERSION_ON,
+	.dtv6_if_freq_khz = TDA10048_IF_3300,
+	.dtv7_if_freq_khz = TDA10048_IF_3500,
+	.dtv8_if_freq_khz = TDA10048_IF_4000,
+	.clk_freq_khz     = TDA10048_CLK_16000,
+};
+
+static struct tda18271_std_map hauppauge_tda18271_std_map = {
+	.atsc_6   = { .if_freq = 3250, .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_hvr22x0_tuner_config = {
+	.std_map	= &hauppauge_tda18271_std_map,
+	.gate		= TDA18271_GATE_ANALOG,
+	.role		= TDA18271_MASTER,
+};
+
+static struct tda18271_config hauppauge_hvr22x0s_tuner_config = {
+	.std_map	= &hauppauge_tda18271_std_map,
+	.gate		= TDA18271_GATE_ANALOG,
+	.role		= TDA18271_SLAVE,
+	.output_opt     = TDA18271_OUTPUT_LT_OFF,
+	.rf_cal_on_startup = 1
+};
+
+static struct s5h1411_config hauppauge_s5h1411_config = {
+	.output_mode   = S5H1411_SERIAL_OUTPUT,
+	.gpio          = S5H1411_GPIO_ON,
+	.qam_if        = S5H1411_IF_4000,
+	.vsb_if        = S5H1411_IF_3250,
+	.inversion     = S5H1411_INVERSION_ON,
+	.status_mode   = S5H1411_DEMODLOCKING,
+	.mpeg_timing   = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK,
+};
+
+static struct lgdt3306a_config hauppauge_hvr2255a_config = {
+	.i2c_addr               = 0xb2 >> 1,
+	.qam_if_khz             = 4000,
+	.vsb_if_khz             = 3250,
+	.deny_i2c_rptr          = 1, /* Disabled */
+	.spectral_inversion     = 0, /* Disabled */
+	.mpeg_mode              = LGDT3306A_MPEG_SERIAL,
+	.tpclk_edge             = LGDT3306A_TPCLK_RISING_EDGE,
+	.tpvalid_polarity       = LGDT3306A_TP_VALID_HIGH,
+	.xtalMHz                = 25, /* 24 or 25 */
+};
+
+static struct lgdt3306a_config hauppauge_hvr2255b_config = {
+	.i2c_addr               = 0x1c >> 1,
+	.qam_if_khz             = 4000,
+	.vsb_if_khz             = 3250,
+	.deny_i2c_rptr          = 1, /* Disabled */
+	.spectral_inversion     = 0, /* Disabled */
+	.mpeg_mode              = LGDT3306A_MPEG_SERIAL,
+	.tpclk_edge             = LGDT3306A_TPCLK_RISING_EDGE,
+	.tpvalid_polarity       = LGDT3306A_TP_VALID_HIGH,
+	.xtalMHz                = 25, /* 24 or 25 */
+};
+
+static struct si2157_config hauppauge_hvr2255_tuner_config = {
+	.inversion = 1,
+	.if_port = 1,
+};
+
+static int si2157_attach(struct saa7164_port *port, struct i2c_adapter *adapter,
+	struct dvb_frontend *fe, u8 addr8bit, struct si2157_config *cfg)
+{
+	struct i2c_board_info bi;
+	struct i2c_client *tuner;
+
+	cfg->fe = fe;
+
+	memset(&bi, 0, sizeof(bi));
+
+	strlcpy(bi.type, "si2157", I2C_NAME_SIZE);
+	bi.platform_data = cfg;
+	bi.addr = addr8bit >> 1;
+
+	request_module(bi.type);
+
+	tuner = i2c_new_device(adapter, &bi);
+	if (tuner == NULL || tuner->dev.driver == NULL)
+		return -ENODEV;
+
+	if (!try_module_get(tuner->dev.driver->owner)) {
+		i2c_unregister_device(tuner);
+		return -ENODEV;
+	}
+
+	port->i2c_client_tuner = tuner;
+
+	return 0;
+}
+
+static int saa7164_dvb_stop_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() stop transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_DVB, "%s()    Stopped\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int saa7164_dvb_acquire_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() acquire transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_DVB, "%s() Acquired\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int saa7164_dvb_pause_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() pause transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_DVB, "%s()   Paused\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+/* Firmware is very windows centric, meaning you have to transition
+ * the part through AVStream / KS Windows stages, forwards or backwards.
+ * States are: stopped, acquired (h/w), paused, started.
+ */
+static int saa7164_dvb_stop_streaming(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct list_head *p, *q;
+	int ret;
+
+	dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
+
+	ret = saa7164_dvb_pause_port(port);
+	ret = saa7164_dvb_acquire_port(port);
+	ret = saa7164_dvb_stop_port(port);
+
+	/* Mark the hardware buffers as free */
+	mutex_lock(&port->dmaqueue_lock);
+	list_for_each_safe(p, q, &port->dmaqueue.list) {
+		buf = list_entry(p, struct saa7164_buffer, list);
+		buf->flags = SAA7164_BUFFER_FREE;
+	}
+	mutex_unlock(&port->dmaqueue_lock);
+
+	return ret;
+}
+
+static int saa7164_dvb_start_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret = 0, result;
+
+	dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
+
+	saa7164_buffer_cfg_port(port);
+
+	/* Acquire the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() acquire transition failed, res = 0x%x\n",
+			__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+		if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+			printk(KERN_ERR "%s() acquire/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+		ret = -EIO;
+		goto out;
+	} else
+		dprintk(DBGLVL_DVB, "%s()   Acquired\n", __func__);
+
+	/* Pause the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() pause transition failed, res = 0x%x\n",
+				__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+		if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+			printk(KERN_ERR "%s() pause/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+
+		ret = -EIO;
+		goto out;
+	} else
+		dprintk(DBGLVL_DVB, "%s()   Paused\n", __func__);
+
+	/* Start the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_RUN);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() run transition failed, result = 0x%x\n",
+				__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+		if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+			printk(KERN_ERR "%s() run/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+
+		ret = -EIO;
+	} else
+		dprintk(DBGLVL_DVB, "%s()   Running\n", __func__);
+
+out:
+	return ret;
+}
+
+static int saa7164_dvb_start_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct saa7164_port *port = (struct saa7164_port *) demux->priv;
+	struct saa7164_dvb *dvb = &port->dvb;
+	struct saa7164_dev *dev = port->dev;
+	int ret = 0;
+
+	dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
+
+	if (!demux->dmx.frontend)
+		return -EINVAL;
+
+	if (dvb) {
+		mutex_lock(&dvb->lock);
+		if (dvb->feeding++ == 0) {
+			/* Start transport */
+			ret = saa7164_dvb_start_port(port);
+		}
+		mutex_unlock(&dvb->lock);
+		dprintk(DBGLVL_DVB, "%s(port=%d) now feeding = %d\n",
+			__func__, port->nr, dvb->feeding);
+	}
+
+	return ret;
+}
+
+static int saa7164_dvb_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct saa7164_port *port = (struct saa7164_port *) demux->priv;
+	struct saa7164_dvb *dvb = &port->dvb;
+	struct saa7164_dev *dev = port->dev;
+	int ret = 0;
+
+	dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
+
+	if (dvb) {
+		mutex_lock(&dvb->lock);
+		if (--dvb->feeding == 0) {
+			/* Stop transport */
+			ret = saa7164_dvb_stop_streaming(port);
+		}
+		mutex_unlock(&dvb->lock);
+		dprintk(DBGLVL_DVB, "%s(port=%d) now feeding = %d\n",
+			__func__, port->nr, dvb->feeding);
+	}
+
+	return ret;
+}
+
+static int dvb_register(struct saa7164_port *port)
+{
+	struct saa7164_dvb *dvb = &port->dvb;
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	int result, i;
+
+	dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
+
+	if (port->type != SAA7164_MPEG_DVB)
+		BUG();
+
+	/* Sanity check that the PCI configuration space is active */
+	if (port->hwcfg.BARLocation == 0) {
+		result = -ENOMEM;
+		printk(KERN_ERR "%s: dvb_register_adapter failed "
+		       "(errno = %d), NO PCI configuration\n",
+			DRIVER_NAME, result);
+		goto fail_adapter;
+	}
+
+	/* Init and establish defaults */
+	port->hw_streamingparams.bitspersample = 8;
+	port->hw_streamingparams.samplesperline = 188;
+	port->hw_streamingparams.numberoflines =
+		(SAA7164_TS_NUMBER_OF_LINES * 188) / 188;
+
+	port->hw_streamingparams.pitch = 188;
+	port->hw_streamingparams.linethreshold = 0;
+	port->hw_streamingparams.pagetablelistvirt = NULL;
+	port->hw_streamingparams.pagetablelistphys = NULL;
+	port->hw_streamingparams.numpagetables = 2 +
+		((SAA7164_TS_NUMBER_OF_LINES * 188) / PAGE_SIZE);
+
+	port->hw_streamingparams.numpagetableentries = port->hwcfg.buffercount;
+
+	/* Allocate the PCI resources */
+	for (i = 0; i < port->hwcfg.buffercount; i++) {
+		buf = saa7164_buffer_alloc(port,
+			port->hw_streamingparams.numberoflines *
+			port->hw_streamingparams.pitch);
+
+		if (!buf) {
+			result = -ENOMEM;
+			printk(KERN_ERR "%s: dvb_register_adapter failed "
+			       "(errno = %d), unable to allocate buffers\n",
+				DRIVER_NAME, result);
+			goto fail_adapter;
+		}
+
+		mutex_lock(&port->dmaqueue_lock);
+		list_add_tail(&buf->list, &port->dmaqueue.list);
+		mutex_unlock(&port->dmaqueue_lock);
+	}
+
+	/* register adapter */
+	result = dvb_register_adapter(&dvb->adapter, DRIVER_NAME, THIS_MODULE,
+			&dev->pci->dev, adapter_nr);
+	if (result < 0) {
+		printk(KERN_ERR "%s: dvb_register_adapter failed "
+		       "(errno = %d)\n", DRIVER_NAME, result);
+		goto fail_adapter;
+	}
+	dvb->adapter.priv = port;
+
+	/* register frontend */
+	result = dvb_register_frontend(&dvb->adapter, dvb->frontend);
+	if (result < 0) {
+		printk(KERN_ERR "%s: dvb_register_frontend failed "
+		       "(errno = %d)\n", DRIVER_NAME, result);
+		goto fail_frontend;
+	}
+
+	/* register demux stuff */
+	dvb->demux.dmx.capabilities =
+		DMX_TS_FILTERING | DMX_SECTION_FILTERING |
+		DMX_MEMORY_BASED_FILTERING;
+	dvb->demux.priv       = port;
+	dvb->demux.filternum  = 256;
+	dvb->demux.feednum    = 256;
+	dvb->demux.start_feed = saa7164_dvb_start_feed;
+	dvb->demux.stop_feed  = saa7164_dvb_stop_feed;
+	result = dvb_dmx_init(&dvb->demux);
+	if (result < 0) {
+		printk(KERN_ERR "%s: dvb_dmx_init failed (errno = %d)\n",
+		       DRIVER_NAME, result);
+		goto fail_dmx;
+	}
+
+	dvb->dmxdev.filternum    = 256;
+	dvb->dmxdev.demux        = &dvb->demux.dmx;
+	dvb->dmxdev.capabilities = 0;
+	result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
+	if (result < 0) {
+		printk(KERN_ERR "%s: dvb_dmxdev_init failed (errno = %d)\n",
+		       DRIVER_NAME, result);
+		goto fail_dmxdev;
+	}
+
+	dvb->fe_hw.source = DMX_FRONTEND_0;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		printk(KERN_ERR "%s: add_frontend failed "
+		       "(DMX_FRONTEND_0, errno = %d)\n", DRIVER_NAME, result);
+		goto fail_fe_hw;
+	}
+
+	dvb->fe_mem.source = DMX_MEMORY_FE;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	if (result < 0) {
+		printk(KERN_ERR "%s: add_frontend failed "
+		       "(DMX_MEMORY_FE, errno = %d)\n", DRIVER_NAME, result);
+		goto fail_fe_mem;
+	}
+
+	result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		printk(KERN_ERR "%s: connect_frontend failed (errno = %d)\n",
+		       DRIVER_NAME, result);
+		goto fail_fe_conn;
+	}
+
+	/* register network adapter */
+	dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx);
+	return 0;
+
+fail_fe_conn:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+fail_fe_mem:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+fail_fe_hw:
+	dvb_dmxdev_release(&dvb->dmxdev);
+fail_dmxdev:
+	dvb_dmx_release(&dvb->demux);
+fail_dmx:
+	dvb_unregister_frontend(dvb->frontend);
+fail_frontend:
+	dvb_frontend_detach(dvb->frontend);
+	dvb_unregister_adapter(&dvb->adapter);
+fail_adapter:
+	return result;
+}
+
+int saa7164_dvb_unregister(struct saa7164_port *port)
+{
+	struct saa7164_dvb *dvb = &port->dvb;
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *b;
+	struct list_head *c, *n;
+	struct i2c_client *client;
+
+	dprintk(DBGLVL_DVB, "%s()\n", __func__);
+
+	if (port->type != SAA7164_MPEG_DVB)
+		BUG();
+
+	/* Remove any allocated buffers */
+	mutex_lock(&port->dmaqueue_lock);
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+		b = list_entry(c, struct saa7164_buffer, list);
+		list_del(c);
+		saa7164_buffer_dealloc(b);
+	}
+	mutex_unlock(&port->dmaqueue_lock);
+
+	if (dvb->frontend == NULL)
+		return 0;
+
+	/* remove I2C client for tuner */
+	client = port->i2c_client_tuner;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	/* remove I2C client for demodulator */
+	client = port->i2c_client_demod;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	dvb_net_release(&dvb->net);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	dvb_dmxdev_release(&dvb->dmxdev);
+	dvb_dmx_release(&dvb->demux);
+	dvb_unregister_frontend(dvb->frontend);
+	dvb_frontend_detach(dvb->frontend);
+	dvb_unregister_adapter(&dvb->adapter);
+	return 0;
+}
+
+/* All the DVB attach calls go here, this function get's modified
+ * for each new card.
+ */
+int saa7164_dvb_register(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_dvb *dvb = &port->dvb;
+	struct saa7164_i2c *i2c_bus = NULL;
+	struct si2168_config si2168_config;
+	struct si2157_config si2157_config;
+	struct i2c_adapter *adapter;
+	struct i2c_board_info info;
+	struct i2c_client *client_demod;
+	struct i2c_client *client_tuner;
+	int ret;
+
+	dprintk(DBGLVL_DVB, "%s()\n", __func__);
+
+	/* init frontend */
+	switch (dev->board) {
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_2:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_3:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_4:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2200_5:
+		i2c_bus = &dev->i2c_bus[port->nr + 1];
+		switch (port->nr) {
+		case 0:
+			port->dvb.frontend = dvb_attach(tda10048_attach,
+				&hauppauge_hvr2200_1_config,
+				&i2c_bus->i2c_adap);
+
+			if (port->dvb.frontend != NULL) {
+				/* TODO: addr is in the card struct */
+				dvb_attach(tda18271_attach, port->dvb.frontend,
+					0xc0 >> 1, &i2c_bus->i2c_adap,
+					&hauppauge_hvr22x0_tuner_config);
+			}
+
+			break;
+		case 1:
+			port->dvb.frontend = dvb_attach(tda10048_attach,
+				&hauppauge_hvr2200_2_config,
+				&i2c_bus->i2c_adap);
+
+			if (port->dvb.frontend != NULL) {
+				/* TODO: addr is in the card struct */
+				dvb_attach(tda18271_attach, port->dvb.frontend,
+					0xc0 >> 1, &i2c_bus->i2c_adap,
+					&hauppauge_hvr22x0s_tuner_config);
+			}
+
+			break;
+		}
+		break;
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250_2:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2250_3:
+		i2c_bus = &dev->i2c_bus[port->nr + 1];
+
+		port->dvb.frontend = dvb_attach(s5h1411_attach,
+			&hauppauge_s5h1411_config,
+			&i2c_bus->i2c_adap);
+
+		if (port->dvb.frontend != NULL) {
+			if (port->nr == 0) {
+				/* Master TDA18271 */
+				/* TODO: addr is in the card struct */
+				dvb_attach(tda18271_attach, port->dvb.frontend,
+					0xc0 >> 1, &i2c_bus->i2c_adap,
+					&hauppauge_hvr22x0_tuner_config);
+			} else {
+				/* Slave TDA18271 */
+				dvb_attach(tda18271_attach, port->dvb.frontend,
+					0xc0 >> 1, &i2c_bus->i2c_adap,
+					&hauppauge_hvr22x0s_tuner_config);
+			}
+		}
+
+		break;
+	case SAA7164_BOARD_HAUPPAUGE_HVR2255proto:
+	case SAA7164_BOARD_HAUPPAUGE_HVR2255:
+		i2c_bus = &dev->i2c_bus[2];
+
+		if (port->nr == 0) {
+			port->dvb.frontend = dvb_attach(lgdt3306a_attach,
+				&hauppauge_hvr2255a_config, &i2c_bus->i2c_adap);
+		} else {
+			port->dvb.frontend = dvb_attach(lgdt3306a_attach,
+				&hauppauge_hvr2255b_config, &i2c_bus->i2c_adap);
+		}
+
+		if (port->dvb.frontend != NULL) {
+
+			if (port->nr == 0) {
+				si2157_attach(port, &dev->i2c_bus[0].i2c_adap,
+					      port->dvb.frontend, 0xc0,
+					      &hauppauge_hvr2255_tuner_config);
+			} else {
+				si2157_attach(port, &dev->i2c_bus[1].i2c_adap,
+					      port->dvb.frontend, 0xc0,
+					      &hauppauge_hvr2255_tuner_config);
+			}
+		}
+		break;
+	case SAA7164_BOARD_HAUPPAUGE_HVR2205:
+
+		if (port->nr == 0) {
+			/* attach frontend */
+			memset(&si2168_config, 0, sizeof(si2168_config));
+			si2168_config.i2c_adapter = &adapter;
+			si2168_config.fe = &port->dvb.frontend;
+			si2168_config.ts_mode = SI2168_TS_SERIAL;
+			memset(&info, 0, sizeof(struct i2c_board_info));
+			strlcpy(info.type, "si2168", I2C_NAME_SIZE);
+			info.addr = 0xc8 >> 1;
+			info.platform_data = &si2168_config;
+			request_module(info.type);
+			client_demod = i2c_new_device(&dev->i2c_bus[2].i2c_adap,
+						      &info);
+			if (!client_demod || !client_demod->dev.driver)
+				goto frontend_detach;
+
+			if (!try_module_get(client_demod->dev.driver->owner)) {
+				i2c_unregister_device(client_demod);
+				goto frontend_detach;
+			}
+			port->i2c_client_demod = client_demod;
+
+			/* attach tuner */
+			memset(&si2157_config, 0, sizeof(si2157_config));
+			si2157_config.if_port = 1;
+			si2157_config.fe = port->dvb.frontend;
+			memset(&info, 0, sizeof(struct i2c_board_info));
+			strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+			info.addr = 0xc0 >> 1;
+			info.platform_data = &si2157_config;
+			request_module(info.type);
+			client_tuner = i2c_new_device(&dev->i2c_bus[0].i2c_adap,
+						      &info);
+			if (!client_tuner || !client_tuner->dev.driver) {
+				module_put(client_demod->dev.driver->owner);
+				i2c_unregister_device(client_demod);
+				goto frontend_detach;
+			}
+			if (!try_module_get(client_tuner->dev.driver->owner)) {
+				i2c_unregister_device(client_tuner);
+				module_put(client_demod->dev.driver->owner);
+				i2c_unregister_device(client_demod);
+				goto frontend_detach;
+			}
+			port->i2c_client_tuner = client_tuner;
+		} else {
+			/* attach frontend */
+			memset(&si2168_config, 0, sizeof(si2168_config));
+			si2168_config.i2c_adapter = &adapter;
+			si2168_config.fe = &port->dvb.frontend;
+			si2168_config.ts_mode = SI2168_TS_SERIAL;
+			memset(&info, 0, sizeof(struct i2c_board_info));
+			strlcpy(info.type, "si2168", I2C_NAME_SIZE);
+			info.addr = 0xcc >> 1;
+			info.platform_data = &si2168_config;
+			request_module(info.type);
+			client_demod = i2c_new_device(&dev->i2c_bus[2].i2c_adap,
+						      &info);
+			if (!client_demod || !client_demod->dev.driver)
+				goto frontend_detach;
+
+			if (!try_module_get(client_demod->dev.driver->owner)) {
+				i2c_unregister_device(client_demod);
+				goto frontend_detach;
+			}
+			port->i2c_client_demod = client_demod;
+
+			/* attach tuner */
+			memset(&si2157_config, 0, sizeof(si2157_config));
+			si2157_config.fe = port->dvb.frontend;
+			si2157_config.if_port = 1;
+			memset(&info, 0, sizeof(struct i2c_board_info));
+			strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+			info.addr = 0xc0 >> 1;
+			info.platform_data = &si2157_config;
+			request_module(info.type);
+			client_tuner = i2c_new_device(&dev->i2c_bus[1].i2c_adap,
+						      &info);
+			if (!client_tuner || !client_tuner->dev.driver) {
+				module_put(client_demod->dev.driver->owner);
+				i2c_unregister_device(client_demod);
+				goto frontend_detach;
+			}
+			if (!try_module_get(client_tuner->dev.driver->owner)) {
+				i2c_unregister_device(client_tuner);
+				module_put(client_demod->dev.driver->owner);
+				i2c_unregister_device(client_demod);
+				goto frontend_detach;
+			}
+			port->i2c_client_tuner = client_tuner;
+		}
+
+		break;
+	default:
+		printk(KERN_ERR "%s: The frontend isn't supported\n",
+		       dev->name);
+		break;
+	}
+	if (NULL == dvb->frontend) {
+		printk(KERN_ERR "%s() Frontend initialization failed\n",
+		       __func__);
+		return -1;
+	}
+
+	/* register everything */
+	ret = dvb_register(port);
+	if (ret < 0) {
+		if (dvb->frontend->ops.release)
+			dvb->frontend->ops.release(dvb->frontend);
+		return ret;
+	}
+
+	return 0;
+
+frontend_detach:
+	printk(KERN_ERR "%s() Frontend/I2C initialization failed\n", __func__);
+	return -1;
+}
+
diff --git a/drivers/media/pci/saa7164/saa7164-encoder.c b/drivers/media/pci/saa7164/saa7164-encoder.c
new file mode 100644
index 0000000..1b184c3
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-encoder.c
@@ -0,0 +1,1169 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "saa7164.h"
+
+#define ENCODER_MAX_BITRATE 6500000
+#define ENCODER_MIN_BITRATE 1000000
+#define ENCODER_DEF_BITRATE 5000000
+
+/*
+ * This is a dummy non-zero value for the sizeimage field of v4l2_pix_format.
+ * It is not actually used for anything since this driver does not support
+ * stream I/O, only read(), and because this driver produces an MPEG stream
+ * and not discrete frames. But the V4L2 spec doesn't allow for this value
+ * to be 0, so set it to 0x10000 instead.
+ *
+ * If we ever change this driver to support stream I/O, then this field
+ * will be the size of the streaming buffers.
+ */
+#define SAA7164_SIZEIMAGE (0x10000)
+
+static struct saa7164_tvnorm saa7164_tvnorms[] = {
+	{
+		.name      = "NTSC-M",
+		.id        = V4L2_STD_NTSC_M,
+	}, {
+		.name      = "NTSC-JP",
+		.id        = V4L2_STD_NTSC_M_JP,
+	}
+};
+
+/* Take the encoder configuration form the port struct and
+ * flush it to the hardware.
+ */
+static void saa7164_encoder_configure(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	dprintk(DBGLVL_ENC, "%s()\n", __func__);
+
+	port->encoder_params.width = port->width;
+	port->encoder_params.height = port->height;
+	port->encoder_params.is_50hz =
+		(port->encodernorm.id & V4L2_STD_625_50) != 0;
+
+	/* Set up the DIF (enable it) for analog mode by default */
+	saa7164_api_initialize_dif(port);
+
+	/* Configure the correct video standard */
+	saa7164_api_configure_dif(port, port->encodernorm.id);
+
+	/* Ensure the audio decoder is correct configured */
+	saa7164_api_set_audio_std(port);
+}
+
+static int saa7164_encoder_buffers_dealloc(struct saa7164_port *port)
+{
+	struct list_head *c, *n, *p, *q, *l, *v;
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct saa7164_user_buffer *ubuf;
+
+	/* Remove any allocated buffers */
+	mutex_lock(&port->dmaqueue_lock);
+
+	dprintk(DBGLVL_ENC, "%s(port=%d) dmaqueue\n", __func__, port->nr);
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+		buf = list_entry(c, struct saa7164_buffer, list);
+		list_del(c);
+		saa7164_buffer_dealloc(buf);
+	}
+
+	dprintk(DBGLVL_ENC, "%s(port=%d) used\n", __func__, port->nr);
+	list_for_each_safe(p, q, &port->list_buf_used.list) {
+		ubuf = list_entry(p, struct saa7164_user_buffer, list);
+		list_del(p);
+		saa7164_buffer_dealloc_user(ubuf);
+	}
+
+	dprintk(DBGLVL_ENC, "%s(port=%d) free\n", __func__, port->nr);
+	list_for_each_safe(l, v, &port->list_buf_free.list) {
+		ubuf = list_entry(l, struct saa7164_user_buffer, list);
+		list_del(l);
+		saa7164_buffer_dealloc_user(ubuf);
+	}
+
+	mutex_unlock(&port->dmaqueue_lock);
+	dprintk(DBGLVL_ENC, "%s(port=%d) done\n", __func__, port->nr);
+
+	return 0;
+}
+
+/* Dynamic buffer switch at encoder start time */
+static int saa7164_encoder_buffers_alloc(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct saa7164_user_buffer *ubuf;
+	struct tmHWStreamParameters *params = &port->hw_streamingparams;
+	int result = -ENODEV, i;
+	int len = 0;
+
+	dprintk(DBGLVL_ENC, "%s()\n", __func__);
+
+	if (port->encoder_params.stream_type ==
+		V4L2_MPEG_STREAM_TYPE_MPEG2_PS) {
+		dprintk(DBGLVL_ENC,
+			"%s() type=V4L2_MPEG_STREAM_TYPE_MPEG2_PS\n",
+			__func__);
+		params->samplesperline = 128;
+		params->numberoflines = 256;
+		params->pitch = 128;
+		params->numpagetables = 2 +
+			((SAA7164_PS_NUMBER_OF_LINES * 128) / PAGE_SIZE);
+	} else
+	if (port->encoder_params.stream_type ==
+		V4L2_MPEG_STREAM_TYPE_MPEG2_TS) {
+		dprintk(DBGLVL_ENC,
+			"%s() type=V4L2_MPEG_STREAM_TYPE_MPEG2_TS\n",
+			__func__);
+		params->samplesperline = 188;
+		params->numberoflines = 312;
+		params->pitch = 188;
+		params->numpagetables = 2 +
+			((SAA7164_TS_NUMBER_OF_LINES * 188) / PAGE_SIZE);
+	} else
+		BUG();
+
+	/* Init and establish defaults */
+	params->bitspersample = 8;
+	params->linethreshold = 0;
+	params->pagetablelistvirt = NULL;
+	params->pagetablelistphys = NULL;
+	params->numpagetableentries = port->hwcfg.buffercount;
+
+	/* Allocate the PCI resources, buffers (hard) */
+	for (i = 0; i < port->hwcfg.buffercount; i++) {
+		buf = saa7164_buffer_alloc(port,
+			params->numberoflines *
+			params->pitch);
+
+		if (!buf) {
+			printk(KERN_ERR "%s() failed "
+			       "(errno = %d), unable to allocate buffer\n",
+				__func__, result);
+			result = -ENOMEM;
+			goto failed;
+		} else {
+
+			mutex_lock(&port->dmaqueue_lock);
+			list_add_tail(&buf->list, &port->dmaqueue.list);
+			mutex_unlock(&port->dmaqueue_lock);
+
+		}
+	}
+
+	/* Allocate some kernel buffers for copying
+	 * to userpsace.
+	 */
+	len = params->numberoflines * params->pitch;
+
+	if (encoder_buffers < 16)
+		encoder_buffers = 16;
+	if (encoder_buffers > 512)
+		encoder_buffers = 512;
+
+	for (i = 0; i < encoder_buffers; i++) {
+
+		ubuf = saa7164_buffer_alloc_user(dev, len);
+		if (ubuf) {
+			mutex_lock(&port->dmaqueue_lock);
+			list_add_tail(&ubuf->list, &port->list_buf_free.list);
+			mutex_unlock(&port->dmaqueue_lock);
+		}
+
+	}
+
+	result = 0;
+
+failed:
+	return result;
+}
+
+static int saa7164_encoder_initialize(struct saa7164_port *port)
+{
+	saa7164_encoder_configure(port);
+	return 0;
+}
+
+/* -- V4L2 --------------------------------------------------------- */
+int saa7164_s_std(struct saa7164_port *port, v4l2_std_id id)
+{
+	struct saa7164_dev *dev = port->dev;
+	unsigned int i;
+
+	dprintk(DBGLVL_ENC, "%s(id=0x%x)\n", __func__, (u32)id);
+
+	for (i = 0; i < ARRAY_SIZE(saa7164_tvnorms); i++) {
+		if (id & saa7164_tvnorms[i].id)
+			break;
+	}
+	if (i == ARRAY_SIZE(saa7164_tvnorms))
+		return -EINVAL;
+
+	port->encodernorm = saa7164_tvnorms[i];
+	port->std = id;
+
+	/* Update the audio decoder while is not running in
+	 * auto detect mode.
+	 */
+	saa7164_api_set_audio_std(port);
+
+	dprintk(DBGLVL_ENC, "%s(id=0x%x) OK\n", __func__, (u32)id);
+
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+
+	return saa7164_s_std(fh->port, id);
+}
+
+int saa7164_g_std(struct saa7164_port *port, v4l2_std_id *id)
+{
+	*id = port->std;
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+
+	return saa7164_g_std(fh->port, id);
+}
+
+int saa7164_enum_input(struct file *file, void *priv, struct v4l2_input *i)
+{
+	static const char * const inputs[] = {
+		"tuner", "composite", "svideo", "aux",
+		"composite 2", "svideo 2", "aux 2"
+	};
+	int n;
+
+	if (i->index >= 7)
+		return -EINVAL;
+
+	strcpy(i->name, inputs[i->index]);
+
+	if (i->index == 0)
+		i->type = V4L2_INPUT_TYPE_TUNER;
+	else
+		i->type  = V4L2_INPUT_TYPE_CAMERA;
+
+	for (n = 0; n < ARRAY_SIZE(saa7164_tvnorms); n++)
+		i->std |= saa7164_tvnorms[n].id;
+
+	return 0;
+}
+
+int saa7164_g_input(struct saa7164_port *port, unsigned int *i)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	if (saa7164_api_get_videomux(port) != SAA_OK)
+		return -EIO;
+
+	*i = (port->mux_input - 1);
+
+	dprintk(DBGLVL_ENC, "%s() input=%d\n", __func__, *i);
+
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+
+	return saa7164_g_input(fh->port, i);
+}
+
+int saa7164_s_input(struct saa7164_port *port, unsigned int i)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	dprintk(DBGLVL_ENC, "%s() input=%d\n", __func__, i);
+
+	if (i >= 7)
+		return -EINVAL;
+
+	port->mux_input = i + 1;
+
+	if (saa7164_api_set_videomux(port) != SAA_OK)
+		return -EIO;
+
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+
+	return saa7164_s_input(fh->port, i);
+}
+
+int saa7164_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+	struct saa7164_dev *dev = port->dev;
+
+	if (0 != t->index)
+		return -EINVAL;
+
+	strcpy(t->name, "tuner");
+	t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO;
+	t->rangelow = SAA7164_TV_MIN_FREQ;
+	t->rangehigh = SAA7164_TV_MAX_FREQ;
+
+	dprintk(DBGLVL_ENC, "VIDIOC_G_TUNER: tuner type %d\n", t->type);
+
+	return 0;
+}
+
+int saa7164_s_tuner(struct file *file, void *priv,
+			   const struct v4l2_tuner *t)
+{
+	if (0 != t->index)
+		return -EINVAL;
+
+	/* Update the A/V core */
+	return 0;
+}
+
+int saa7164_g_frequency(struct saa7164_port *port, struct v4l2_frequency *f)
+{
+	if (f->tuner)
+		return -EINVAL;
+
+	f->frequency = port->freq;
+	return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+	struct v4l2_frequency *f)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+
+	return saa7164_g_frequency(fh->port, f);
+}
+
+int saa7164_s_frequency(struct saa7164_port *port,
+			const struct v4l2_frequency *f)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_port *tsport;
+	struct dvb_frontend *fe;
+
+	/* TODO: Pull this for the std */
+	struct analog_parameters params = {
+		.mode      = V4L2_TUNER_ANALOG_TV,
+		.audmode   = V4L2_TUNER_MODE_STEREO,
+		.std       = port->encodernorm.id,
+		.frequency = f->frequency
+	};
+
+	/* Stop the encoder */
+	dprintk(DBGLVL_ENC, "%s() frequency=%d tuner=%d\n", __func__,
+		f->frequency, f->tuner);
+
+	if (f->tuner != 0)
+		return -EINVAL;
+
+	port->freq = clamp(f->frequency,
+			   SAA7164_TV_MIN_FREQ, SAA7164_TV_MAX_FREQ);
+
+	/* Update the hardware */
+	if (port->nr == SAA7164_PORT_ENC1)
+		tsport = &dev->ports[SAA7164_PORT_TS1];
+	else if (port->nr == SAA7164_PORT_ENC2)
+		tsport = &dev->ports[SAA7164_PORT_TS2];
+	else
+		BUG();
+
+	fe = tsport->dvb.frontend;
+
+	if (fe && fe->ops.tuner_ops.set_analog_params)
+		fe->ops.tuner_ops.set_analog_params(fe, &params);
+	else
+		printk(KERN_ERR "%s() No analog tuner, aborting\n", __func__);
+
+	saa7164_encoder_initialize(port);
+
+	return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+			      const struct v4l2_frequency *f)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+
+	return saa7164_s_frequency(fh->port, f);
+}
+
+static int saa7164_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct saa7164_port *port =
+		container_of(ctrl->handler, struct saa7164_port, ctrl_handler);
+	struct saa7164_encoder_params *params = &port->encoder_params;
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		port->ctl_brightness = ctrl->val;
+		saa7164_api_set_usercontrol(port, PU_BRIGHTNESS_CONTROL);
+		break;
+	case V4L2_CID_CONTRAST:
+		port->ctl_contrast = ctrl->val;
+		saa7164_api_set_usercontrol(port, PU_CONTRAST_CONTROL);
+		break;
+	case V4L2_CID_SATURATION:
+		port->ctl_saturation = ctrl->val;
+		saa7164_api_set_usercontrol(port, PU_SATURATION_CONTROL);
+		break;
+	case V4L2_CID_HUE:
+		port->ctl_hue = ctrl->val;
+		saa7164_api_set_usercontrol(port, PU_HUE_CONTROL);
+		break;
+	case V4L2_CID_SHARPNESS:
+		port->ctl_sharpness = ctrl->val;
+		saa7164_api_set_usercontrol(port, PU_SHARPNESS_CONTROL);
+		break;
+	case V4L2_CID_AUDIO_VOLUME:
+		port->ctl_volume = ctrl->val;
+		saa7164_api_set_audio_volume(port, port->ctl_volume);
+		break;
+	case V4L2_CID_MPEG_VIDEO_BITRATE:
+		params->bitrate = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_STREAM_TYPE:
+		params->stream_type = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_AUDIO_MUTE:
+		params->ctl_mute = ctrl->val;
+		ret = saa7164_api_audio_mute(port, params->ctl_mute);
+		if (ret != SAA_OK) {
+			printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__,
+				ret);
+			ret = -EIO;
+		}
+		break;
+	case V4L2_CID_MPEG_VIDEO_ASPECT:
+		params->ctl_aspect = ctrl->val;
+		ret = saa7164_api_set_aspect_ratio(port);
+		if (ret != SAA_OK) {
+			printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__,
+				ret);
+			ret = -EIO;
+		}
+		break;
+	case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
+		params->bitrate_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_B_FRAMES:
+		params->refdist = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK:
+		params->bitrate_peak = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+		params->gop_size = ctrl->val;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int vidioc_querycap(struct file *file, void  *priv,
+	struct v4l2_capability *cap)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+	struct saa7164_dev *dev = port->dev;
+
+	strcpy(cap->driver, dev->name);
+	strlcpy(cap->card, saa7164_boards[dev->board].name,
+		sizeof(cap->card));
+	sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
+
+	cap->device_caps =
+		V4L2_CAP_VIDEO_CAPTURE |
+		V4L2_CAP_READWRITE |
+		V4L2_CAP_TUNER;
+
+	cap->capabilities = cap->device_caps |
+		V4L2_CAP_VBI_CAPTURE |
+		V4L2_CAP_DEVICE_CAPS;
+
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+	struct v4l2_fmtdesc *f)
+{
+	if (f->index != 0)
+		return -EINVAL;
+
+	strlcpy(f->description, "MPEG", sizeof(f->description));
+	f->pixelformat = V4L2_PIX_FMT_MPEG;
+
+	return 0;
+}
+
+static int vidioc_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+
+	f->fmt.pix.pixelformat  = V4L2_PIX_FMT_MPEG;
+	f->fmt.pix.bytesperline = 0;
+	f->fmt.pix.sizeimage    = SAA7164_SIZEIMAGE;
+	f->fmt.pix.field        = V4L2_FIELD_INTERLACED;
+	f->fmt.pix.colorspace   = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.width        = port->width;
+	f->fmt.pix.height       = port->height;
+	return 0;
+}
+
+static int saa7164_encoder_stop_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() stop transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_ENC, "%s()    Stopped\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int saa7164_encoder_acquire_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() acquire transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_ENC, "%s() Acquired\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int saa7164_encoder_pause_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() pause transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_ENC, "%s()   Paused\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+/* Firmware is very windows centric, meaning you have to transition
+ * the part through AVStream / KS Windows stages, forwards or backwards.
+ * States are: stopped, acquired (h/w), paused, started.
+ * We have to leave here will all of the soft buffers on the free list,
+ * else the cfg_post() func won't have soft buffers to correctly configure.
+ */
+static int saa7164_encoder_stop_streaming(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct saa7164_user_buffer *ubuf;
+	struct list_head *c, *n;
+	int ret;
+
+	dprintk(DBGLVL_ENC, "%s(port=%d)\n", __func__, port->nr);
+
+	ret = saa7164_encoder_pause_port(port);
+	ret = saa7164_encoder_acquire_port(port);
+	ret = saa7164_encoder_stop_port(port);
+
+	dprintk(DBGLVL_ENC, "%s(port=%d) Hardware stopped\n", __func__,
+		port->nr);
+
+	/* Reset the state of any allocated buffer resources */
+	mutex_lock(&port->dmaqueue_lock);
+
+	/* Reset the hard and soft buffer state */
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+		buf = list_entry(c, struct saa7164_buffer, list);
+		buf->flags = SAA7164_BUFFER_FREE;
+		buf->pos = 0;
+	}
+
+	list_for_each_safe(c, n, &port->list_buf_used.list) {
+		ubuf = list_entry(c, struct saa7164_user_buffer, list);
+		ubuf->pos = 0;
+		list_move_tail(&ubuf->list, &port->list_buf_free.list);
+	}
+
+	mutex_unlock(&port->dmaqueue_lock);
+
+	/* Free any allocated resources */
+	saa7164_encoder_buffers_dealloc(port);
+
+	dprintk(DBGLVL_ENC, "%s(port=%d) Released\n", __func__, port->nr);
+
+	return ret;
+}
+
+static int saa7164_encoder_start_streaming(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int result, ret = 0;
+
+	dprintk(DBGLVL_ENC, "%s(port=%d)\n", __func__, port->nr);
+
+	port->done_first_interrupt = 0;
+
+	/* allocate all of the PCIe DMA buffer resources on the fly,
+	 * allowing switching between TS and PS payloads without
+	 * requiring a complete driver reload.
+	 */
+	saa7164_encoder_buffers_alloc(port);
+
+	/* Configure the encoder with any cache values */
+	saa7164_api_set_encoder(port);
+	saa7164_api_get_encoder(port);
+
+	/* Place the empty buffers on the hardware */
+	saa7164_buffer_cfg_port(port);
+
+	/* Acquire the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() acquire transition failed, res = 0x%x\n",
+			__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+		if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+			printk(KERN_ERR "%s() acquire/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+		ret = -EIO;
+		goto out;
+	} else
+		dprintk(DBGLVL_ENC, "%s()   Acquired\n", __func__);
+
+	/* Pause the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() pause transition failed, res = 0x%x\n",
+				__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+		if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+			printk(KERN_ERR "%s() pause/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+
+		ret = -EIO;
+		goto out;
+	} else
+		dprintk(DBGLVL_ENC, "%s()   Paused\n", __func__);
+
+	/* Start the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_RUN);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() run transition failed, result = 0x%x\n",
+				__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+		if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+			printk(KERN_ERR "%s() run/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+
+		ret = -EIO;
+	} else
+		dprintk(DBGLVL_ENC, "%s()   Running\n", __func__);
+
+out:
+	return ret;
+}
+
+static int fops_open(struct file *file)
+{
+	struct saa7164_dev *dev;
+	struct saa7164_port *port;
+	struct saa7164_encoder_fh *fh;
+
+	port = (struct saa7164_port *)video_get_drvdata(video_devdata(file));
+	if (!port)
+		return -ENODEV;
+
+	dev = port->dev;
+
+	dprintk(DBGLVL_ENC, "%s()\n", __func__);
+
+	/* allocate + initialize per filehandle data */
+	fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+	if (NULL == fh)
+		return -ENOMEM;
+
+	fh->port = port;
+	v4l2_fh_init(&fh->fh, video_devdata(file));
+	v4l2_fh_add(&fh->fh);
+	file->private_data = fh;
+
+	return 0;
+}
+
+static int fops_release(struct file *file)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+	struct saa7164_dev *dev = port->dev;
+
+	dprintk(DBGLVL_ENC, "%s()\n", __func__);
+
+	/* Shut device down on last close */
+	if (atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) {
+		if (atomic_dec_return(&port->v4l_reader_count) == 0) {
+			/* stop mpeg capture then cancel buffers */
+			saa7164_encoder_stop_streaming(port);
+		}
+	}
+
+	v4l2_fh_del(&fh->fh);
+	v4l2_fh_exit(&fh->fh);
+	kfree(fh);
+
+	return 0;
+}
+
+static struct
+saa7164_user_buffer *saa7164_enc_next_buf(struct saa7164_port *port)
+{
+	struct saa7164_user_buffer *ubuf = NULL;
+	struct saa7164_dev *dev = port->dev;
+	u32 crc;
+
+	mutex_lock(&port->dmaqueue_lock);
+	if (!list_empty(&port->list_buf_used.list)) {
+		ubuf = list_first_entry(&port->list_buf_used.list,
+			struct saa7164_user_buffer, list);
+
+		if (crc_checking) {
+			crc = crc32(0, ubuf->data, ubuf->actual_size);
+			if (crc != ubuf->crc) {
+				printk(KERN_ERR
+		"%s() ubuf %p crc became invalid, was 0x%x became 0x%x\n",
+					__func__,
+					ubuf, ubuf->crc, crc);
+			}
+		}
+
+	}
+	mutex_unlock(&port->dmaqueue_lock);
+
+	dprintk(DBGLVL_ENC, "%s() returns %p\n", __func__, ubuf);
+
+	return ubuf;
+}
+
+static ssize_t fops_read(struct file *file, char __user *buffer,
+	size_t count, loff_t *pos)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+	struct saa7164_user_buffer *ubuf = NULL;
+	struct saa7164_dev *dev = port->dev;
+	int ret = 0;
+	int rem, cnt;
+	u8 *p;
+
+	port->last_read_msecs_diff = port->last_read_msecs;
+	port->last_read_msecs = jiffies_to_msecs(jiffies);
+	port->last_read_msecs_diff = port->last_read_msecs -
+		port->last_read_msecs_diff;
+
+	saa7164_histogram_update(&port->read_interval,
+		port->last_read_msecs_diff);
+
+	if (*pos) {
+		printk(KERN_ERR "%s() ESPIPE\n", __func__);
+		return -ESPIPE;
+	}
+
+	if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) {
+		if (atomic_inc_return(&port->v4l_reader_count) == 1) {
+
+			if (saa7164_encoder_initialize(port) < 0) {
+				printk(KERN_ERR "%s() EINVAL\n", __func__);
+				return -EINVAL;
+			}
+
+			saa7164_encoder_start_streaming(port);
+			msleep(200);
+		}
+	}
+
+	/* blocking wait for buffer */
+	if ((file->f_flags & O_NONBLOCK) == 0) {
+		if (wait_event_interruptible(port->wait_read,
+			saa7164_enc_next_buf(port))) {
+				printk(KERN_ERR "%s() ERESTARTSYS\n", __func__);
+				return -ERESTARTSYS;
+		}
+	}
+
+	/* Pull the first buffer from the used list */
+	ubuf = saa7164_enc_next_buf(port);
+
+	while ((count > 0) && ubuf) {
+
+		/* set remaining bytes to copy */
+		rem = ubuf->actual_size - ubuf->pos;
+		cnt = rem > count ? count : rem;
+
+		p = ubuf->data + ubuf->pos;
+
+		dprintk(DBGLVL_ENC,
+			"%s() count=%d cnt=%d rem=%d buf=%p buf->pos=%d\n",
+			__func__, (int)count, cnt, rem, ubuf, ubuf->pos);
+
+		if (copy_to_user(buffer, p, cnt)) {
+			printk(KERN_ERR "%s() copy_to_user failed\n", __func__);
+			if (!ret) {
+				printk(KERN_ERR "%s() EFAULT\n", __func__);
+				ret = -EFAULT;
+			}
+			goto err;
+		}
+
+		ubuf->pos += cnt;
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+
+		if (ubuf->pos > ubuf->actual_size)
+			printk(KERN_ERR "read() pos > actual, huh?\n");
+
+		if (ubuf->pos == ubuf->actual_size) {
+
+			/* finished with current buffer, take next buffer */
+
+			/* Requeue the buffer on the free list */
+			ubuf->pos = 0;
+
+			mutex_lock(&port->dmaqueue_lock);
+			list_move_tail(&ubuf->list, &port->list_buf_free.list);
+			mutex_unlock(&port->dmaqueue_lock);
+
+			/* Dequeue next */
+			if ((file->f_flags & O_NONBLOCK) == 0) {
+				if (wait_event_interruptible(port->wait_read,
+					saa7164_enc_next_buf(port))) {
+						break;
+				}
+			}
+			ubuf = saa7164_enc_next_buf(port);
+		}
+	}
+err:
+	if (!ret && !ubuf)
+		ret = -EAGAIN;
+
+	return ret;
+}
+
+static unsigned int fops_poll(struct file *file, poll_table *wait)
+{
+	unsigned long req_events = poll_requested_events(wait);
+	struct saa7164_encoder_fh *fh =
+		(struct saa7164_encoder_fh *)file->private_data;
+	struct saa7164_port *port = fh->port;
+	unsigned int mask = v4l2_ctrl_poll(file, wait);
+
+	port->last_poll_msecs_diff = port->last_poll_msecs;
+	port->last_poll_msecs = jiffies_to_msecs(jiffies);
+	port->last_poll_msecs_diff = port->last_poll_msecs -
+		port->last_poll_msecs_diff;
+
+	saa7164_histogram_update(&port->poll_interval,
+		port->last_poll_msecs_diff);
+
+	if (!(req_events & (POLLIN | POLLRDNORM)))
+		return mask;
+
+	if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) {
+		if (atomic_inc_return(&port->v4l_reader_count) == 1) {
+			if (saa7164_encoder_initialize(port) < 0)
+				return mask | POLLERR;
+			saa7164_encoder_start_streaming(port);
+			msleep(200);
+		}
+	}
+
+	/* Pull the first buffer from the used list */
+	if (!list_empty(&port->list_buf_used.list))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static const struct v4l2_ctrl_ops saa7164_ctrl_ops = {
+	.s_ctrl = saa7164_s_ctrl,
+};
+
+static const struct v4l2_file_operations mpeg_fops = {
+	.owner		= THIS_MODULE,
+	.open		= fops_open,
+	.release	= fops_release,
+	.read		= fops_read,
+	.poll		= fops_poll,
+	.unlocked_ioctl	= video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops mpeg_ioctl_ops = {
+	.vidioc_s_std		 = vidioc_s_std,
+	.vidioc_g_std		 = vidioc_g_std,
+	.vidioc_enum_input	 = saa7164_enum_input,
+	.vidioc_g_input		 = vidioc_g_input,
+	.vidioc_s_input		 = vidioc_s_input,
+	.vidioc_g_tuner		 = saa7164_g_tuner,
+	.vidioc_s_tuner		 = saa7164_s_tuner,
+	.vidioc_g_frequency	 = vidioc_g_frequency,
+	.vidioc_s_frequency	 = vidioc_s_frequency,
+	.vidioc_querycap	 = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap	 = vidioc_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap	 = vidioc_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap	 = vidioc_fmt_vid_cap,
+	.vidioc_log_status	 = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event  = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device saa7164_mpeg_template = {
+	.name          = "saa7164",
+	.fops          = &mpeg_fops,
+	.ioctl_ops     = &mpeg_ioctl_ops,
+	.minor         = -1,
+	.tvnorms       = SAA7164_NORMS,
+};
+
+static struct video_device *saa7164_encoder_alloc(
+	struct saa7164_port *port,
+	struct pci_dev *pci,
+	struct video_device *template,
+	char *type)
+{
+	struct video_device *vfd;
+	struct saa7164_dev *dev = port->dev;
+
+	dprintk(DBGLVL_ENC, "%s()\n", __func__);
+
+	vfd = video_device_alloc();
+	if (NULL == vfd)
+		return NULL;
+
+	*vfd = *template;
+	snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", dev->name,
+		type, saa7164_boards[dev->board].name);
+
+	vfd->v4l2_dev  = &dev->v4l2_dev;
+	vfd->release = video_device_release;
+	return vfd;
+}
+
+int saa7164_encoder_register(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct v4l2_ctrl_handler *hdl = &port->ctrl_handler;
+	int result = -ENODEV;
+
+	dprintk(DBGLVL_ENC, "%s()\n", __func__);
+
+	if (port->type != SAA7164_MPEG_ENCODER)
+		BUG();
+
+	/* Sanity check that the PCI configuration space is active */
+	if (port->hwcfg.BARLocation == 0) {
+		printk(KERN_ERR "%s() failed "
+		       "(errno = %d), NO PCI configuration\n",
+			__func__, result);
+		result = -ENOMEM;
+		goto failed;
+	}
+
+	/* Establish encoder defaults here */
+	/* Set default TV standard */
+	port->encodernorm = saa7164_tvnorms[0];
+	port->width = 720;
+	port->mux_input = 1; /* Composite */
+	port->video_format = EU_VIDEO_FORMAT_MPEG_2;
+	port->audio_format = 0;
+	port->video_resolution = 0;
+	port->freq = SAA7164_TV_MIN_FREQ;
+
+	v4l2_ctrl_handler_init(hdl, 14);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_CONTRAST, 0, 255, 1, 66);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_SATURATION, 0, 255, 1, 62);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_HUE, 0, 255, 1, 128);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_SHARPNESS, 0x0, 0x0f, 1, 8);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_MPEG_AUDIO_MUTE, 0x0, 0x01, 1, 0);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_AUDIO_VOLUME, -83, 24, 1, 20);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_BITRATE,
+			  ENCODER_MIN_BITRATE, ENCODER_MAX_BITRATE,
+			  100000, ENCODER_DEF_BITRATE);
+	v4l2_ctrl_new_std_menu(hdl, &saa7164_ctrl_ops,
+			       V4L2_CID_MPEG_STREAM_TYPE,
+			       V4L2_MPEG_STREAM_TYPE_MPEG2_TS, 0,
+			       V4L2_MPEG_STREAM_TYPE_MPEG2_PS);
+	v4l2_ctrl_new_std_menu(hdl, &saa7164_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_ASPECT,
+			       V4L2_MPEG_VIDEO_ASPECT_221x100, 0,
+			       V4L2_MPEG_VIDEO_ASPECT_4x3);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 255, 1, 15);
+	v4l2_ctrl_new_std_menu(hdl, &saa7164_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+			       V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0,
+			       V4L2_MPEG_VIDEO_BITRATE_MODE_VBR);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_B_FRAMES, 1, 3, 1, 1);
+	v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+			  ENCODER_MIN_BITRATE, ENCODER_MAX_BITRATE,
+			  100000, ENCODER_DEF_BITRATE);
+	if (hdl->error) {
+		result = hdl->error;
+		goto failed;
+	}
+
+	port->std = V4L2_STD_NTSC_M;
+
+	if (port->encodernorm.id & V4L2_STD_525_60)
+		port->height = 480;
+	else
+		port->height = 576;
+
+	/* Allocate and register the video device node */
+	port->v4l_device = saa7164_encoder_alloc(port,
+		dev->pci, &saa7164_mpeg_template, "mpeg");
+
+	if (!port->v4l_device) {
+		printk(KERN_INFO "%s: can't allocate mpeg device\n",
+			dev->name);
+		result = -ENOMEM;
+		goto failed;
+	}
+
+	port->v4l_device->ctrl_handler = hdl;
+	v4l2_ctrl_handler_setup(hdl);
+	video_set_drvdata(port->v4l_device, port);
+	result = video_register_device(port->v4l_device,
+		VFL_TYPE_GRABBER, -1);
+	if (result < 0) {
+		printk(KERN_INFO "%s: can't register mpeg device\n",
+			dev->name);
+		/* TODO: We're going to leak here if we don't dealloc
+		 The buffers above. The unreg function can't deal wit it.
+		*/
+		goto failed;
+	}
+
+	printk(KERN_INFO "%s: registered device video%d [mpeg]\n",
+		dev->name, port->v4l_device->num);
+
+	/* Configure the hardware defaults */
+	saa7164_api_set_videomux(port);
+	saa7164_api_set_usercontrol(port, PU_BRIGHTNESS_CONTROL);
+	saa7164_api_set_usercontrol(port, PU_CONTRAST_CONTROL);
+	saa7164_api_set_usercontrol(port, PU_HUE_CONTROL);
+	saa7164_api_set_usercontrol(port, PU_SATURATION_CONTROL);
+	saa7164_api_set_usercontrol(port, PU_SHARPNESS_CONTROL);
+	saa7164_api_audio_mute(port, 0);
+	saa7164_api_set_audio_volume(port, 20);
+	saa7164_api_set_aspect_ratio(port);
+
+	/* Disable audio standard detection, it's buggy */
+	saa7164_api_set_audio_detection(port, 0);
+
+	saa7164_api_set_encoder(port);
+	saa7164_api_get_encoder(port);
+
+	result = 0;
+failed:
+	return result;
+}
+
+void saa7164_encoder_unregister(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	dprintk(DBGLVL_ENC, "%s(port=%d)\n", __func__, port->nr);
+
+	if (port->type != SAA7164_MPEG_ENCODER)
+		BUG();
+
+	if (port->v4l_device) {
+		if (port->v4l_device->minor != -1)
+			video_unregister_device(port->v4l_device);
+		else
+			video_device_release(port->v4l_device);
+
+		port->v4l_device = NULL;
+	}
+	v4l2_ctrl_handler_free(&port->ctrl_handler);
+
+	dprintk(DBGLVL_ENC, "%s(port=%d) done\n", __func__, port->nr);
+}
+
diff --git a/drivers/media/pci/saa7164/saa7164-fw.c b/drivers/media/pci/saa7164/saa7164-fw.c
new file mode 100644
index 0000000..269e078
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-fw.c
@@ -0,0 +1,613 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/firmware.h>
+#include <linux/slab.h>
+
+#include "saa7164.h"
+
+#define SAA7164_REV2_FIRMWARE		"NXP7164-2010-03-10.1.fw"
+#define SAA7164_REV2_FIRMWARE_SIZE	4019072
+
+#define SAA7164_REV3_FIRMWARE		"NXP7164-2010-03-10.1.fw"
+#define SAA7164_REV3_FIRMWARE_SIZE	4019072
+
+struct fw_header {
+	u32	firmwaresize;
+	u32	bslsize;
+	u32	reserved;
+	u32	version;
+};
+
+static int saa7164_dl_wait_ack(struct saa7164_dev *dev, u32 reg)
+{
+	u32 timeout = SAA_DEVICE_TIMEOUT;
+	while ((saa7164_readl(reg) & 0x01) == 0) {
+		timeout -= 10;
+		if (timeout == 0) {
+			printk(KERN_ERR "%s() timeout (no d/l ack)\n",
+				__func__);
+			return -EBUSY;
+		}
+		msleep(100);
+	}
+
+	return 0;
+}
+
+static int saa7164_dl_wait_clr(struct saa7164_dev *dev, u32 reg)
+{
+	u32 timeout = SAA_DEVICE_TIMEOUT;
+	while (saa7164_readl(reg) & 0x01) {
+		timeout -= 10;
+		if (timeout == 0) {
+			printk(KERN_ERR "%s() timeout (no d/l clr)\n",
+				__func__);
+			return -EBUSY;
+		}
+		msleep(100);
+	}
+
+	return 0;
+}
+
+/* TODO: move dlflags into dev-> and change to write/readl/b */
+/* TODO: Excessive levels of debug */
+static int saa7164_downloadimage(struct saa7164_dev *dev, u8 *src, u32 srcsize,
+				 u32 dlflags, u8 __iomem *dst, u32 dstsize)
+{
+	u32 reg, timeout, offset;
+	u8 *srcbuf = NULL;
+	int ret;
+
+	u32 dlflag = dlflags;
+	u32 dlflag_ack = dlflag + 4;
+	u32 drflag = dlflag_ack + 4;
+	u32 drflag_ack = drflag + 4;
+	u32 bleflag = drflag_ack + 4;
+
+	dprintk(DBGLVL_FW,
+		"%s(image=%p, size=%d, flags=0x%x, dst=%p, dstsize=0x%x)\n",
+		__func__, src, srcsize, dlflags, dst, dstsize);
+
+	if ((src == NULL) || (dst == NULL)) {
+		ret = -EIO;
+		goto out;
+	}
+
+	srcbuf = kzalloc(4 * 1048576, GFP_KERNEL);
+	if (NULL == srcbuf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (srcsize > (4*1048576)) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(srcbuf, src, srcsize);
+
+	dprintk(DBGLVL_FW, "%s() dlflag = 0x%x\n", __func__, dlflag);
+	dprintk(DBGLVL_FW, "%s() dlflag_ack = 0x%x\n", __func__, dlflag_ack);
+	dprintk(DBGLVL_FW, "%s() drflag = 0x%x\n", __func__, drflag);
+	dprintk(DBGLVL_FW, "%s() drflag_ack = 0x%x\n", __func__, drflag_ack);
+	dprintk(DBGLVL_FW, "%s() bleflag = 0x%x\n", __func__, bleflag);
+
+	reg = saa7164_readl(dlflag);
+	dprintk(DBGLVL_FW, "%s() dlflag (0x%x)= 0x%x\n", __func__, dlflag, reg);
+	if (reg == 1)
+		dprintk(DBGLVL_FW,
+			"%s() Download flag already set, please reboot\n",
+			__func__);
+
+	/* Indicate download start */
+	saa7164_writel(dlflag, 1);
+	ret = saa7164_dl_wait_ack(dev, dlflag_ack);
+	if (ret < 0)
+		goto out;
+
+	/* Ack download start, then wait for wait */
+	saa7164_writel(dlflag, 0);
+	ret = saa7164_dl_wait_clr(dev, dlflag_ack);
+	if (ret < 0)
+		goto out;
+
+	/* Deal with the raw firmware, in the appropriate chunk size */
+	for (offset = 0; srcsize > dstsize;
+		srcsize -= dstsize, offset += dstsize) {
+
+		dprintk(DBGLVL_FW, "%s() memcpy %d\n", __func__, dstsize);
+		memcpy_toio(dst, srcbuf + offset, dstsize);
+
+		/* Flag the data as ready */
+		saa7164_writel(drflag, 1);
+		ret = saa7164_dl_wait_ack(dev, drflag_ack);
+		if (ret < 0)
+			goto out;
+
+		/* Wait for indication data was received */
+		saa7164_writel(drflag, 0);
+		ret = saa7164_dl_wait_clr(dev, drflag_ack);
+		if (ret < 0)
+			goto out;
+
+	}
+
+	dprintk(DBGLVL_FW, "%s() memcpy(l) %d\n", __func__, dstsize);
+	/* Write last block to the device */
+	memcpy_toio(dst, srcbuf+offset, srcsize);
+
+	/* Flag the data as ready */
+	saa7164_writel(drflag, 1);
+	ret = saa7164_dl_wait_ack(dev, drflag_ack);
+	if (ret < 0)
+		goto out;
+
+	saa7164_writel(drflag, 0);
+	timeout = 0;
+	while (saa7164_readl(bleflag) != SAA_DEVICE_IMAGE_BOOTING) {
+		if (saa7164_readl(bleflag) & SAA_DEVICE_IMAGE_CORRUPT) {
+			printk(KERN_ERR "%s() image corrupt\n", __func__);
+			ret = -EBUSY;
+			goto out;
+		}
+
+		if (saa7164_readl(bleflag) & SAA_DEVICE_MEMORY_CORRUPT) {
+			printk(KERN_ERR "%s() device memory corrupt\n",
+				__func__);
+			ret = -EBUSY;
+			goto out;
+		}
+
+		msleep(10); /* Checkpatch throws a < 20ms warning */
+		if (timeout++ > 60)
+			break;
+	}
+
+	printk(KERN_INFO "%s() Image downloaded, booting...\n", __func__);
+
+	ret = saa7164_dl_wait_clr(dev, drflag_ack);
+	if (ret < 0)
+		goto out;
+
+	printk(KERN_INFO "%s() Image booted successfully.\n", __func__);
+	ret = 0;
+
+out:
+	kfree(srcbuf);
+	return ret;
+}
+
+/* TODO: Excessive debug */
+/* Load the firmware. Optionally it can be in ROM or newer versions
+ * can be on disk, saving the expense of the ROM hardware. */
+int saa7164_downloadfirmware(struct saa7164_dev *dev)
+{
+	/* u32 second_timeout = 60 * SAA_DEVICE_TIMEOUT; */
+	u32 tmp, filesize, version, err_flags, first_timeout, fwlength;
+	u32 second_timeout, updatebootloader = 1, bootloadersize = 0;
+	const struct firmware *fw = NULL;
+	struct fw_header *hdr, *boothdr = NULL, *fwhdr;
+	u32 bootloaderversion = 0, fwloadersize;
+	u8 *bootloaderoffset = NULL, *fwloaderoffset;
+	char *fwname;
+	int ret;
+
+	dprintk(DBGLVL_FW, "%s()\n", __func__);
+
+	if (saa7164_boards[dev->board].chiprev == SAA7164_CHIP_REV2) {
+		fwname = SAA7164_REV2_FIRMWARE;
+		fwlength = SAA7164_REV2_FIRMWARE_SIZE;
+	} else {
+		fwname = SAA7164_REV3_FIRMWARE;
+		fwlength = SAA7164_REV3_FIRMWARE_SIZE;
+	}
+
+	version = saa7164_getcurrentfirmwareversion(dev);
+
+	if (version == 0x00) {
+
+		second_timeout = 100;
+		first_timeout = 100;
+		err_flags = saa7164_readl(SAA_BOOTLOADERERROR_FLAGS);
+		dprintk(DBGLVL_FW, "%s() err_flags = %x\n",
+			__func__, err_flags);
+
+		while (err_flags != SAA_DEVICE_IMAGE_BOOTING) {
+			dprintk(DBGLVL_FW, "%s() err_flags = %x\n",
+				__func__, err_flags);
+			msleep(10); /* Checkpatch throws a < 20ms warning */
+
+			if (err_flags & SAA_DEVICE_IMAGE_CORRUPT) {
+				printk(KERN_ERR "%s() firmware corrupt\n",
+					__func__);
+				break;
+			}
+			if (err_flags & SAA_DEVICE_MEMORY_CORRUPT) {
+				printk(KERN_ERR "%s() device memory corrupt\n",
+					__func__);
+				break;
+			}
+			if (err_flags & SAA_DEVICE_NO_IMAGE) {
+				printk(KERN_ERR "%s() no first image\n",
+				__func__);
+				break;
+			}
+			if (err_flags & SAA_DEVICE_IMAGE_SEARCHING) {
+				first_timeout -= 10;
+				if (first_timeout == 0) {
+					printk(KERN_ERR
+						"%s() no first image\n",
+						__func__);
+					break;
+				}
+			} else if (err_flags & SAA_DEVICE_IMAGE_LOADING) {
+				second_timeout -= 10;
+				if (second_timeout == 0) {
+					printk(KERN_ERR
+					"%s() FW load time exceeded\n",
+						__func__);
+					break;
+				}
+			} else {
+				second_timeout -= 10;
+				if (second_timeout == 0) {
+					printk(KERN_ERR
+					"%s() Unknown bootloader flags 0x%x\n",
+						__func__, err_flags);
+					break;
+				}
+			}
+
+			err_flags = saa7164_readl(SAA_BOOTLOADERERROR_FLAGS);
+		} /* While != Booting */
+
+		if (err_flags == SAA_DEVICE_IMAGE_BOOTING) {
+			dprintk(DBGLVL_FW, "%s() Loader 1 has loaded.\n",
+				__func__);
+			first_timeout = SAA_DEVICE_TIMEOUT;
+			second_timeout = 60 * SAA_DEVICE_TIMEOUT;
+			second_timeout = 100;
+
+			err_flags = saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS);
+			dprintk(DBGLVL_FW, "%s() err_flags2 = %x\n",
+				__func__, err_flags);
+			while (err_flags != SAA_DEVICE_IMAGE_BOOTING) {
+				dprintk(DBGLVL_FW, "%s() err_flags2 = %x\n",
+					__func__, err_flags);
+				msleep(10); /* Checkpatch throws a < 20ms warning */
+
+				if (err_flags & SAA_DEVICE_IMAGE_CORRUPT) {
+					printk(KERN_ERR
+						"%s() firmware corrupt\n",
+						__func__);
+					break;
+				}
+				if (err_flags & SAA_DEVICE_MEMORY_CORRUPT) {
+					printk(KERN_ERR
+						"%s() device memory corrupt\n",
+						__func__);
+					break;
+				}
+				if (err_flags & SAA_DEVICE_NO_IMAGE) {
+					printk(KERN_ERR "%s() no first image\n",
+						__func__);
+					break;
+				}
+				if (err_flags & SAA_DEVICE_IMAGE_SEARCHING) {
+					first_timeout -= 10;
+					if (first_timeout == 0) {
+						printk(KERN_ERR
+						"%s() no second image\n",
+							__func__);
+						break;
+					}
+				} else if (err_flags &
+					SAA_DEVICE_IMAGE_LOADING) {
+					second_timeout -= 10;
+					if (second_timeout == 0) {
+						printk(KERN_ERR
+						"%s() FW load time exceeded\n",
+							__func__);
+						break;
+					}
+				} else {
+					second_timeout -= 10;
+					if (second_timeout == 0) {
+						printk(KERN_ERR
+					"%s() Unknown bootloader flags 0x%x\n",
+							__func__, err_flags);
+						break;
+					}
+				}
+
+				err_flags =
+				saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS);
+			} /* err_flags != SAA_DEVICE_IMAGE_BOOTING */
+
+			dprintk(DBGLVL_FW, "%s() Loader flags 1:0x%x 2:0x%x.\n",
+				__func__,
+				saa7164_readl(SAA_BOOTLOADERERROR_FLAGS),
+				saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS));
+
+		} /* err_flags == SAA_DEVICE_IMAGE_BOOTING */
+
+		/* It's possible for both firmwares to have booted,
+		 * but that doesn't mean they've finished booting yet.
+		 */
+		if ((saa7164_readl(SAA_BOOTLOADERERROR_FLAGS) ==
+			SAA_DEVICE_IMAGE_BOOTING) &&
+			(saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS) ==
+			SAA_DEVICE_IMAGE_BOOTING)) {
+
+
+			dprintk(DBGLVL_FW, "%s() Loader 2 has loaded.\n",
+				__func__);
+
+			first_timeout = SAA_DEVICE_TIMEOUT;
+			while (first_timeout) {
+				msleep(10); /* Checkpatch throws a < 20ms warning */
+
+				version =
+					saa7164_getcurrentfirmwareversion(dev);
+				if (version) {
+					dprintk(DBGLVL_FW,
+					"%s() All f/w loaded successfully\n",
+						__func__);
+					break;
+				} else {
+					first_timeout -= 10;
+					if (first_timeout == 0) {
+						printk(KERN_ERR
+						"%s() FW did not boot\n",
+							__func__);
+						break;
+					}
+				}
+			}
+		}
+		version = saa7164_getcurrentfirmwareversion(dev);
+	} /* version == 0 */
+
+	/* Has the firmware really booted? */
+	if ((saa7164_readl(SAA_BOOTLOADERERROR_FLAGS) ==
+		SAA_DEVICE_IMAGE_BOOTING) &&
+		(saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS) ==
+		SAA_DEVICE_IMAGE_BOOTING) && (version == 0)) {
+
+		printk(KERN_ERR
+			"%s() The firmware hung, probably bad firmware\n",
+			__func__);
+
+		/* Tell the second stage loader we have a deadlock */
+		saa7164_writel(SAA_DEVICE_DEADLOCK_DETECTED_OFFSET,
+			SAA_DEVICE_DEADLOCK_DETECTED);
+
+		saa7164_getfirmwarestatus(dev);
+
+		return -ENOMEM;
+	}
+
+	dprintk(DBGLVL_FW, "Device has Firmware Version %d.%d.%d.%d\n",
+		(version & 0x0000fc00) >> 10,
+		(version & 0x000003e0) >> 5,
+		(version & 0x0000001f),
+		(version & 0xffff0000) >> 16);
+
+	/* Load the firmwware from the disk if required */
+	if (version == 0) {
+
+		printk(KERN_INFO "%s() Waiting for firmware upload (%s)\n",
+			__func__, fwname);
+
+		ret = request_firmware(&fw, fwname, &dev->pci->dev);
+		if (ret) {
+			printk(KERN_ERR "%s() Upload failed. "
+				"(file not found?)\n", __func__);
+			return -ENOMEM;
+		}
+
+		printk(KERN_INFO "%s() firmware read %Zu bytes.\n",
+			__func__, fw->size);
+
+		if (fw->size != fwlength) {
+			printk(KERN_ERR "xc5000: firmware incorrect size\n");
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		printk(KERN_INFO "%s() firmware loaded.\n", __func__);
+
+		hdr = (struct fw_header *)fw->data;
+		printk(KERN_INFO "Firmware file header part 1:\n");
+		printk(KERN_INFO " .FirmwareSize = 0x%x\n", hdr->firmwaresize);
+		printk(KERN_INFO " .BSLSize = 0x%x\n", hdr->bslsize);
+		printk(KERN_INFO " .Reserved = 0x%x\n", hdr->reserved);
+		printk(KERN_INFO " .Version = 0x%x\n", hdr->version);
+
+		/* Retrieve bootloader if reqd */
+		if ((hdr->firmwaresize == 0) && (hdr->bslsize == 0))
+			/* Second bootloader in the firmware file */
+			filesize = hdr->reserved * 16;
+		else
+			filesize = (hdr->firmwaresize + hdr->bslsize) *
+				16 + sizeof(struct fw_header);
+
+		printk(KERN_INFO "%s() SecBootLoader.FileSize = %d\n",
+			__func__, filesize);
+
+		/* Get bootloader (if reqd) and firmware header */
+		if ((hdr->firmwaresize == 0) && (hdr->bslsize == 0)) {
+			/* Second boot loader is required */
+
+			/* Get the loader header */
+			boothdr = (struct fw_header *)(fw->data +
+				sizeof(struct fw_header));
+
+			bootloaderversion =
+				saa7164_readl(SAA_DEVICE_2ND_VERSION);
+			dprintk(DBGLVL_FW, "Onboard BootLoader:\n");
+			dprintk(DBGLVL_FW, "->Flag 0x%x\n",
+				saa7164_readl(SAA_BOOTLOADERERROR_FLAGS));
+			dprintk(DBGLVL_FW, "->Ack 0x%x\n",
+				saa7164_readl(SAA_DATAREADY_FLAG_ACK));
+			dprintk(DBGLVL_FW, "->FW Version 0x%x\n", version);
+			dprintk(DBGLVL_FW, "->Loader Version 0x%x\n",
+				bootloaderversion);
+
+			if ((saa7164_readl(SAA_BOOTLOADERERROR_FLAGS) ==
+				0x03) && (saa7164_readl(SAA_DATAREADY_FLAG_ACK)
+				== 0x00) && (version == 0x00)) {
+
+				dprintk(DBGLVL_FW, "BootLoader version in  "
+					"rom %d.%d.%d.%d\n",
+					(bootloaderversion & 0x0000fc00) >> 10,
+					(bootloaderversion & 0x000003e0) >> 5,
+					(bootloaderversion & 0x0000001f),
+					(bootloaderversion & 0xffff0000) >> 16
+					);
+				dprintk(DBGLVL_FW, "BootLoader version "
+					"in file %d.%d.%d.%d\n",
+					(boothdr->version & 0x0000fc00) >> 10,
+					(boothdr->version & 0x000003e0) >> 5,
+					(boothdr->version & 0x0000001f),
+					(boothdr->version & 0xffff0000) >> 16
+					);
+
+				if (bootloaderversion == boothdr->version)
+					updatebootloader = 0;
+			}
+
+			/* Calculate offset to firmware header */
+			tmp = (boothdr->firmwaresize + boothdr->bslsize) * 16 +
+				(sizeof(struct fw_header) +
+				sizeof(struct fw_header));
+
+			fwhdr = (struct fw_header *)(fw->data+tmp);
+		} else {
+			/* No second boot loader */
+			fwhdr = hdr;
+		}
+
+		dprintk(DBGLVL_FW, "Firmware version in file %d.%d.%d.%d\n",
+			(fwhdr->version & 0x0000fc00) >> 10,
+			(fwhdr->version & 0x000003e0) >> 5,
+			(fwhdr->version & 0x0000001f),
+			(fwhdr->version & 0xffff0000) >> 16
+			);
+
+		if (version == fwhdr->version) {
+			/* No download, firmware already on board */
+			ret = 0;
+			goto out;
+		}
+
+		if ((hdr->firmwaresize == 0) && (hdr->bslsize == 0)) {
+			if (updatebootloader) {
+				/* Get ready to upload the bootloader */
+				bootloadersize = (boothdr->firmwaresize +
+					boothdr->bslsize) * 16 +
+					sizeof(struct fw_header);
+
+				bootloaderoffset = (u8 *)(fw->data +
+					sizeof(struct fw_header));
+
+				dprintk(DBGLVL_FW, "bootloader d/l starts.\n");
+				printk(KERN_INFO "%s() FirmwareSize = 0x%x\n",
+					__func__, boothdr->firmwaresize);
+				printk(KERN_INFO "%s() BSLSize = 0x%x\n",
+					__func__, boothdr->bslsize);
+				printk(KERN_INFO "%s() Reserved = 0x%x\n",
+					__func__, boothdr->reserved);
+				printk(KERN_INFO "%s() Version = 0x%x\n",
+					__func__, boothdr->version);
+				ret = saa7164_downloadimage(
+					dev,
+					bootloaderoffset,
+					bootloadersize,
+					SAA_DOWNLOAD_FLAGS,
+					dev->bmmio + SAA_DEVICE_DOWNLOAD_OFFSET,
+					SAA_DEVICE_BUFFERBLOCKSIZE);
+				if (ret < 0) {
+					printk(KERN_ERR
+						"bootloader d/l has failed\n");
+					goto out;
+				}
+				dprintk(DBGLVL_FW,
+					"bootloader download complete.\n");
+
+			}
+
+			printk(KERN_ERR "starting firmware download(2)\n");
+			bootloadersize = (boothdr->firmwaresize +
+				boothdr->bslsize) * 16 +
+				sizeof(struct fw_header);
+
+			bootloaderoffset =
+				(u8 *)(fw->data + sizeof(struct fw_header));
+
+			fwloaderoffset = bootloaderoffset + bootloadersize;
+
+			/* TODO: fix this bounds overrun here with old f/ws */
+			fwloadersize = (fwhdr->firmwaresize + fwhdr->bslsize) *
+				16 + sizeof(struct fw_header);
+
+			ret = saa7164_downloadimage(
+				dev,
+				fwloaderoffset,
+				fwloadersize,
+				SAA_DEVICE_2ND_DOWNLOADFLAG_OFFSET,
+				dev->bmmio + SAA_DEVICE_2ND_DOWNLOAD_OFFSET,
+				SAA_DEVICE_2ND_BUFFERBLOCKSIZE);
+			if (ret < 0) {
+				printk(KERN_ERR "firmware download failed\n");
+				goto out;
+			}
+			printk(KERN_ERR "firmware download complete.\n");
+
+		} else {
+
+			/* No bootloader update reqd, download firmware only */
+			printk(KERN_ERR "starting firmware download(3)\n");
+
+			ret = saa7164_downloadimage(
+				dev,
+				(u8 *)fw->data,
+				fw->size,
+				SAA_DOWNLOAD_FLAGS,
+				dev->bmmio + SAA_DEVICE_DOWNLOAD_OFFSET,
+				SAA_DEVICE_BUFFERBLOCKSIZE);
+			if (ret < 0) {
+				printk(KERN_ERR "firmware download failed\n");
+				goto out;
+			}
+			printk(KERN_ERR "firmware download complete.\n");
+		}
+	}
+
+	dev->firmwareloaded = 1;
+	ret = 0;
+
+out:
+	release_firmware(fw);
+	return ret;
+}
diff --git a/drivers/media/pci/saa7164/saa7164-i2c.c b/drivers/media/pci/saa7164/saa7164-i2c.c
new file mode 100644
index 0000000..0342d84
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-i2c.c
@@ -0,0 +1,126 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include "saa7164.h"
+
+static int i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num)
+{
+	struct saa7164_i2c *bus = i2c_adap->algo_data;
+	struct saa7164_dev *dev = bus->dev;
+	int i, retval = 0;
+
+	dprintk(DBGLVL_I2C, "%s(num = %d)\n", __func__, num);
+
+	for (i = 0 ; i < num; i++) {
+		dprintk(DBGLVL_I2C, "%s(num = %d) addr = 0x%02x  len = 0x%x\n",
+			__func__, num, msgs[i].addr, msgs[i].len);
+		if (msgs[i].flags & I2C_M_RD) {
+			retval = saa7164_api_i2c_read(bus,
+				msgs[i].addr,
+				0 /* reglen */,
+				NULL /* reg */, msgs[i].len, msgs[i].buf);
+		} else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) &&
+			   msgs[i].addr == msgs[i + 1].addr) {
+			/* write then read from same address */
+
+			retval = saa7164_api_i2c_read(bus, msgs[i].addr,
+				msgs[i].len, msgs[i].buf,
+				msgs[i+1].len, msgs[i+1].buf
+				);
+
+			i++;
+
+			if (retval < 0)
+				goto err;
+		} else {
+			/* write */
+			retval = saa7164_api_i2c_write(bus, msgs[i].addr,
+				msgs[i].len, msgs[i].buf);
+		}
+		if (retval < 0)
+			goto err;
+	}
+	return num;
+
+err:
+	return retval;
+}
+
+static u32 saa7164_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm saa7164_i2c_algo_template = {
+	.master_xfer	= i2c_xfer,
+	.functionality	= saa7164_functionality,
+};
+
+/* ----------------------------------------------------------------------- */
+
+static struct i2c_adapter saa7164_i2c_adap_template = {
+	.name              = "saa7164",
+	.owner             = THIS_MODULE,
+	.algo              = &saa7164_i2c_algo_template,
+};
+
+static struct i2c_client saa7164_i2c_client_template = {
+	.name	= "saa7164 internal",
+};
+
+int saa7164_i2c_register(struct saa7164_i2c *bus)
+{
+	struct saa7164_dev *dev = bus->dev;
+
+	dprintk(DBGLVL_I2C, "%s(bus = %d)\n", __func__, bus->nr);
+
+	bus->i2c_adap = saa7164_i2c_adap_template;
+	bus->i2c_client = saa7164_i2c_client_template;
+
+	bus->i2c_adap.dev.parent = &dev->pci->dev;
+
+	strlcpy(bus->i2c_adap.name, bus->dev->name,
+		sizeof(bus->i2c_adap.name));
+
+	bus->i2c_adap.algo_data = bus;
+	i2c_set_adapdata(&bus->i2c_adap, bus);
+	i2c_add_adapter(&bus->i2c_adap);
+
+	bus->i2c_client.adapter = &bus->i2c_adap;
+
+	if (0 != bus->i2c_rc)
+		printk(KERN_ERR "%s: i2c bus %d register FAILED\n",
+			dev->name, bus->nr);
+
+	return bus->i2c_rc;
+}
+
+int saa7164_i2c_unregister(struct saa7164_i2c *bus)
+{
+	i2c_del_adapter(&bus->i2c_adap);
+	return 0;
+}
diff --git a/drivers/media/pci/saa7164/saa7164-reg.h b/drivers/media/pci/saa7164/saa7164-reg.h
new file mode 100644
index 0000000..37521a2
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-reg.h
@@ -0,0 +1,219 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* TODO: Retest the driver with errors expressed as negatives */
+
+/* Result codes */
+#define SAA_OK				0
+#define SAA_ERR_BAD_PARAMETER		0x09
+#define SAA_ERR_NO_RESOURCES		0x0c
+#define SAA_ERR_NOT_SUPPORTED		0x13
+#define SAA_ERR_BUSY			0x15
+#define SAA_ERR_READ			0x17
+#define SAA_ERR_TIMEOUT			0x1f
+#define SAA_ERR_OVERFLOW		0x20
+#define SAA_ERR_EMPTY			0x22
+#define SAA_ERR_NOT_STARTED		0x23
+#define SAA_ERR_ALREADY_STARTED		0x24
+#define SAA_ERR_NOT_STOPPED		0x25
+#define SAA_ERR_ALREADY_STOPPED		0x26
+#define SAA_ERR_INVALID_COMMAND		0x3e
+#define SAA_ERR_NULL_PACKET		0x59
+
+/* Errors and flags from the silicon */
+#define PVC_ERRORCODE_UNKNOWN		0x00
+#define PVC_ERRORCODE_INVALID_COMMAND	0x01
+#define PVC_ERRORCODE_INVALID_CONTROL	0x02
+#define PVC_ERRORCODE_INVALID_DATA	0x03
+#define PVC_ERRORCODE_TIMEOUT		0x04
+#define PVC_ERRORCODE_NAK		0x05
+#define PVC_RESPONSEFLAG_ERROR		0x01
+#define PVC_RESPONSEFLAG_OVERFLOW	0x02
+#define PVC_RESPONSEFLAG_RESET		0x04
+#define PVC_RESPONSEFLAG_INTERFACE	0x08
+#define PVC_RESPONSEFLAG_CONTINUED	0x10
+#define PVC_CMDFLAG_INTERRUPT		0x02
+#define PVC_CMDFLAG_INTERFACE		0x04
+#define PVC_CMDFLAG_SERIALIZE		0x08
+#define PVC_CMDFLAG_CONTINUE		0x10
+
+/* Silicon Commands */
+#define GET_DESCRIPTORS_CONTROL		0x01
+#define GET_STRING_CONTROL		0x03
+#define GET_LANGUAGE_CONTROL		0x05
+#define SET_POWER_CONTROL		0x07
+#define GET_FW_STATUS_CONTROL		0x08
+#define GET_FW_VERSION_CONTROL		0x09
+#define SET_DEBUG_LEVEL_CONTROL		0x0B
+#define GET_DEBUG_DATA_CONTROL		0x0C
+#define GET_PRODUCTION_INFO_CONTROL	0x0D
+
+/* cmd defines */
+#define SAA_CMDFLAG_CONTINUE		0x10
+#define SAA_CMD_MAX_MSG_UNITS		256
+
+/* Some defines */
+#define SAA_BUS_TIMEOUT			50
+#define SAA_DEVICE_TIMEOUT		5000
+#define SAA_DEVICE_MAXREQUESTSIZE	256
+
+/* Register addresses */
+#define SAA_DEVICE_VERSION		0x30
+#define SAA_DOWNLOAD_FLAGS		0x34
+#define SAA_DOWNLOAD_FLAG		0x34
+#define SAA_DOWNLOAD_FLAG_ACK		0x38
+#define SAA_DATAREADY_FLAG		0x3C
+#define SAA_DATAREADY_FLAG_ACK		0x40
+
+/* Boot loader register and bit definitions */
+#define SAA_BOOTLOADERERROR_FLAGS	0x44
+#define SAA_DEVICE_IMAGE_SEARCHING	0x01
+#define SAA_DEVICE_IMAGE_LOADING	0x02
+#define SAA_DEVICE_IMAGE_BOOTING	0x03
+#define SAA_DEVICE_IMAGE_CORRUPT	0x04
+#define SAA_DEVICE_MEMORY_CORRUPT	0x08
+#define SAA_DEVICE_NO_IMAGE		0x10
+
+/* Register addresses */
+#define SAA_DEVICE_2ND_VERSION			0x50
+#define SAA_DEVICE_2ND_DOWNLOADFLAG_OFFSET	0x54
+
+/* Register addresses */
+#define SAA_SECONDSTAGEERROR_FLAGS		0x64
+
+/* Bootloader regs and flags */
+#define SAA_DEVICE_DEADLOCK_DETECTED_OFFSET	0x6C
+#define SAA_DEVICE_DEADLOCK_DETECTED		0xDEADDEAD
+
+/* Basic firmware status registers */
+#define SAA_DEVICE_SYSINIT_STATUS_OFFSET	0x70
+#define SAA_DEVICE_SYSINIT_STATUS		0x70
+#define SAA_DEVICE_SYSINIT_MODE			0x74
+#define SAA_DEVICE_SYSINIT_SPEC			0x78
+#define SAA_DEVICE_SYSINIT_INST			0x7C
+#define SAA_DEVICE_SYSINIT_CPULOAD		0x80
+#define SAA_DEVICE_SYSINIT_REMAINHEAP		0x84
+
+#define SAA_DEVICE_DOWNLOAD_OFFSET		0x1000
+#define SAA_DEVICE_BUFFERBLOCKSIZE		0x1000
+
+#define SAA_DEVICE_2ND_BUFFERBLOCKSIZE		0x100000
+#define SAA_DEVICE_2ND_DOWNLOAD_OFFSET		0x200000
+
+/* Descriptors */
+#define CS_INTERFACE	0x24
+
+/* Descriptor subtypes */
+#define VC_INPUT_TERMINAL		0x02
+#define VC_OUTPUT_TERMINAL		0x03
+#define VC_SELECTOR_UNIT		0x04
+#define VC_PROCESSING_UNIT		0x05
+#define FEATURE_UNIT			0x06
+#define TUNER_UNIT			0x09
+#define ENCODER_UNIT			0x0A
+#define EXTENSION_UNIT			0x0B
+#define VC_TUNER_PATH			0xF0
+#define PVC_HARDWARE_DESCRIPTOR		0xF1
+#define PVC_INTERFACE_DESCRIPTOR	0xF2
+#define PVC_INFRARED_UNIT		0xF3
+#define DRM_UNIT			0xF4
+#define GENERAL_REQUEST			0xF5
+
+/* Format Types */
+#define VS_FORMAT_TYPE         0x02
+#define VS_FORMAT_TYPE_I       0x01
+#define VS_FORMAT_UNCOMPRESSED 0x04
+#define VS_FRAME_UNCOMPRESSED  0x05
+#define VS_FORMAT_MPEG2PS      0x09
+#define VS_FORMAT_MPEG2TS      0x0A
+#define VS_FORMAT_MPEG4SL      0x0B
+#define VS_FORMAT_WM9          0x0C
+#define VS_FORMAT_DIVX         0x0D
+#define VS_FORMAT_VBI          0x0E
+#define VS_FORMAT_RDS          0x0F
+
+/* Device extension commands */
+#define EXU_REGISTER_ACCESS_CONTROL	0x00
+#define EXU_GPIO_CONTROL		0x01
+#define EXU_GPIO_GROUP_CONTROL		0x02
+#define EXU_INTERRUPT_CONTROL		0x03
+
+/* State Transition and args */
+#define SAA_PROBE_CONTROL	0x01
+#define SAA_COMMIT_CONTROL	0x02
+#define SAA_STATE_CONTROL	0x03
+#define SAA_DMASTATE_STOP	0x00
+#define SAA_DMASTATE_ACQUIRE	0x01
+#define SAA_DMASTATE_PAUSE	0x02
+#define SAA_DMASTATE_RUN	0x03
+
+/* A/V Mux Input Selector */
+#define SU_INPUT_SELECT_CONTROL 0x01
+
+/* Encoder Profiles */
+#define EU_PROFILE_PS_DVD	0x06
+#define EU_PROFILE_TS_HQ	0x09
+#define EU_VIDEO_FORMAT_MPEG_2	0x02
+
+/* Tuner */
+#define TU_AUDIO_MODE_CONTROL  0x17
+
+/* Video Formats */
+#define TU_STANDARD_CONTROL		0x00
+#define TU_STANDARD_AUTO_CONTROL	0x01
+#define TU_STANDARD_NONE		0x00
+#define TU_STANDARD_NTSC_M		0x01
+#define TU_STANDARD_PAL_I		0x08
+#define TU_STANDARD_MANUAL		0x00
+#define TU_STANDARD_AUTO		0x01
+
+/* Video Controls */
+#define PU_BRIGHTNESS_CONTROL	0x02
+#define PU_CONTRAST_CONTROL	0x03
+#define PU_HUE_CONTROL		0x06
+#define PU_SATURATION_CONTROL	0x07
+#define PU_SHARPNESS_CONTROL	0x08
+
+/* Audio Controls */
+#define MUTE_CONTROL		0x01
+#define VOLUME_CONTROL		0x02
+#define AUDIO_DEFAULT_CONTROL	0x0D
+
+/* Default Volume Levels */
+#define TMHW_LEV_ADJ_DECLEV_DEFAULT     0x00
+#define TMHW_LEV_ADJ_MONOLEV_DEFAULT    0x00
+#define TMHW_LEV_ADJ_NICLEV_DEFAULT     0x00
+#define TMHW_LEV_ADJ_SAPLEV_DEFAULT     0x00
+#define TMHW_LEV_ADJ_ADCLEV_DEFAULT     0x00
+
+/* Encoder Related Commands */
+#define EU_PROFILE_CONTROL		0x00
+#define EU_VIDEO_FORMAT_CONTROL		0x01
+#define EU_VIDEO_BIT_RATE_CONTROL	0x02
+#define EU_VIDEO_RESOLUTION_CONTROL	0x03
+#define EU_VIDEO_GOP_STRUCTURE_CONTROL	0x04
+#define EU_VIDEO_INPUT_ASPECT_CONTROL	0x0A
+#define EU_AUDIO_FORMAT_CONTROL		0x0C
+#define EU_AUDIO_BIT_RATE_CONTROL	0x0D
+
+/* Firmware Debugging */
+#define SET_DEBUG_LEVEL_CONTROL	0x0B
+#define GET_DEBUG_DATA_CONTROL	0x0C
diff --git a/drivers/media/pci/saa7164/saa7164-types.h b/drivers/media/pci/saa7164/saa7164-types.h
new file mode 100644
index 0000000..1efba6c
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-types.h
@@ -0,0 +1,442 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* TODO: Cleanup and shorten the namespace */
+
+/* Some structues are passed directly to/from the firmware and
+ * have strict alignment requirements. This is one of them.
+ */
+struct tmComResHWDescr {
+	u8	bLength;
+	u8	bDescriptorType;
+	u8	bDescriptorSubtype;
+	u16	bcdSpecVersion;
+	u32	dwClockFrequency;
+	u32	dwClockUpdateRes;
+	u8	bCapabilities;
+	u32	dwDeviceRegistersLocation;
+	u32	dwHostMemoryRegion;
+	u32	dwHostMemoryRegionSize;
+	u32	dwHostHibernatMemRegion;
+	u32	dwHostHibernatMemRegionSize;
+} __attribute__((packed));
+
+/* This is DWORD aligned on windows but I can't find the right
+ * gcc syntax to match the binary data from the device.
+ * I've manually padded with Reserved[3] bytes to match the hardware,
+ * but this could break if GCC decies to pack in a different way.
+ */
+struct tmComResInterfaceDescr {
+	u8	bLength;
+	u8	bDescriptorType;
+	u8	bDescriptorSubtype;
+	u8	bFlags;
+	u8	bInterfaceType;
+	u8	bInterfaceId;
+	u8	bBaseInterface;
+	u8	bInterruptId;
+	u8	bDebugInterruptId;
+	u8	BARLocation;
+	u8	Reserved[3];
+};
+
+struct tmComResBusDescr {
+	u64	CommandRing;
+	u64	ResponseRing;
+	u32	CommandWrite;
+	u32	CommandRead;
+	u32	ResponseWrite;
+	u32	ResponseRead;
+};
+
+enum tmBusType {
+	NONE		= 0,
+	TYPE_BUS_PCI	= 1,
+	TYPE_BUS_PCIe	= 2,
+	TYPE_BUS_USB	= 3,
+	TYPE_BUS_I2C	= 4
+};
+
+struct tmComResBusInfo {
+	enum tmBusType Type;
+	u16	m_wMaxReqSize;
+	u8 __iomem *m_pdwSetRing;
+	u32	m_dwSizeSetRing;
+	u8 __iomem *m_pdwGetRing;
+	u32	m_dwSizeGetRing;
+	u32	m_dwSetWritePos;
+	u32	m_dwSetReadPos;
+	u32	m_dwGetWritePos;
+	u32	m_dwGetReadPos;
+
+	/* All access is protected */
+	struct mutex lock;
+
+};
+
+struct tmComResInfo {
+	u8	id;
+	u8	flags;
+	u16	size;
+	u32	command;
+	u16	controlselector;
+	u8	seqno;
+} __attribute__((packed));
+
+enum tmComResCmd {
+	SET_CUR  = 0x01,
+	GET_CUR  = 0x81,
+	GET_MIN  = 0x82,
+	GET_MAX  = 0x83,
+	GET_RES  = 0x84,
+	GET_LEN  = 0x85,
+	GET_INFO = 0x86,
+	GET_DEF  = 0x87
+};
+
+struct cmd {
+	u8 seqno;
+	u32 inuse;
+	u32 timeout;
+	u32 signalled;
+	struct mutex lock;
+	wait_queue_head_t wait;
+};
+
+struct tmDescriptor {
+	u32	pathid;
+	u32	size;
+	void	*descriptor;
+};
+
+struct tmComResDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	unitid;
+} __attribute__((packed));
+
+struct tmComResExtDevDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	unitid;
+	u32	devicetype;
+	u16	deviceid;
+	u32	numgpiopins;
+	u8	numgpiogroups;
+	u8	controlsize;
+} __attribute__((packed));
+
+struct tmComResGPIO {
+	u32	pin;
+	u8	state;
+} __attribute__((packed));
+
+struct tmComResPathDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	pathid;
+} __attribute__((packed));
+
+/* terminaltype */
+enum tmComResTermType {
+	ITT_ANTENNA              = 0x0203,
+	LINE_CONNECTOR           = 0x0603,
+	SPDIF_CONNECTOR          = 0x0605,
+	COMPOSITE_CONNECTOR      = 0x0401,
+	SVIDEO_CONNECTOR         = 0x0402,
+	COMPONENT_CONNECTOR      = 0x0403,
+	STANDARD_DMA             = 0xF101
+};
+
+struct tmComResAntTermDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	terminalid;
+	u16	terminaltype;
+	u8	assocterminal;
+	u8	iterminal;
+	u8	controlsize;
+} __attribute__((packed));
+
+struct tmComResTunerDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	unitid;
+	u8	sourceid;
+	u8	iunit;
+	u32	tuningstandards;
+	u8	controlsize;
+	u32	controls;
+} __attribute__((packed));
+
+enum tmBufferFlag {
+	/* the buffer does not contain any valid data */
+	TM_BUFFER_FLAG_EMPTY,
+
+	/* the buffer is filled with valid data */
+	TM_BUFFER_FLAG_DONE,
+
+	/* the buffer is the dummy buffer - TODO??? */
+	TM_BUFFER_FLAG_DUMMY_BUFFER
+};
+
+struct tmBuffer {
+	u64		*pagetablevirt;
+	u64		pagetablephys;
+	u16		offset;
+	u8		*context;
+	u64		timestamp;
+	enum tmBufferFlag BufferFlag;
+	u32		lostbuffers;
+	u32		validbuffers;
+	u64		*dummypagevirt;
+	u64		dummypagephys;
+	u64		*addressvirt;
+};
+
+struct tmHWStreamParameters {
+	u32	bitspersample;
+	u32	samplesperline;
+	u32	numberoflines;
+	u32	pitch;
+	u32	linethreshold;
+	u64	**pagetablelistvirt;
+	u64	*pagetablelistphys;
+	u32	numpagetables;
+	u32	numpagetableentries;
+};
+
+struct tmStreamParameters {
+	struct tmHWStreamParameters	HWStreamParameters;
+	u64				qwDummyPageTablePhys;
+	u64				*pDummyPageTableVirt;
+};
+
+struct tmComResDMATermDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtyle;
+	u8	unitid;
+	u16	terminaltype;
+	u8	assocterminal;
+	u8	sourceid;
+	u8	iterminal;
+	u32	BARLocation;
+	u8	flags;
+	u8	interruptid;
+	u8	buffercount;
+	u8	metadatasize;
+	u8	numformats;
+	u8	controlsize;
+} __attribute__((packed));
+
+/*
+ *
+ * Description:
+ *  This is the transport stream format header.
+ *
+ * Settings:
+ *  bLength                 - The size of this descriptor in bytes.
+ *  bDescriptorType         - CS_INTERFACE.
+ *  bDescriptorSubtype      - VS_FORMAT_MPEG2TS descriptor subtype.
+ *  bFormatIndex            - A non-zero constant that uniquely identifies the
+ *                            format.
+ *  bDataOffset             - Offset to TSP packet within MPEG-2 TS transport
+ *                            stride, in bytes.
+ *  bPacketLength           - Length of TSP packet, in bytes (typically 188).
+ *  bStrideLength           - Length of MPEG-2 TS transport stride.
+ *  guidStrideFormat        - A Globally Unique Identifier indicating the
+ *                            format of the stride data (if any). Set to zeros
+ *                            if there is no Stride Data, or if the Stride
+ *                            Data is to be ignored by the application.
+ *
+ */
+struct tmComResTSFormatDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	bFormatIndex;
+	u8	bDataOffset;
+	u8	bPacketLength;
+	u8	bStrideLength;
+	u8	guidStrideFormat[16];
+} __attribute__((packed));
+
+/* Encoder related structures */
+
+/* A/V Mux Selector */
+struct tmComResSelDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	unitid;
+	u8	nrinpins;
+	u8	sourceid;
+} __attribute__((packed));
+
+/* A/V Audio processor definitions */
+struct tmComResProcDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	unitid;
+	u8	sourceid;
+	u16	wreserved;
+	u8	controlsize;
+} __attribute__((packed));
+
+/* Video bitrate control message */
+#define EU_VIDEO_BIT_RATE_MODE_CONSTANT		(0)
+#define EU_VIDEO_BIT_RATE_MODE_VARIABLE_AVERAGE (1)
+#define EU_VIDEO_BIT_RATE_MODE_VARIABLE_PEAK	(2)
+struct tmComResEncVideoBitRate {
+	u8	ucVideoBitRateMode;
+	u32	dwVideoBitRate;
+	u32	dwVideoBitRatePeak;
+} __attribute__((packed));
+
+/* Video Encoder Aspect Ratio message */
+struct tmComResEncVideoInputAspectRatio {
+	u8	width;
+	u8	height;
+} __attribute__((packed));
+
+/* Video Encoder GOP IBP message */
+/* 1. IPPPPPPPPPPPPPP */
+/* 2. IBPBPBPBPBPBPBP */
+/* 3. IBBPBBPBBPBBP   */
+#define SAA7164_ENCODER_DEFAULT_GOP_DIST (1)
+#define SAA7164_ENCODER_DEFAULT_GOP_SIZE (15)
+struct tmComResEncVideoGopStructure {
+	u8	ucGOPSize;	/* GOP Size 12, 15 */
+	u8	ucRefFrameDist; /* Reference Frame Distance */
+} __attribute__((packed));
+
+/* Encoder processor definition */
+struct tmComResEncoderDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	unitid;
+	u8	vsourceid;
+	u8	asourceid;
+	u8	iunit;
+	u32	dwmControlCap;
+	u32	dwmProfileCap;
+	u32	dwmVidFormatCap;
+	u8	bmVidBitrateCap;
+	u16	wmVidResolutionsCap;
+	u16	wmVidFrmRateCap;
+	u32	dwmAudFormatCap;
+	u8	bmAudBitrateCap;
+} __attribute__((packed));
+
+/* Audio processor definition */
+struct tmComResAFeatureDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	unitid;
+	u8	sourceid;
+	u8	controlsize;
+} __attribute__((packed));
+
+/* Audio control messages */
+struct tmComResAudioDefaults {
+	u8	ucDecoderLevel;
+	u8	ucDecoderFM_Level;
+	u8	ucMonoLevel;
+	u8	ucNICAM_Level;
+	u8	ucSAP_Level;
+	u8	ucADC_Level;
+} __attribute__((packed));
+
+/* Audio bitrate control message */
+struct tmComResEncAudioBitRate {
+	u8	ucAudioBitRateMode;
+	u32	dwAudioBitRate;
+	u32	dwAudioBitRatePeak;
+} __attribute__((packed));
+
+/* Tuner / AV Decoder messages */
+struct tmComResTunerStandard {
+	u8	std;
+	u32	country;
+} __attribute__((packed));
+
+struct tmComResTunerStandardAuto {
+	u8	mode;
+} __attribute__((packed));
+
+/* EEPROM definition for PS stream types */
+struct tmComResPSFormatDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype;
+	u8	bFormatIndex;
+	u16	wPacketLength;
+	u16	wPackLength;
+	u8	bPackDataType;
+} __attribute__((packed));
+
+/* VBI control structure */
+struct tmComResVBIFormatDescrHeader {
+	u8	len;
+	u8	type;
+	u8	subtype; /* VS_FORMAT_VBI */
+	u8	bFormatIndex;
+	u32	VideoStandard; /* See KS_AnalogVideoStandard, NTSC = 1 */
+	u8	StartLine; /* NTSC Start = 10 */
+	u8	EndLine; /* NTSC = 21 */
+	u8	FieldRate; /* 60 for NTSC */
+	u8	bNumLines; /* Unused - scheduled for removal */
+} __attribute__((packed));
+
+struct tmComResProbeCommit {
+	u16	bmHint;
+	u8	bFormatIndex;
+	u8	bFrameIndex;
+} __attribute__((packed));
+
+struct tmComResDebugSetLevel {
+	u32	dwDebugLevel;
+} __attribute__((packed));
+
+struct tmComResDebugGetData {
+	u32	dwResult;
+	u8	ucDebugData[256];
+} __attribute__((packed));
+
+struct tmFwInfoStruct {
+	u32	status;
+	u32	mode;
+	u32	devicespec;
+	u32	deviceinst;
+	u32	CPULoad;
+	u32	RemainHeap;
+	u32	CPUClock;
+	u32	RAMSpeed;
+} __attribute__((packed));
diff --git a/drivers/media/pci/saa7164/saa7164-vbi.c b/drivers/media/pci/saa7164/saa7164-vbi.c
new file mode 100644
index 0000000..ee54491
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164-vbi.c
@@ -0,0 +1,792 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "saa7164.h"
+
+/* Take the encoder configuration from the port struct and
+ * flush it to the hardware.
+ */
+static void saa7164_vbi_configure(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	dprintk(DBGLVL_VBI, "%s()\n", __func__);
+
+	port->vbi_params.width = port->enc_port->width;
+	port->vbi_params.height = port->enc_port->height;
+	port->vbi_params.is_50hz =
+		(port->enc_port->encodernorm.id & V4L2_STD_625_50) != 0;
+
+	/* Set up the DIF (enable it) for analog mode by default */
+	saa7164_api_initialize_dif(port);
+	dprintk(DBGLVL_VBI, "%s() ends\n", __func__);
+}
+
+static int saa7164_vbi_buffers_dealloc(struct saa7164_port *port)
+{
+	struct list_head *c, *n, *p, *q, *l, *v;
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct saa7164_user_buffer *ubuf;
+
+	/* Remove any allocated buffers */
+	mutex_lock(&port->dmaqueue_lock);
+
+	dprintk(DBGLVL_VBI, "%s(port=%d) dmaqueue\n", __func__, port->nr);
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+		buf = list_entry(c, struct saa7164_buffer, list);
+		list_del(c);
+		saa7164_buffer_dealloc(buf);
+	}
+
+	dprintk(DBGLVL_VBI, "%s(port=%d) used\n", __func__, port->nr);
+	list_for_each_safe(p, q, &port->list_buf_used.list) {
+		ubuf = list_entry(p, struct saa7164_user_buffer, list);
+		list_del(p);
+		saa7164_buffer_dealloc_user(ubuf);
+	}
+
+	dprintk(DBGLVL_VBI, "%s(port=%d) free\n", __func__, port->nr);
+	list_for_each_safe(l, v, &port->list_buf_free.list) {
+		ubuf = list_entry(l, struct saa7164_user_buffer, list);
+		list_del(l);
+		saa7164_buffer_dealloc_user(ubuf);
+	}
+
+	mutex_unlock(&port->dmaqueue_lock);
+	dprintk(DBGLVL_VBI, "%s(port=%d) done\n", __func__, port->nr);
+
+	return 0;
+}
+
+/* Dynamic buffer switch at vbi start time */
+static int saa7164_vbi_buffers_alloc(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct saa7164_user_buffer *ubuf;
+	struct tmHWStreamParameters *params = &port->hw_streamingparams;
+	int result = -ENODEV, i;
+	int len = 0;
+
+	dprintk(DBGLVL_VBI, "%s()\n", __func__);
+
+	/* TODO: NTSC SPECIFIC */
+	/* Init and establish defaults */
+	params->samplesperline = 1440;
+	params->numberoflines = 12;
+	params->numberoflines = 18;
+	params->pitch = 1600;
+	params->pitch = 1440;
+	params->numpagetables = 2 +
+		((params->numberoflines * params->pitch) / PAGE_SIZE);
+	params->bitspersample = 8;
+	params->linethreshold = 0;
+	params->pagetablelistvirt = NULL;
+	params->pagetablelistphys = NULL;
+	params->numpagetableentries = port->hwcfg.buffercount;
+
+	/* Allocate the PCI resources, buffers (hard) */
+	for (i = 0; i < port->hwcfg.buffercount; i++) {
+		buf = saa7164_buffer_alloc(port,
+			params->numberoflines *
+			params->pitch);
+
+		if (!buf) {
+			printk(KERN_ERR "%s() failed "
+			       "(errno = %d), unable to allocate buffer\n",
+				__func__, result);
+			result = -ENOMEM;
+			goto failed;
+		} else {
+
+			mutex_lock(&port->dmaqueue_lock);
+			list_add_tail(&buf->list, &port->dmaqueue.list);
+			mutex_unlock(&port->dmaqueue_lock);
+
+		}
+	}
+
+	/* Allocate some kernel buffers for copying
+	 * to userpsace.
+	 */
+	len = params->numberoflines * params->pitch;
+
+	if (vbi_buffers < 16)
+		vbi_buffers = 16;
+	if (vbi_buffers > 512)
+		vbi_buffers = 512;
+
+	for (i = 0; i < vbi_buffers; i++) {
+
+		ubuf = saa7164_buffer_alloc_user(dev, len);
+		if (ubuf) {
+			mutex_lock(&port->dmaqueue_lock);
+			list_add_tail(&ubuf->list, &port->list_buf_free.list);
+			mutex_unlock(&port->dmaqueue_lock);
+		}
+
+	}
+
+	result = 0;
+
+failed:
+	return result;
+}
+
+
+static int saa7164_vbi_initialize(struct saa7164_port *port)
+{
+	saa7164_vbi_configure(port);
+	return 0;
+}
+
+/* -- V4L2 --------------------------------------------------------- */
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+
+	return saa7164_s_std(fh->port->enc_port, id);
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+	struct saa7164_encoder_fh *fh = file->private_data;
+
+	return saa7164_g_std(fh->port->enc_port, id);
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+
+	return saa7164_g_input(fh->port->enc_port, i);
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+
+	return saa7164_s_input(fh->port->enc_port, i);
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+	struct v4l2_frequency *f)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+
+	return saa7164_g_frequency(fh->port->enc_port, f);
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+	const struct v4l2_frequency *f)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+	int ret = saa7164_s_frequency(fh->port->enc_port, f);
+
+	if (ret == 0)
+		saa7164_vbi_initialize(fh->port);
+	return ret;
+}
+
+static int vidioc_querycap(struct file *file, void  *priv,
+	struct v4l2_capability *cap)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+	struct saa7164_dev *dev = port->dev;
+
+	strcpy(cap->driver, dev->name);
+	strlcpy(cap->card, saa7164_boards[dev->board].name,
+		sizeof(cap->card));
+	sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
+
+	cap->device_caps =
+		V4L2_CAP_VBI_CAPTURE |
+		V4L2_CAP_READWRITE |
+		V4L2_CAP_TUNER;
+
+	cap->capabilities = cap->device_caps |
+		V4L2_CAP_VIDEO_CAPTURE |
+		V4L2_CAP_DEVICE_CAPS;
+
+	return 0;
+}
+
+static int saa7164_vbi_stop_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() stop transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_VBI, "%s()    Stopped\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int saa7164_vbi_acquire_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() acquire transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_VBI, "%s() Acquired\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int saa7164_vbi_pause_port(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int ret;
+
+	ret = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
+	if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() pause transition failed, ret = 0x%x\n",
+			__func__, ret);
+		ret = -EIO;
+	} else {
+		dprintk(DBGLVL_VBI, "%s()   Paused\n", __func__);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+/* Firmware is very windows centric, meaning you have to transition
+ * the part through AVStream / KS Windows stages, forwards or backwards.
+ * States are: stopped, acquired (h/w), paused, started.
+ * We have to leave here will all of the soft buffers on the free list,
+ * else the cfg_post() func won't have soft buffers to correctly configure.
+ */
+static int saa7164_vbi_stop_streaming(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	struct saa7164_buffer *buf;
+	struct saa7164_user_buffer *ubuf;
+	struct list_head *c, *n;
+	int ret;
+
+	dprintk(DBGLVL_VBI, "%s(port=%d)\n", __func__, port->nr);
+
+	ret = saa7164_vbi_pause_port(port);
+	ret = saa7164_vbi_acquire_port(port);
+	ret = saa7164_vbi_stop_port(port);
+
+	dprintk(DBGLVL_VBI, "%s(port=%d) Hardware stopped\n", __func__,
+		port->nr);
+
+	/* Reset the state of any allocated buffer resources */
+	mutex_lock(&port->dmaqueue_lock);
+
+	/* Reset the hard and soft buffer state */
+	list_for_each_safe(c, n, &port->dmaqueue.list) {
+		buf = list_entry(c, struct saa7164_buffer, list);
+		buf->flags = SAA7164_BUFFER_FREE;
+		buf->pos = 0;
+	}
+
+	list_for_each_safe(c, n, &port->list_buf_used.list) {
+		ubuf = list_entry(c, struct saa7164_user_buffer, list);
+		ubuf->pos = 0;
+		list_move_tail(&ubuf->list, &port->list_buf_free.list);
+	}
+
+	mutex_unlock(&port->dmaqueue_lock);
+
+	/* Free any allocated resources */
+	saa7164_vbi_buffers_dealloc(port);
+
+	dprintk(DBGLVL_VBI, "%s(port=%d) Released\n", __func__, port->nr);
+
+	return ret;
+}
+
+static int saa7164_vbi_start_streaming(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int result, ret = 0;
+
+	dprintk(DBGLVL_VBI, "%s(port=%d)\n", __func__, port->nr);
+
+	port->done_first_interrupt = 0;
+
+	/* allocate all of the PCIe DMA buffer resources on the fly,
+	 * allowing switching between TS and PS payloads without
+	 * requiring a complete driver reload.
+	 */
+	saa7164_vbi_buffers_alloc(port);
+
+	/* Configure the encoder with any cache values */
+#if 0
+	saa7164_api_set_encoder(port);
+	saa7164_api_get_encoder(port);
+#endif
+
+	/* Place the empty buffers on the hardware */
+	saa7164_buffer_cfg_port(port);
+
+	/* Negotiate format */
+	if (saa7164_api_set_vbi_format(port) != SAA_OK) {
+		printk(KERN_ERR "%s() No supported VBI format\n", __func__);
+		ret = -EIO;
+		goto out;
+	}
+
+	/* Acquire the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() acquire transition failed, res = 0x%x\n",
+			__func__, result);
+
+		ret = -EIO;
+		goto out;
+	} else
+		dprintk(DBGLVL_VBI, "%s()   Acquired\n", __func__);
+
+	/* Pause the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() pause transition failed, res = 0x%x\n",
+				__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_vbi_stop_port(port);
+		if (result != SAA_OK) {
+			printk(KERN_ERR "%s() pause/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+
+		ret = -EIO;
+		goto out;
+	} else
+		dprintk(DBGLVL_VBI, "%s()   Paused\n", __func__);
+
+	/* Start the hardware */
+	result = saa7164_api_transition_port(port, SAA_DMASTATE_RUN);
+	if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
+		printk(KERN_ERR "%s() run transition failed, result = 0x%x\n",
+				__func__, result);
+
+		/* Stop the hardware, regardless */
+		result = saa7164_vbi_acquire_port(port);
+		result = saa7164_vbi_stop_port(port);
+		if (result != SAA_OK) {
+			printk(KERN_ERR "%s() run/forced stop transition "
+				"failed, res = 0x%x\n", __func__, result);
+		}
+
+		ret = -EIO;
+	} else
+		dprintk(DBGLVL_VBI, "%s()   Running\n", __func__);
+
+out:
+	return ret;
+}
+
+static int saa7164_vbi_fmt(struct file *file, void *priv,
+			   struct v4l2_format *f)
+{
+	/* ntsc */
+	f->fmt.vbi.samples_per_line = 1440;
+	f->fmt.vbi.sampling_rate = 27000000;
+	f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+	f->fmt.vbi.offset = 0;
+	f->fmt.vbi.flags = 0;
+	f->fmt.vbi.start[0] = 10;
+	f->fmt.vbi.count[0] = 18;
+	f->fmt.vbi.start[1] = 263 + 10 + 1;
+	f->fmt.vbi.count[1] = 18;
+	memset(f->fmt.vbi.reserved, 0, sizeof(f->fmt.vbi.reserved));
+	return 0;
+}
+
+static int fops_open(struct file *file)
+{
+	struct saa7164_dev *dev;
+	struct saa7164_port *port;
+	struct saa7164_vbi_fh *fh;
+
+	port = (struct saa7164_port *)video_get_drvdata(video_devdata(file));
+	if (!port)
+		return -ENODEV;
+
+	dev = port->dev;
+
+	dprintk(DBGLVL_VBI, "%s()\n", __func__);
+
+	/* allocate + initialize per filehandle data */
+	fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+	if (NULL == fh)
+		return -ENOMEM;
+
+	fh->port = port;
+	v4l2_fh_init(&fh->fh, video_devdata(file));
+	v4l2_fh_add(&fh->fh);
+	file->private_data = fh;
+
+	return 0;
+}
+
+static int fops_release(struct file *file)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+	struct saa7164_dev *dev = port->dev;
+
+	dprintk(DBGLVL_VBI, "%s()\n", __func__);
+
+	/* Shut device down on last close */
+	if (atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) {
+		if (atomic_dec_return(&port->v4l_reader_count) == 0) {
+			/* stop vbi capture then cancel buffers */
+			saa7164_vbi_stop_streaming(port);
+		}
+	}
+
+	v4l2_fh_del(&fh->fh);
+	v4l2_fh_exit(&fh->fh);
+	kfree(fh);
+
+	return 0;
+}
+
+static struct
+saa7164_user_buffer *saa7164_vbi_next_buf(struct saa7164_port *port)
+{
+	struct saa7164_user_buffer *ubuf = NULL;
+	struct saa7164_dev *dev = port->dev;
+	u32 crc;
+
+	mutex_lock(&port->dmaqueue_lock);
+	if (!list_empty(&port->list_buf_used.list)) {
+		ubuf = list_first_entry(&port->list_buf_used.list,
+			struct saa7164_user_buffer, list);
+
+		if (crc_checking) {
+			crc = crc32(0, ubuf->data, ubuf->actual_size);
+			if (crc != ubuf->crc) {
+				printk(KERN_ERR "%s() ubuf %p crc became invalid, was 0x%x became 0x%x\n",
+					__func__,
+					ubuf, ubuf->crc, crc);
+			}
+		}
+
+	}
+	mutex_unlock(&port->dmaqueue_lock);
+
+	dprintk(DBGLVL_VBI, "%s() returns %p\n", __func__, ubuf);
+
+	return ubuf;
+}
+
+static ssize_t fops_read(struct file *file, char __user *buffer,
+	size_t count, loff_t *pos)
+{
+	struct saa7164_vbi_fh *fh = file->private_data;
+	struct saa7164_port *port = fh->port;
+	struct saa7164_user_buffer *ubuf = NULL;
+	struct saa7164_dev *dev = port->dev;
+	int ret = 0;
+	int rem, cnt;
+	u8 *p;
+
+	port->last_read_msecs_diff = port->last_read_msecs;
+	port->last_read_msecs = jiffies_to_msecs(jiffies);
+	port->last_read_msecs_diff = port->last_read_msecs -
+		port->last_read_msecs_diff;
+
+	saa7164_histogram_update(&port->read_interval,
+		port->last_read_msecs_diff);
+
+	if (*pos) {
+		printk(KERN_ERR "%s() ESPIPE\n", __func__);
+		return -ESPIPE;
+	}
+
+	if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) {
+		if (atomic_inc_return(&port->v4l_reader_count) == 1) {
+
+			if (saa7164_vbi_initialize(port) < 0) {
+				printk(KERN_ERR "%s() EINVAL\n", __func__);
+				return -EINVAL;
+			}
+
+			saa7164_vbi_start_streaming(port);
+			msleep(200);
+		}
+	}
+
+	/* blocking wait for buffer */
+	if ((file->f_flags & O_NONBLOCK) == 0) {
+		if (wait_event_interruptible(port->wait_read,
+			saa7164_vbi_next_buf(port))) {
+				printk(KERN_ERR "%s() ERESTARTSYS\n", __func__);
+				return -ERESTARTSYS;
+		}
+	}
+
+	/* Pull the first buffer from the used list */
+	ubuf = saa7164_vbi_next_buf(port);
+
+	while ((count > 0) && ubuf) {
+
+		/* set remaining bytes to copy */
+		rem = ubuf->actual_size - ubuf->pos;
+		cnt = rem > count ? count : rem;
+
+		p = ubuf->data + ubuf->pos;
+
+		dprintk(DBGLVL_VBI,
+			"%s() count=%d cnt=%d rem=%d buf=%p buf->pos=%d\n",
+			__func__, (int)count, cnt, rem, ubuf, ubuf->pos);
+
+		if (copy_to_user(buffer, p, cnt)) {
+			printk(KERN_ERR "%s() copy_to_user failed\n", __func__);
+			if (!ret) {
+				printk(KERN_ERR "%s() EFAULT\n", __func__);
+				ret = -EFAULT;
+			}
+			goto err;
+		}
+
+		ubuf->pos += cnt;
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+
+		if (ubuf->pos > ubuf->actual_size)
+			printk(KERN_ERR "read() pos > actual, huh?\n");
+
+		if (ubuf->pos == ubuf->actual_size) {
+
+			/* finished with current buffer, take next buffer */
+
+			/* Requeue the buffer on the free list */
+			ubuf->pos = 0;
+
+			mutex_lock(&port->dmaqueue_lock);
+			list_move_tail(&ubuf->list, &port->list_buf_free.list);
+			mutex_unlock(&port->dmaqueue_lock);
+
+			/* Dequeue next */
+			if ((file->f_flags & O_NONBLOCK) == 0) {
+				if (wait_event_interruptible(port->wait_read,
+					saa7164_vbi_next_buf(port))) {
+						break;
+				}
+			}
+			ubuf = saa7164_vbi_next_buf(port);
+		}
+	}
+err:
+	if (!ret && !ubuf) {
+		printk(KERN_ERR "%s() EAGAIN\n", __func__);
+		ret = -EAGAIN;
+	}
+
+	return ret;
+}
+
+static unsigned int fops_poll(struct file *file, poll_table *wait)
+{
+	struct saa7164_vbi_fh *fh = (struct saa7164_vbi_fh *)file->private_data;
+	struct saa7164_port *port = fh->port;
+	unsigned int mask = 0;
+
+	port->last_poll_msecs_diff = port->last_poll_msecs;
+	port->last_poll_msecs = jiffies_to_msecs(jiffies);
+	port->last_poll_msecs_diff = port->last_poll_msecs -
+		port->last_poll_msecs_diff;
+
+	saa7164_histogram_update(&port->poll_interval,
+		port->last_poll_msecs_diff);
+
+	if (!video_is_registered(port->v4l_device))
+		return -EIO;
+
+	if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) {
+		if (atomic_inc_return(&port->v4l_reader_count) == 1) {
+			if (saa7164_vbi_initialize(port) < 0)
+				return -EINVAL;
+			saa7164_vbi_start_streaming(port);
+			msleep(200);
+		}
+	}
+
+	/* blocking wait for buffer */
+	if ((file->f_flags & O_NONBLOCK) == 0) {
+		if (wait_event_interruptible(port->wait_read,
+			saa7164_vbi_next_buf(port))) {
+				return -ERESTARTSYS;
+		}
+	}
+
+	/* Pull the first buffer from the used list */
+	if (!list_empty(&port->list_buf_used.list))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+static const struct v4l2_file_operations vbi_fops = {
+	.owner		= THIS_MODULE,
+	.open		= fops_open,
+	.release	= fops_release,
+	.read		= fops_read,
+	.poll		= fops_poll,
+	.unlocked_ioctl	= video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops vbi_ioctl_ops = {
+	.vidioc_s_std		 = vidioc_s_std,
+	.vidioc_g_std		 = vidioc_g_std,
+	.vidioc_enum_input	 = saa7164_enum_input,
+	.vidioc_g_input		 = vidioc_g_input,
+	.vidioc_s_input		 = vidioc_s_input,
+	.vidioc_g_tuner		 = saa7164_g_tuner,
+	.vidioc_s_tuner		 = saa7164_s_tuner,
+	.vidioc_g_frequency	 = vidioc_g_frequency,
+	.vidioc_s_frequency	 = vidioc_s_frequency,
+	.vidioc_querycap	 = vidioc_querycap,
+	.vidioc_g_fmt_vbi_cap	 = saa7164_vbi_fmt,
+	.vidioc_try_fmt_vbi_cap	 = saa7164_vbi_fmt,
+	.vidioc_s_fmt_vbi_cap	 = saa7164_vbi_fmt,
+};
+
+static struct video_device saa7164_vbi_template = {
+	.name          = "saa7164",
+	.fops          = &vbi_fops,
+	.ioctl_ops     = &vbi_ioctl_ops,
+	.minor         = -1,
+	.tvnorms       = SAA7164_NORMS,
+};
+
+static struct video_device *saa7164_vbi_alloc(
+	struct saa7164_port *port,
+	struct pci_dev *pci,
+	struct video_device *template,
+	char *type)
+{
+	struct video_device *vfd;
+	struct saa7164_dev *dev = port->dev;
+
+	dprintk(DBGLVL_VBI, "%s()\n", __func__);
+
+	vfd = video_device_alloc();
+	if (NULL == vfd)
+		return NULL;
+
+	*vfd = *template;
+	snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", dev->name,
+		type, saa7164_boards[dev->board].name);
+
+	vfd->v4l2_dev  = &dev->v4l2_dev;
+	vfd->release = video_device_release;
+	return vfd;
+}
+
+int saa7164_vbi_register(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+	int result = -ENODEV;
+
+	dprintk(DBGLVL_VBI, "%s()\n", __func__);
+
+	if (port->type != SAA7164_MPEG_VBI)
+		BUG();
+
+	/* Sanity check that the PCI configuration space is active */
+	if (port->hwcfg.BARLocation == 0) {
+		printk(KERN_ERR "%s() failed "
+		       "(errno = %d), NO PCI configuration\n",
+			__func__, result);
+		result = -ENOMEM;
+		goto failed;
+	}
+
+	/* Establish VBI defaults here */
+
+	/* Allocate and register the video device node */
+	port->v4l_device = saa7164_vbi_alloc(port,
+		dev->pci, &saa7164_vbi_template, "vbi");
+
+	if (!port->v4l_device) {
+		printk(KERN_INFO "%s: can't allocate vbi device\n",
+			dev->name);
+		result = -ENOMEM;
+		goto failed;
+	}
+
+	port->enc_port = &dev->ports[port->nr - 2];
+	video_set_drvdata(port->v4l_device, port);
+	result = video_register_device(port->v4l_device,
+		VFL_TYPE_VBI, -1);
+	if (result < 0) {
+		printk(KERN_INFO "%s: can't register vbi device\n",
+			dev->name);
+		/* TODO: We're going to leak here if we don't dealloc
+		 The buffers above. The unreg function can't deal wit it.
+		*/
+		goto failed;
+	}
+
+	printk(KERN_INFO "%s: registered device vbi%d [vbi]\n",
+		dev->name, port->v4l_device->num);
+
+	/* Configure the hardware defaults */
+
+	result = 0;
+failed:
+	return result;
+}
+
+void saa7164_vbi_unregister(struct saa7164_port *port)
+{
+	struct saa7164_dev *dev = port->dev;
+
+	dprintk(DBGLVL_VBI, "%s(port=%d)\n", __func__, port->nr);
+
+	if (port->type != SAA7164_MPEG_VBI)
+		BUG();
+
+	if (port->v4l_device) {
+		if (port->v4l_device->minor != -1)
+			video_unregister_device(port->v4l_device);
+		else
+			video_device_release(port->v4l_device);
+
+		port->v4l_device = NULL;
+	}
+
+}
diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h
new file mode 100644
index 0000000..8337524
--- /dev/null
+++ b/drivers/media/pci/saa7164/saa7164.h
@@ -0,0 +1,641 @@
+/*
+ *  Driver for the NXP SAA7164 PCIe bridge
+ *
+ *  Copyright (c) 2010-2015 Steven Toth <stoth@kernellabs.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, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+	Driver architecture
+	*******************
+
+	saa7164_core.c/buffer.c/cards.c/i2c.c/dvb.c
+		|	: Standard Linux driver framework for creating
+		|	: exposing and managing interfaces to the rest
+		|	: of the kernel or userland. Also uses _fw.c to load
+		|	: firmware direct into the PCIe bus, bypassing layers.
+		V
+	saa7164_api..()	: Translate kernel specific functions/features
+		|	: into command buffers.
+		V
+	saa7164_cmd..()	: Manages the flow of command packets on/off,
+		|	: the bus. Deal with bus errors, timeouts etc.
+		V
+	saa7164_bus..() : Manage a read/write memory ring buffer in the
+		|	: PCIe Address space.
+		|
+		|		saa7164_fw...()	: Load any frimware
+		|			|	: direct into the device
+		V			V
+	<- ----------------- PCIe address space -------------------- ->
+*/
+
+#include <linux/pci.h>
+#include <linux/i2c.h>
+#include <linux/kdev_t.h>
+#include <linux/mutex.h>
+#include <linux/crc32.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+
+#include <media/tuner.h>
+#include <media/tveeprom.h>
+#include <dvb_demux.h>
+#include <dvb_frontend.h>
+#include <dvb_net.h>
+#include <dvbdev.h>
+#include <dmxdev.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+
+#include "saa7164-reg.h"
+#include "saa7164-types.h"
+
+#define SAA7164_MAXBOARDS 8
+
+#define UNSET (-1U)
+#define SAA7164_BOARD_NOAUTO			UNSET
+#define SAA7164_BOARD_UNKNOWN			0
+#define SAA7164_BOARD_UNKNOWN_REV2		1
+#define SAA7164_BOARD_UNKNOWN_REV3		2
+#define SAA7164_BOARD_HAUPPAUGE_HVR2250		3
+#define SAA7164_BOARD_HAUPPAUGE_HVR2200		4
+#define SAA7164_BOARD_HAUPPAUGE_HVR2200_2	5
+#define SAA7164_BOARD_HAUPPAUGE_HVR2200_3	6
+#define SAA7164_BOARD_HAUPPAUGE_HVR2250_2	7
+#define SAA7164_BOARD_HAUPPAUGE_HVR2250_3	8
+#define SAA7164_BOARD_HAUPPAUGE_HVR2200_4	9
+#define SAA7164_BOARD_HAUPPAUGE_HVR2200_5	10
+#define SAA7164_BOARD_HAUPPAUGE_HVR2255proto	11
+#define SAA7164_BOARD_HAUPPAUGE_HVR2255		12
+#define SAA7164_BOARD_HAUPPAUGE_HVR2205		13
+
+#define SAA7164_MAX_UNITS		8
+#define SAA7164_TS_NUMBER_OF_LINES	312
+#define SAA7164_PS_NUMBER_OF_LINES	256
+#define SAA7164_PT_ENTRIES		16 /* (312 * 188) / 4096 */
+#define SAA7164_MAX_ENCODER_BUFFERS	64 /* max 5secs of latency at 6Mbps */
+#define SAA7164_MAX_VBI_BUFFERS		64
+
+/* Port related defines */
+#define SAA7164_PORT_TS1	(0)
+#define SAA7164_PORT_TS2	(SAA7164_PORT_TS1 + 1)
+#define SAA7164_PORT_ENC1	(SAA7164_PORT_TS2 + 1)
+#define SAA7164_PORT_ENC2	(SAA7164_PORT_ENC1 + 1)
+#define SAA7164_PORT_VBI1	(SAA7164_PORT_ENC2 + 1)
+#define SAA7164_PORT_VBI2	(SAA7164_PORT_VBI1 + 1)
+#define SAA7164_MAX_PORTS	(SAA7164_PORT_VBI2 + 1)
+
+#define DBGLVL_FW    4
+#define DBGLVL_DVB   8
+#define DBGLVL_I2C  16
+#define DBGLVL_API  32
+#define DBGLVL_CMD  64
+#define DBGLVL_BUS 128
+#define DBGLVL_IRQ 256
+#define DBGLVL_BUF 512
+#define DBGLVL_ENC 1024
+#define DBGLVL_VBI 2048
+#define DBGLVL_THR 4096
+#define DBGLVL_CPU 8192
+
+#define SAA7164_NORMS \
+	(V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP)
+
+/* TV frequency range copied from tuner-core.c */
+#define SAA7164_TV_MIN_FREQ (44U * 16U)
+#define SAA7164_TV_MAX_FREQ (958U * 16U)
+
+enum port_t {
+	SAA7164_MPEG_UNDEFINED = 0,
+	SAA7164_MPEG_DVB,
+	SAA7164_MPEG_ENCODER,
+	SAA7164_MPEG_VBI,
+};
+
+enum saa7164_i2c_bus_nr {
+	SAA7164_I2C_BUS_0 = 0,
+	SAA7164_I2C_BUS_1,
+	SAA7164_I2C_BUS_2,
+};
+
+enum saa7164_buffer_flags {
+	SAA7164_BUFFER_UNDEFINED = 0,
+	SAA7164_BUFFER_FREE,
+	SAA7164_BUFFER_BUSY,
+	SAA7164_BUFFER_FULL
+};
+
+enum saa7164_unit_type {
+	SAA7164_UNIT_UNDEFINED = 0,
+	SAA7164_UNIT_DIGITAL_DEMODULATOR,
+	SAA7164_UNIT_ANALOG_DEMODULATOR,
+	SAA7164_UNIT_TUNER,
+	SAA7164_UNIT_EEPROM,
+	SAA7164_UNIT_ZILOG_IRBLASTER,
+	SAA7164_UNIT_ENCODER,
+};
+
+/* The PCIe bridge doesn't grant direct access to i2c.
+ * Instead, you address i2c devices using a uniqely
+ * allocated 'unitid' value via a messaging API. This
+ * is a problem. The kernel and existing demod/tuner
+ * drivers expect to talk 'i2c', so we have to maintain
+ * a translation layer, and a series of functions to
+ * convert i2c bus + device address into a unit id.
+ */
+struct saa7164_unit {
+	enum saa7164_unit_type type;
+	u8	id;
+	char	*name;
+	enum saa7164_i2c_bus_nr i2c_bus_nr;
+	u8	i2c_bus_addr;
+	u8	i2c_reg_len;
+};
+
+struct saa7164_board {
+	char	*name;
+	enum port_t porta, portb, portc,
+		portd, porte, portf;
+	enum {
+		SAA7164_CHIP_UNDEFINED = 0,
+		SAA7164_CHIP_REV2,
+		SAA7164_CHIP_REV3,
+	} chiprev;
+	struct	saa7164_unit unit[SAA7164_MAX_UNITS];
+};
+
+struct saa7164_subid {
+	u16     subvendor;
+	u16     subdevice;
+	u32     card;
+};
+
+struct saa7164_encoder_fh {
+	struct v4l2_fh fh;
+	struct saa7164_port *port;
+	atomic_t v4l_reading;
+};
+
+struct saa7164_vbi_fh {
+	struct v4l2_fh fh;
+	struct saa7164_port *port;
+	atomic_t v4l_reading;
+};
+
+struct saa7164_histogram_bucket {
+	u32 val;
+	u32 count;
+	u64 update_time;
+};
+
+struct saa7164_histogram {
+	char name[32];
+	struct saa7164_histogram_bucket counter1[64];
+};
+
+struct saa7164_user_buffer {
+	struct list_head list;
+
+	/* Attributes */
+	u8  *data;
+	u32 pos;
+	u32 actual_size;
+
+	u32 crc;
+};
+
+struct saa7164_fw_status {
+
+	/* RISC Core details */
+	u32	status;
+	u32	mode;
+	u32	spec;
+	u32	inst;
+	u32	cpuload;
+	u32	remainheap;
+
+	/* Firmware version */
+	u32	version;
+	u32	major;
+	u32	sub;
+	u32	rel;
+	u32	buildnr;
+};
+
+struct saa7164_dvb {
+	struct mutex lock;
+	struct dvb_adapter adapter;
+	struct dvb_frontend *frontend;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	struct dmx_frontend fe_hw;
+	struct dmx_frontend fe_mem;
+	struct dvb_net net;
+	int feeding;
+};
+
+struct saa7164_i2c {
+	struct saa7164_dev		*dev;
+
+	enum saa7164_i2c_bus_nr		nr;
+
+	/* I2C I/O */
+	struct i2c_adapter		i2c_adap;
+	struct i2c_client		i2c_client;
+	u32				i2c_rc;
+};
+
+struct saa7164_ctrl {
+	struct v4l2_queryctrl v;
+};
+
+struct saa7164_tvnorm {
+	char		*name;
+	v4l2_std_id	id;
+};
+
+struct saa7164_encoder_params {
+	struct saa7164_tvnorm encodernorm;
+	u32 height;
+	u32 width;
+	u32 is_50hz;
+	u32 bitrate; /* bps */
+	u32 bitrate_peak; /* bps */
+	u32 bitrate_mode;
+	u32 stream_type; /* V4L2_MPEG_STREAM_TYPE_MPEG2_TS */
+
+	u32 audio_sampling_freq;
+	u32 ctl_mute;
+	u32 ctl_aspect;
+	u32 refdist;
+	u32 gop_size;
+};
+
+struct saa7164_vbi_params {
+	struct saa7164_tvnorm encodernorm;
+	u32 height;
+	u32 width;
+	u32 is_50hz;
+	u32 bitrate; /* bps */
+	u32 bitrate_peak; /* bps */
+	u32 bitrate_mode;
+	u32 stream_type; /* V4L2_MPEG_STREAM_TYPE_MPEG2_TS */
+
+	u32 audio_sampling_freq;
+	u32 ctl_mute;
+	u32 ctl_aspect;
+	u32 refdist;
+	u32 gop_size;
+};
+
+struct saa7164_port;
+
+struct saa7164_buffer {
+	struct list_head list;
+
+	/* Note of which h/w buffer list index position we occupy */
+	int idx;
+
+	struct saa7164_port *port;
+
+	/* Hardware Specific */
+	/* PCI Memory allocations */
+	enum saa7164_buffer_flags flags; /* Free, Busy, Full */
+
+	/* A block of page align PCI memory */
+	u32 pci_size;	/* PCI allocation size in bytes */
+	u64 *cpu;	/* Virtual address */
+	dma_addr_t dma;	/* Physical address */
+	u32 crc;	/* Checksum for the entire buffer data */
+
+	/* A page table that splits the block into a number of entries */
+	u32 pt_size;		/* PCI allocation size in bytes */
+	u64 *pt_cpu;		/* Virtual address */
+	dma_addr_t pt_dma;	/* Physical address */
+
+	/* Encoder fops */
+	u32 pos;
+	u32 actual_size;
+};
+
+struct saa7164_port {
+
+	struct saa7164_dev *dev;
+	enum port_t type;
+	int nr;
+
+	/* --- Generic port attributes --- */
+
+	/* HW stream parameters */
+	struct tmHWStreamParameters hw_streamingparams;
+
+	/* DMA configuration values, is seeded during initialization */
+	struct tmComResDMATermDescrHeader hwcfg;
+
+	/* hardware specific registers */
+	u32 bufcounter;
+	u32 pitch;
+	u32 bufsize;
+	u32 bufoffset;
+	u32 bufptr32l;
+	u32 bufptr32h;
+	u64 bufptr64;
+
+	u32 numpte;	/* Number of entries in array, only valid in head */
+
+	struct mutex dmaqueue_lock;
+	struct saa7164_buffer dmaqueue;
+
+	u64 last_irq_msecs, last_svc_msecs;
+	u64 last_irq_msecs_diff, last_svc_msecs_diff;
+	u32 last_svc_wp;
+	u32 last_svc_rp;
+	u64 last_irq_svc_msecs_diff;
+	u64 last_read_msecs, last_read_msecs_diff;
+	u64 last_poll_msecs, last_poll_msecs_diff;
+
+	struct saa7164_histogram irq_interval;
+	struct saa7164_histogram svc_interval;
+	struct saa7164_histogram irq_svc_interval;
+	struct saa7164_histogram read_interval;
+	struct saa7164_histogram poll_interval;
+
+	/* --- DVB Transport Specific --- */
+	struct saa7164_dvb dvb;
+	struct i2c_client *i2c_client_demod;
+	struct i2c_client *i2c_client_tuner;
+
+	/* --- Encoder/V4L related attributes --- */
+	/* Encoder */
+	/* Defaults established in saa7164-encoder.c */
+	struct saa7164_tvnorm encodernorm;
+	struct v4l2_ctrl_handler ctrl_handler;
+	v4l2_std_id std;
+	u32 height;
+	u32 width;
+	u32 freq;
+	u8 mux_input;
+	u8 encoder_profile;
+	u8 video_format;
+	u8 audio_format;
+	u8 video_resolution;
+	u16 ctl_brightness;
+	u16 ctl_contrast;
+	u16 ctl_hue;
+	u16 ctl_saturation;
+	u16 ctl_sharpness;
+	s8 ctl_volume;
+
+	struct tmComResAFeatureDescrHeader audfeat;
+	struct tmComResEncoderDescrHeader encunit;
+	struct tmComResProcDescrHeader vidproc;
+	struct tmComResExtDevDescrHeader ifunit;
+	struct tmComResTunerDescrHeader tunerunit;
+
+	struct work_struct workenc;
+
+	/* V4L Encoder Video */
+	struct saa7164_encoder_params encoder_params;
+	struct video_device *v4l_device;
+	atomic_t v4l_reader_count;
+
+	struct saa7164_buffer list_buf_used;
+	struct saa7164_buffer list_buf_free;
+	wait_queue_head_t wait_read;
+
+	/* V4L VBI */
+	struct tmComResVBIFormatDescrHeader vbi_fmt_ntsc;
+	struct saa7164_vbi_params vbi_params;
+	struct saa7164_port *enc_port;
+
+	/* Debug */
+	u32 sync_errors;
+	u32 v_cc_errors;
+	u32 a_cc_errors;
+	u8 last_v_cc;
+	u8 last_a_cc;
+	u32 done_first_interrupt;
+};
+
+struct saa7164_dev {
+	struct list_head	devlist;
+	atomic_t		refcount;
+
+	struct v4l2_device v4l2_dev;
+
+	/* pci stuff */
+	struct pci_dev	*pci;
+	unsigned char	pci_rev, pci_lat;
+	int		pci_bus, pci_slot;
+	u32		__iomem *lmmio;
+	u8		__iomem *bmmio;
+	u32		__iomem *lmmio2;
+	u8		__iomem *bmmio2;
+	int		pci_irqmask;
+
+	/* board details */
+	int	nr;
+	int	hwrevision;
+	u32	board;
+	char	name[16];
+
+	/* firmware status */
+	struct saa7164_fw_status	fw_status;
+	u32				firmwareloaded;
+
+	struct tmComResHWDescr		hwdesc;
+	struct tmComResInterfaceDescr	intfdesc;
+	struct tmComResBusDescr		busdesc;
+
+	struct tmComResBusInfo		bus;
+
+	/* Interrupt status and ack registers */
+	u32 int_status;
+	u32 int_ack;
+	bool msi;
+
+	struct cmd			cmds[SAA_CMD_MAX_MSG_UNITS];
+	struct mutex			lock;
+
+	/* I2c related */
+	struct saa7164_i2c i2c_bus[3];
+
+	/* Transport related */
+	struct saa7164_port ports[SAA7164_MAX_PORTS];
+
+	/* Deferred command/api interrupts handling */
+	struct work_struct workcmd;
+
+	/* A kernel thread to monitor the firmware log, used
+	 * only in debug mode.
+	 */
+	struct task_struct *kthread;
+
+};
+
+extern struct list_head saa7164_devlist;
+extern unsigned int waitsecs;
+extern unsigned int encoder_buffers;
+extern unsigned int vbi_buffers;
+
+/* ----------------------------------------------------------- */
+/* saa7164-core.c                                              */
+void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr);
+void saa7164_getfirmwarestatus(struct saa7164_dev *dev);
+u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev);
+void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val);
+
+/* ----------------------------------------------------------- */
+/* saa7164-fw.c                                                */
+int saa7164_downloadfirmware(struct saa7164_dev *dev);
+
+/* ----------------------------------------------------------- */
+/* saa7164-i2c.c                                               */
+extern int saa7164_i2c_register(struct saa7164_i2c *bus);
+extern int saa7164_i2c_unregister(struct saa7164_i2c *bus);
+extern void saa7164_call_i2c_clients(struct saa7164_i2c *bus,
+	unsigned int cmd, void *arg);
+
+/* ----------------------------------------------------------- */
+/* saa7164-bus.c                                               */
+int saa7164_bus_setup(struct saa7164_dev *dev);
+void saa7164_bus_dump(struct saa7164_dev *dev);
+int saa7164_bus_set(struct saa7164_dev *dev, struct tmComResInfo* msg,
+	void *buf);
+int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg,
+	void *buf, int peekonly);
+
+/* ----------------------------------------------------------- */
+/* saa7164-cmd.c                                               */
+int saa7164_cmd_send(struct saa7164_dev *dev,
+	u8 id, enum tmComResCmd command, u16 controlselector,
+	u16 size, void *buf);
+void saa7164_cmd_signal(struct saa7164_dev *dev, u8 seqno);
+int saa7164_irq_dequeue(struct saa7164_dev *dev);
+
+/* ----------------------------------------------------------- */
+/* saa7164-api.c                                               */
+int saa7164_api_get_fw_version(struct saa7164_dev *dev, u32 *version);
+int saa7164_api_enum_subdevs(struct saa7164_dev *dev);
+int saa7164_api_i2c_read(struct saa7164_i2c *bus, u8 addr, u32 reglen, u8 *reg,
+	u32 datalen, u8 *data);
+int saa7164_api_i2c_write(struct saa7164_i2c *bus, u8 addr,
+	u32 datalen, u8 *data);
+int saa7164_api_dif_write(struct saa7164_i2c *bus, u8 addr,
+	u32 datalen, u8 *data);
+int saa7164_api_read_eeprom(struct saa7164_dev *dev, u8 *buf, int buflen);
+int saa7164_api_set_gpiobit(struct saa7164_dev *dev, u8 unitid, u8 pin);
+int saa7164_api_clear_gpiobit(struct saa7164_dev *dev, u8 unitid, u8 pin);
+int saa7164_api_transition_port(struct saa7164_port *port, u8 mode);
+int saa7164_api_initialize_dif(struct saa7164_port *port);
+int saa7164_api_configure_dif(struct saa7164_port *port, u32 std);
+int saa7164_api_set_encoder(struct saa7164_port *port);
+int saa7164_api_get_encoder(struct saa7164_port *port);
+int saa7164_api_set_aspect_ratio(struct saa7164_port *port);
+int saa7164_api_set_usercontrol(struct saa7164_port *port, u8 ctl);
+int saa7164_api_get_usercontrol(struct saa7164_port *port, u8 ctl);
+int saa7164_api_set_videomux(struct saa7164_port *port);
+int saa7164_api_audio_mute(struct saa7164_port *port, int mute);
+int saa7164_api_set_audio_volume(struct saa7164_port *port, s8 level);
+int saa7164_api_set_audio_std(struct saa7164_port *port);
+int saa7164_api_set_audio_detection(struct saa7164_port *port, int autodetect);
+int saa7164_api_get_videomux(struct saa7164_port *port);
+int saa7164_api_set_vbi_format(struct saa7164_port *port);
+int saa7164_api_set_debug(struct saa7164_dev *dev, u8 level);
+int saa7164_api_collect_debug(struct saa7164_dev *dev);
+int saa7164_api_get_load_info(struct saa7164_dev *dev,
+	struct tmFwInfoStruct *i);
+
+/* ----------------------------------------------------------- */
+/* saa7164-cards.c                                             */
+extern struct saa7164_board saa7164_boards[];
+extern const unsigned int saa7164_bcount;
+
+extern struct saa7164_subid saa7164_subids[];
+extern const unsigned int saa7164_idcount;
+
+extern void saa7164_card_list(struct saa7164_dev *dev);
+extern void saa7164_gpio_setup(struct saa7164_dev *dev);
+extern void saa7164_card_setup(struct saa7164_dev *dev);
+
+extern int saa7164_i2caddr_to_reglen(struct saa7164_i2c *bus, int addr);
+extern int saa7164_i2caddr_to_unitid(struct saa7164_i2c *bus, int addr);
+extern char *saa7164_unitid_name(struct saa7164_dev *dev, u8 unitid);
+
+/* ----------------------------------------------------------- */
+/* saa7164-dvb.c                                               */
+extern int saa7164_dvb_register(struct saa7164_port *port);
+extern int saa7164_dvb_unregister(struct saa7164_port *port);
+
+/* ----------------------------------------------------------- */
+/* saa7164-buffer.c                                            */
+extern struct saa7164_buffer *saa7164_buffer_alloc(
+	struct saa7164_port *port, u32 len);
+extern int saa7164_buffer_dealloc(struct saa7164_buffer *buf);
+extern void saa7164_buffer_display(struct saa7164_buffer *buf);
+extern int saa7164_buffer_activate(struct saa7164_buffer *buf, int i);
+extern int saa7164_buffer_cfg_port(struct saa7164_port *port);
+extern struct saa7164_user_buffer *saa7164_buffer_alloc_user(
+	struct saa7164_dev *dev, u32 len);
+extern void saa7164_buffer_dealloc_user(struct saa7164_user_buffer *buf);
+extern int saa7164_buffer_zero_offsets(struct saa7164_port *port, int i);
+
+/* ----------------------------------------------------------- */
+/* saa7164-encoder.c                                            */
+int saa7164_s_std(struct saa7164_port *port, v4l2_std_id id);
+int saa7164_g_std(struct saa7164_port *port, v4l2_std_id *id);
+int saa7164_enum_input(struct file *file, void *priv, struct v4l2_input *i);
+int saa7164_g_input(struct saa7164_port *port, unsigned int *i);
+int saa7164_s_input(struct saa7164_port *port, unsigned int i);
+int saa7164_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t);
+int saa7164_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t);
+int saa7164_g_frequency(struct saa7164_port *port, struct v4l2_frequency *f);
+int saa7164_s_frequency(struct saa7164_port *port,
+			const struct v4l2_frequency *f);
+int saa7164_encoder_register(struct saa7164_port *port);
+void saa7164_encoder_unregister(struct saa7164_port *port);
+
+/* ----------------------------------------------------------- */
+/* saa7164-vbi.c                                            */
+int saa7164_vbi_register(struct saa7164_port *port);
+void saa7164_vbi_unregister(struct saa7164_port *port);
+
+/* ----------------------------------------------------------- */
+
+extern unsigned int crc_checking;
+
+extern unsigned int saa_debug;
+#define dprintk(level, fmt, arg...)\
+	do { if (saa_debug & level)\
+		printk(KERN_DEBUG "%s: " fmt, dev->name, ## arg);\
+	} while (0)
+
+#define log_warn(fmt, arg...)\
+	do { \
+		printk(KERN_WARNING "%s: " fmt, dev->name, ## arg);\
+	} while (0)
+
+#define saa7164_readl(reg) readl(dev->lmmio + ((reg) >> 2))
+#define saa7164_writel(reg, value) writel((value), dev->lmmio + ((reg) >> 2))
+
+#define saa7164_readb(reg)             readl(dev->bmmio + (reg))
+#define saa7164_writeb(reg, value)     writel((value), dev->bmmio + (reg))
+