[PATCH] Add support for FusionHDTV DVB-T NANO 2 / Dual Digital 4

Firmware required:

Mirror 1: http://konstantin.filtschew.de/v4l-firmware/

Mirror 2: http://www.tuxamito.com.es/em2880/

This patch is for users, and NOT meant to be merged into the kernel.

For AU support, apply Rogers patch afterwards:
http://linuxtv.org/~mkrufky/pending/xc/dd4.au.patch

From: Michael Krufky <mkrufky@linuxtv.org>
---
 linux/drivers/media/dvb/dvb-usb/cxusb.c       |  157 +++++++
 linux/drivers/media/dvb/dvb-usb/cxusb.h       |    2 
 linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h |    2 
 linux/drivers/media/dvb/frontends/Kconfig     |    7 
 linux/drivers/media/dvb/frontends/Makefile    |    1 
 linux/drivers/media/dvb/frontends/xc3028-fe.c |  532 ++++++++++++++++++++++++++
 linux/drivers/media/dvb/frontends/xc3028.h    |   56 ++
 v4l/versions.txt                              |    1 
 8 files changed, 757 insertions(+), 1 deletion(-)

--- v4l-dvb.orig/linux/drivers/media/dvb/dvb-usb/cxusb.c
+++ v4l-dvb/linux/drivers/media/dvb/dvb-usb/cxusb.c
@@ -30,6 +30,7 @@
 #include "mt352.h"
 #include "mt352_priv.h"
 #include "zl10353.h"
+#include "xc3028.h"
 
 /* debug */
 int dvb_usb_cxusb_debug;
@@ -72,6 +73,28 @@
 	st->gpio_write_state[GPIO_TUNER] = onoff;
 }
 
+static void cxusb_bluebird_gpio(struct dvb_usb_device *d, u8 pin)
+{
+	u8 o[2],i;
+
+	o[0] = 0xff & ~pin;
+	o[1] = 0x00;
+
+	cxusb_ctrl_msg(d, CMD_BLUEBIRD_GPIO_WRITE, o, 2, &i, 1);
+	msleep(140);
+
+	if ((i & pin) != 0x00)
+		deb_info("gpio_write failed.\n");
+
+	o[1] = pin;
+
+	cxusb_ctrl_msg(d, CMD_BLUEBIRD_GPIO_WRITE, o, 2, &i, 1);
+	msleep(140);
+
+	if ((i & pin) != pin)
+		deb_info("gpio_write failed.\n");
+}
+
 /* I2C */
 static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
 			  int num)
@@ -351,6 +374,64 @@
 	.demod_init    = cxusb_mt352_demod_init,
 };
 
+static struct zl10353_config cxusb_zl10353_dualdig4_config = {
+	.demod_address = 0x0f,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+struct bcode {
+	int reg;
+	char *txt;
+	int len;
+	int delay;
+};
+
+static int cxusb_xc3028_zl353_gpio_reset(struct dvb_frontend* fe, int ptr)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dvb_usb_device *d = adap->dev;
+	int j;
+	struct bcode zlconf[] = {
+		/* borrowed from em2880-dvb
+		 * this should be fixed in zl10353.c instead */
+		{0x1e,"\x60\x00",2,0},
+		{0x1e,"\x61\x4d",2,0},
+
+		{0x1e,"\x50\x0b",2,0},
+		{0x1e,"\x51\x44",2,0},
+		{0x1e,"\x52\x46",2,0},
+		{0x1e,"\x53\x15",2,0},
+		{0x1e,"\x54\x0f",2,0},
+		{0x1e,"\x5e\x00",2,0},
+		{0x1e,"\x5f\x12",2,0},
+
+
+		{0x1e,"\x55\x80",2,0}, /* reset */
+		{0x1e,"\xea\x01",2,0},
+		{0x1e,"\xea\x00",2,0},
+		{0x1e,"\x5a\xcd",2,0},
+
+
+		{0x1e,"\x6c\xe6",2,0}, // set input frequency
+		{0x1e,"\x6d\x09",2,0},
+		{}
+	};
+
+	if (ptr == 0 || ptr == 1)
+		/* pulse the GPIO tuner reset pin  */
+		cxusb_bluebird_gpio(d,0x01);
+	else if (ptr == 2)
+		for(j = 0; zlconf[j].txt; j++)
+			d->adapter[0].fe->ops.write(d->adapter[0].fe, zlconf[j].txt,zlconf[j].len);
+
+	return 0;
+}
+
+static struct xc3028_config cxusb_xc3028_config = {
+	.gpio_reset = cxusb_xc3028_zl353_gpio_reset,
+};
+
 /* Callbacks for DVB USB */
 static int cxusb_fmd1216me_tuner_attach(struct dvb_usb_adapter *adap)
 {
@@ -386,6 +467,12 @@
 	return 0;
 }
 
+static int cxusb_xc3028_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(xc3028_attach,adap->fe, &adap->dev->i2c_adap, &cxusb_xc3028_config);
+	return 0;
+}
+
 static int cxusb_cx22702_frontend_attach(struct dvb_usb_adapter *adap)
 {
 	u8 b;
@@ -401,6 +488,24 @@
 	return -EIO;
 }
 
+static int cxusb_dualdig4_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	if (usb_set_interface(adap->dev->udev, 0, 1) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0);
+
+	/* pulse the GPIO demod reset pin  */
+	cxusb_bluebird_gpio(adap->dev,0x02);
+
+	if ((adap->fe = dvb_attach(zl10353_attach,
+				   &cxusb_zl10353_dualdig4_config,
+				   &adap->dev->i2c_adap)) != NULL)
+		return 0;
+
+	return -EIO;
+}
+
 static int cxusb_lgdt3303_frontend_attach(struct dvb_usb_adapter *adap)
 {
 	if (usb_set_interface(adap->dev->udev, 0, 7) < 0)
@@ -479,6 +584,7 @@
 static struct dvb_usb_device_properties cxusb_bluebird_dee1601_properties;
 static struct dvb_usb_device_properties cxusb_bluebird_lgz201_properties;
 static struct dvb_usb_device_properties cxusb_bluebird_dtt7579_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_dualdig4_properties;
 
 static int cxusb_probe(struct usb_interface *intf,
 		       const struct usb_device_id *id)
@@ -487,7 +593,8 @@
 		dvb_usb_device_init(intf,&cxusb_bluebird_lgh064f_properties,THIS_MODULE,NULL) == 0 ||
 		dvb_usb_device_init(intf,&cxusb_bluebird_dee1601_properties,THIS_MODULE,NULL) == 0 ||
 		dvb_usb_device_init(intf,&cxusb_bluebird_lgz201_properties,THIS_MODULE,NULL) == 0 ||
-		dvb_usb_device_init(intf,&cxusb_bluebird_dtt7579_properties,THIS_MODULE,NULL) == 0) {
+		dvb_usb_device_init(intf,&cxusb_bluebird_dtt7579_properties,THIS_MODULE,NULL) == 0 ||
+		dvb_usb_device_init(intf,&cxusb_bluebird_dualdig4_properties,THIS_MODULE,NULL) == 0) {
 		return 0;
 	}
 
@@ -508,6 +615,8 @@
 	{ USB_DEVICE(USB_VID_DVICO, USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_WARM) },
 	{ USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_2_COLD) },
 	{ USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_2_WARM) },
+	{ USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_4) },
+	{ USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DVBT_NANO_2) },
 	{}		/* Terminating entry */
 };
 MODULE_DEVICE_TABLE (usb, cxusb_table);
@@ -766,6 +875,52 @@
 	}
 };
 
+static struct dvb_usb_device_properties cxusb_bluebird_dualdig4_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl          = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_dualdig4_frontend_attach,
+			.tuner_attach     = cxusb_xc3028_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		},
+	},
+
+	.power_ctrl       = cxusb_bluebird_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 2,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T Dual Digital 4",
+			{ NULL },
+			{ &cxusb_table[13], NULL },
+		},
+		{   "DViCO FusionHDTV DVB-T NANO2",
+			{ NULL },
+			{ &cxusb_table[14], NULL },
+		},
+	}
+};
+
 static struct usb_driver cxusb_driver = {
 #if LINUX_VERSION_CODE <=  KERNEL_VERSION(2,6,15)
 	.owner		= THIS_MODULE,
--- v4l-dvb.orig/linux/drivers/media/dvb/dvb-usb/cxusb.h
+++ v4l-dvb/linux/drivers/media/dvb/dvb-usb/cxusb.h
@@ -28,6 +28,8 @@
 #define CMD_ANALOG        0x50
 #define CMD_DIGITAL       0x51
 
+#define CMD_BLUEBIRD_GPIO_WRITE 0x05
+
 struct cxusb_state {
 	u8 gpio_write_state[3];
 };
--- v4l-dvb.orig/linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h
+++ v4l-dvb/linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h
@@ -142,6 +142,8 @@
 #define USB_PID_DVICO_BLUEBIRD_DUAL_1_WARM		0xdb51
 #define USB_PID_DVICO_BLUEBIRD_DUAL_2_COLD		0xdb58
 #define USB_PID_DVICO_BLUEBIRD_DUAL_2_WARM		0xdb59
+#define USB_PID_DVICO_BLUEBIRD_DUAL_4			0xdb78
+#define USB_PID_DVICO_BLUEBIRD_DVBT_NANO_2		0xdb70
 #define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_COLD		0xdb54
 #define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_WARM		0xdb55
 #define USB_PID_MEDION_MD95700				0x0932
--- v4l-dvb.orig/linux/drivers/media/dvb/frontends/Kconfig
+++ v4l-dvb/linux/drivers/media/dvb/frontends/Kconfig
@@ -353,6 +353,13 @@
 	  This device is only used inside a SiP called togther with a
 	  demodulator for now.
 
+config DVB_XC3028
+	tristate "Xceive XC3028 silicon tuner"
+	depends on I2C
+	default m if DVB_FE_CUSTOMISE
+	help
+	  A driver for the silicon tuner XC3028 from Xceive.
+
 comment "Miscellaneous devices"
 	depends on DVB_CORE
 
--- v4l-dvb.orig/linux/drivers/media/dvb/frontends/Makefile
+++ v4l-dvb/linux/drivers/media/dvb/frontends/Makefile
@@ -4,6 +4,7 @@
 
 EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core/
 
+obj-$(CONFIG_DVB_XC3028) += xc3028-fe.o
 obj-$(CONFIG_DVB_PLL) += dvb-pll.o
 obj-$(CONFIG_DVB_STV0299) += stv0299.o
 obj-$(CONFIG_DVB_SP8870) += sp8870.o
--- /dev/null
+++ v4l-dvb/linux/drivers/media/dvb/frontends/xc3028-fe.c
@@ -0,0 +1,532 @@
+/*
+
+   Xceive -  xc3028 tuner interface (Firmware 2.7)
+
+   Copyright (c) 2007 Michael Krufky <mkrufky@linuxtv.org>
+   Copyright (c) 2006 Markus Rechberger <mrechberger@gmail.com>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; 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/i2c.h>
+#include "compat.h"
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <media/tuner.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+#include "i2c-compat.h"
+#endif
+#include "dvb_frontend.h"
+#include "xc3028.h"
+
+
+struct xc3028_priv {
+	u8 tuning_code[12];
+	enum v4l2_tuner_type type;
+	v4l2_std_id std;
+	//	unsigned int mode;
+
+	struct xc3028_config *cfg;
+
+	struct i2c_adapter *i2c_adap;
+	u32 frequency;
+	int bandwidth;
+};
+
+MODULE_DESCRIPTION("Xceive xc3028 dvb frontend driver");
+MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_LICENSE("GPL");
+
+
+/* hack */
+#define V4L2_TUNER_DVBT_TV 19
+#define V4L2_TUNER_DVBC_TV 29
+#define V4L2_TUNER_ATSC_TV 39
+
+/* firmware functions */
+#define XC3028_BYTECODE 0
+#define XC3028_RESET 1
+#define XC3028_SLEEP 2
+
+#define XC3028_BW8MHZ 0
+#define XC3028_BW7MHZ 1
+#define XC3028_BW6MHZ 2
+#define XC3028_ATSC_BW6MHZ 3
+#define XC3028_RADIO  4
+
+
+#define XC3028_ANALOGUE_FW "xc3028_init0.i2c.fw"
+#define XC3028_DIGITAL_FW  "xc3028_8MHz_init0.i2c.fw"
+#define XC3028_RADIO_FW    "xc3028_FM_init0.i2c.fw"
+#define XC3028_SPECIFIC_RADIO_FW "xc3028_specific_radio.fw"
+
+#define TUNING_GRANULARITY      15625
+
+static int firmware_loader(struct dvb_frontend *fe, const struct firmware *fw);
+static int upload_firmware(struct dvb_frontend *fe);
+static int xc3028_set_mode(struct dvb_frontend *fe, enum v4l2_tuner_type type);
+
+static struct _analogue_standards{
+	v4l2_std_id standard;
+	u8 filename[50];
+} xc3028_standards[]={
+	{V4L2_STD_PAL_BG,"xc3028_BG_PAL_A2_A.i2c.fw"},
+	{V4L2_STD_PAL_I,"xc3028_I_PAL_NICAM.i2c.fw"},
+	{V4L2_STD_PAL_DK,"xc3028_DK_PAL_A2.i2c.fw"},
+	{V4L2_STD_MN,"xc3028_MN_NTSCPAL_A2.i2c.fw"},
+	{V4L2_STD_PAL,  "xc3028_BG_PAL_A2_A.i2c.fw"},
+
+	{V4L2_STD_SECAM_DK,"xc3028_DK_SECAM_A2_DK1.i2c.fw"},
+	{V4L2_STD_SECAM_L,"xc3028_L_SECAM_NICAM.i2c.fw"},
+	{V4L2_STD_SECAM_LC,"xc3028_L'_SECAM_NICAM.i2c.fw"},
+	{V4L2_STD_SECAM_K1,"xc3028_DK_SECAM_A2_DK1.i2c.fw"},
+};
+
+/* TODO: add the other standards here
+   The linux DVB framework sends us following values
+   from 0..2 to set up the correct channel bandwidth
+
+   0 ... 8 mhz
+   1 ... 7 mhz
+   2 ... 6 mhz
+
+   we do not have any settings for 6MHz at least I haven't found one,
+   feel free to complete this list
+
+ */
+
+static struct _digital_standards{
+	unsigned int dvb:1;
+	unsigned int atsc:1;
+	int bandwidth;
+	char filename[50];
+} xc3028_dtv_standards[]={
+	{1, 0, XC3028_BW8MHZ /* 8mhz  */, "xc3028_DTV8_2633.i2c.fw"     },
+	{1, 0, XC3028_BW7MHZ /* 7mhz  */, "xc3028_DTV7_2633.i2c.fw"  },
+	    /* 2    6mhz  */
+	{0, 1, 0 /* ????  */, "xc3028_DTV6_ATSC_2620.i2c.fw"},
+	{0, 1, 1 /* ????  */, "xc3028_DTV6_ATSC_2620.i2c.fw"},
+	{0, 1, XC3028_ATSC_BW6MHZ, "xc3028_DTV6_ATSC_2620.i2c.fw"},
+};
+
+/* ---------------------------------------------------------------------- */
+#define XC3028_I2C_ADDR 0x61
+
+static int xc3028_i2c_xfer(struct i2c_adapter* adap, char *buf, int len)
+{
+	int ret;
+	struct i2c_msg msg = { .addr = XC3028_I2C_ADDR, .flags = 0,
+			       .buf = buf, .len = len };
+
+	ret = i2c_transfer(adap, &msg, 1);
+
+	/* If everything went ok (i.e. 1 msg transmitted), return #bytes
+	   transmitted, else error code. */
+	return (ret == 1) ? len : ret;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int xc3028_set_params(struct dvb_frontend *fe,
+			     struct dvb_frontend_parameters *params)
+{
+	struct xc3028_priv *priv = fe->tuner_priv;
+	const struct firmware *fw = NULL;
+	unsigned char chanbuf[4];
+	unsigned long frequency=0;
+	unsigned long value;
+	int bandwidth;
+	int i;
+	enum v4l2_tuner_type type;
+
+	if (fe->ops.info.type == FE_ATSC) {
+		type = V4L2_TUNER_ATSC_TV;
+	} else { // if (fe->ops.info.type == FE_OFDM)
+		type = V4L2_TUNER_DVBT_TV;
+	}
+
+	xc3028_set_mode(fe, type);
+
+	if (priv->type == V4L2_TUNER_ATSC_TV) {
+		bandwidth = XC3028_ATSC_BW6MHZ;
+	} else {
+		bandwidth = params->u.ofdm.bandwidth;
+	}
+
+	if (priv->bandwidth != bandwidth) {
+		switch(bandwidth) {
+		case -1:
+			/* analogue */
+			priv->bandwidth = bandwidth;
+			break;
+		case XC3028_ATSC_BW6MHZ:
+			for (i = 0; i < ARRAY_SIZE(xc3028_dtv_standards); i++) {
+				if (xc3028_dtv_standards[i].bandwidth == XC3028_ATSC_BW6MHZ) {
+					printk("Loading 6MHz Bandwidth settings: %s\n",xc3028_dtv_standards[i].filename);
+					if (request_firmware(&fw, xc3028_dtv_standards[i].filename, &priv->i2c_adap->dev) == 0) {
+						if (firmware_loader(fe,fw) != 0) {
+							release_firmware(fw);
+							printk("xc3028-tuner.c: error uploading firmware!\n");
+							return -EINVAL;
+						}
+						release_firmware(fw);
+						break;
+					} else
+						printk("Loading firmware from file failed!\n");
+				}
+			}
+			priv->bandwidth = bandwidth;
+			break;
+		case XC3028_BW8MHZ: /* 8 MHz */
+			for (i = 0; i < ARRAY_SIZE(xc3028_dtv_standards); i++) {
+				if (xc3028_dtv_standards[i].bandwidth == XC3028_BW8MHZ) {
+					printk("Loading 8MHz Bandwidth settings: %s\n",xc3028_dtv_standards[i].filename);
+					if (request_firmware(&fw, xc3028_dtv_standards[i].filename, &priv->i2c_adap->dev) == 0) {
+						if (firmware_loader(fe,fw) != 0) {
+							release_firmware(fw);
+							printk("xc3028-tuner.c: error uploading firmware!\n");
+							return -EINVAL;
+						}
+						release_firmware(fw);
+						break;
+					} else
+						printk("Loading firmware from file failed!\n");
+				}
+			}
+			priv->bandwidth = bandwidth;
+			break;
+		case XC3028_BW7MHZ: /* 7 MHz */
+			for (i = 0; i < ARRAY_SIZE(xc3028_dtv_standards); i++) {
+				if (xc3028_dtv_standards[i].bandwidth == XC3028_BW7MHZ) {
+					printk("Loading 7MHz Bandwidth settings: %s\n",xc3028_dtv_standards[i].filename);
+					if (request_firmware(&fw, xc3028_dtv_standards[i].filename, &priv->i2c_adap->dev) == 0) {
+						if (firmware_loader(fe,fw) != 0) {
+							release_firmware(fw);
+							printk("xc3028-tuner.c: error uploading firmware!\n");
+							return -EINVAL;
+						}
+						release_firmware(fw);
+						break;
+					} else
+						printk("Loading firmware from file failed!\n");
+				}
+			}
+			priv->bandwidth = bandwidth;
+			break;
+		default:
+			printk("xc3028-tuner.c: sorry [%d] bandwidth isn't supported (please report)\n",bandwidth);
+		}
+	}
+	/* TODO: 7 MHz (1) has the same offset as 8 MHz -- this depends on the used firmware */
+#if 0
+	if(t->mode == V4L2_TUNER_RADIO){
+		frequency=(unsigned long long)f->frequency*1000/16;
+	} else { }
+#endif
+	switch(bandwidth) {
+	case XC3028_BW8MHZ:
+	case XC3028_BW7MHZ:
+		frequency=(unsigned long long)params->frequency-2750000;
+		break;
+	case XC3028_BW6MHZ:
+	case XC3028_ATSC_BW6MHZ:
+		frequency=(unsigned long long)params->frequency-1750000;
+		break;
+	default:
+		frequency=(unsigned long long)params->frequency;
+	}
+
+	value=(frequency+(TUNING_GRANULARITY/2))/TUNING_GRANULARITY;
+	chanbuf[0]=0;
+	chanbuf[1]=0;
+	chanbuf[2]=(value&0xff00)>>8;
+	chanbuf[3]=value&0x00ff;
+
+#if 0
+	/* seems like it's not needed! */
+	rc=i2c_master_send(c,"\xa0\x00\x00\x00",4);
+	if(priv && priv->tuning_code)
+		i2c_master_send(c,priv->tuning_code,12);
+	else
+		printk("ERROR: *** NO TUNING CODE SET **\n");
+	i2c_master_send(c,"\x00\x8c",2);
+#endif
+	xc3028_i2c_xfer(priv->i2c_adap,"\x80\x02\x00\x00",4);
+	xc3028_i2c_xfer(priv->i2c_adap,chanbuf,4);
+	return 0;
+}
+
+static int firmware_loader(struct dvb_frontend *fe, const struct firmware *fw)
+{
+	int txtlen=0;
+	int current_ptr=0;
+	int version;
+	int function;
+	int x;
+	struct xc3028_priv *priv = fe->tuner_priv;
+
+	if (fw->size == 0)
+		return -EINVAL;
+
+	version = fw->data[current_ptr++];
+
+	switch (version) {
+	case 1:
+		while (current_ptr < fw->size) {
+			function = fw->data[current_ptr++];
+			switch (function) {
+			case XC3028_BYTECODE:
+				txtlen = fw->data[current_ptr++];
+				if ((current_ptr + txtlen) > fw->size)
+					return -EINVAL;
+				if (fw->data[current_ptr]==0x1e && txtlen == 12){
+					memcpy(priv->tuning_code, &fw->data[current_ptr], 12);
+				}
+				if (txtlen != xc3028_i2c_xfer(priv->i2c_adap,&fw->data[current_ptr],txtlen)) {
+					printk("failed: %02x| ",txtlen);
+					for(x=0; x < txtlen; x++) {
+						printk("%02x ",(unsigned char)fw->data[current_ptr+x]);
+					}
+					printk("\n");
+				}
+
+				current_ptr += txtlen;
+				break;
+			case XC3028_RESET:
+				x = fw->data[current_ptr++];
+
+				if ((priv->cfg) && (priv->cfg->gpio_reset))
+					priv->cfg->gpio_reset(fe,x);
+				else
+					printk("XC3028: NO GPIO CALLBACK FUNCTION PROVIDED - SWITCHING MODES WON'T WORK (GPIO FW ARG: %d)!\n",x);
+				break;
+			case XC3028_SLEEP:
+				msleep(fw->data[current_ptr++]);
+				break;
+			default:
+				printk("xc3028-tuner.c: error while loading firmware!\n");
+				return -EINVAL;
+			}
+		}
+		break;
+	default:
+		printk("xc3028-tuner.c: Firmware Loader: Unknown firmware version (%d)\n",version);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int upload_firmware(struct dvb_frontend *fe) {
+	struct xc3028_priv *priv = fe->tuner_priv;
+	const struct firmware *fw = NULL;
+	int i;
+
+	switch (priv->type) {
+	case V4L2_TUNER_ANALOG_TV:
+		printk("Loading base firmware: %s\n", XC3028_ANALOGUE_FW);
+		if (request_firmware(&fw, XC3028_ANALOGUE_FW, &priv->i2c_adap->dev) == 0) {
+			firmware_loader(fe,fw);
+			release_firmware(fw);
+		} else {
+			printk("xc3028-tuner.c: Unable to load firmware\n");
+			printk("xc3028-tuner.c: ** PLEASE HAVE A LOOK AT **\n");
+			printk("xc3028-tuner.c: http://linuxtv.org/v4lwiki/index.php/Talk:Em2880#Firmware\n");
+			return -EINVAL;
+		}
+		for (i = 0; i < ARRAY_SIZE(xc3028_standards); i++) {
+			if (xc3028_standards[i].standard & priv->std) {
+				printk("%s, Loading specific analogue TV settings: %s\n",__FUNCTION__, xc3028_standards[i].filename);
+				if (request_firmware(&fw, xc3028_standards[i].filename, &priv->i2c_adap->dev) == 0){
+					if(firmware_loader(fe,fw)==0){
+						release_firmware(fw);
+						return 0;
+					}
+					release_firmware(fw);
+				} else {
+					printk("Loading configuration from file failed!\n");
+				}
+				break;
+			}
+		}
+		printk("Loading default analogue TV settings: %s\n",xc3028_standards[0].filename);
+		priv->std=xc3028_standards[0].standard;
+		if (request_firmware(&fw, xc3028_standards[0].filename, &priv->i2c_adap->dev) == 0) {
+			if (firmware_loader(fe,fw) == 0) {
+				release_firmware(fw);
+				return 0;
+			}
+			release_firmware(fw);
+		}
+		printk("xc3028-tuner.c: error loading firmware (analogue TV)! (please report -> mrechberger@gmail.com)\n");
+		break;
+	case V4L2_TUNER_DVBT_TV:
+	case V4L2_TUNER_DVBC_TV:
+	case V4L2_TUNER_ATSC_TV:
+		printk("Loading base firmware: %s\n", XC3028_DIGITAL_FW);
+		/* reset analog standard */
+		priv->std = 0;
+		if (request_firmware(&fw, XC3028_DIGITAL_FW, &priv->i2c_adap->dev) == 0) {
+			firmware_loader(fe,fw);
+			release_firmware(fw);
+		} else {
+			printk("xc3028-tuner.c: Unable to load firmware\n");
+			printk("xc3028-tuner.c: ** PLEASE HAVE A LOOK AT **\n");
+			printk("xc3028-tuner.c: http://linuxtv.org/v4lwiki/index.php/Talk:Em2880#Firmware\n");
+			return -EINVAL;
+		}
+		for( i = 0; i < ARRAY_SIZE(xc3028_dtv_standards); i++) {
+			if ((xc3028_dtv_standards[i].dvb==1 &&
+			     xc3028_dtv_standards[i].bandwidth == priv->bandwidth) ||
+			    (priv->type == V4L2_TUNER_ATSC_TV &&
+			     xc3028_dtv_standards[i].atsc == 1)) {
+				printk("Loading specific dtv settings: %s\n",xc3028_dtv_standards[i].filename);
+				if (request_firmware(&fw, xc3028_dtv_standards[i].filename, &priv->i2c_adap->dev) == 0) {
+					if (firmware_loader(fe,fw) == 0) {
+						release_firmware(fw);
+						return 0;
+					}
+					release_firmware(fw);
+				}
+			}
+			i++;
+		}
+
+		/* this gets accessed if a switchover occures, t->bandwidth will be set to -1 */
+		printk("Loading default dtv settings: %s\n",xc3028_dtv_standards[0].filename);
+		priv->bandwidth = xc3028_dtv_standards[0].bandwidth;
+		if (request_firmware(&fw, xc3028_dtv_standards[0].filename, &priv->i2c_adap->dev) == 0) {
+			if (firmware_loader(fe,fw) == 0) {
+				release_firmware(fw);
+				return 0;
+			}
+			release_firmware(fw);
+		}
+		printk("xc3028-tuner.c: error loading firmware (analogue TV)! (please report -> mrechberger@gmail.com)\n");
+		break;
+	case V4L2_TUNER_RADIO:
+		printk("Loading base firmware: %s\n", XC3028_RADIO_FW);
+		if (request_firmware(&fw, XC3028_RADIO_FW, &priv->i2c_adap->dev) == 0) {
+			firmware_loader(fe,fw);
+			release_firmware(fw);
+		} else {
+			printk("xc3028-tuner.c: Unable to load (radio) firmware\n");
+			printk("xc3028-tuner.c: ** PLEASE HAVE A LOOK AT **\n");
+			printk("xc3028-tuner.c: http://linuxtv.org/v4lwiki/index.php/Talk:Em2880#Firmware\n");
+			return -EINVAL;
+		}
+		printk("%s, Loading specific radio firmware: %s\n",__FUNCTION__, XC3028_SPECIFIC_RADIO_FW);
+		if (request_firmware(&fw, XC3028_SPECIFIC_RADIO_FW, &priv->i2c_adap->dev) == 0) {
+			if (firmware_loader(fe,fw) == 0) {
+				release_firmware(fw);
+				return 0;
+			}
+			release_firmware(fw);
+		} else {
+			printk("Loading configuration from file failed!\n");
+		}
+		return 0;
+	default:
+		printk("ERROR TUNER TYPE NOT SUPPORTED (%d)\n",priv->type);
+		return -EINVAL;
+	}
+
+	printk("xc3028-tuner.c: *********************************************************\n");
+	printk("xc3028-tuner.c: no firmware uploaded\n" );
+	printk("xc3028-tuner.c: ** please have a look at: **\n");
+	printk("xc3028-tuner.c: http://linuxtv.org/v4lwiki/index.php/Talk:Em2880#Firmware\n");
+	printk("xc3028-tuner.c: *********************************************************\n");
+
+	return -EINVAL;
+}
+
+static int xc3028_set_mode(struct dvb_frontend *fe, enum v4l2_tuner_type type)
+{
+	struct xc3028_priv *priv = fe->tuner_priv;
+
+	priv->type = type;
+
+	upload_firmware(fe);
+
+	if (priv->cfg && priv->cfg->gpio_reset && (priv->type == V4L2_TUNER_DVBT_TV ||
+						   priv->type == V4L2_TUNER_DVBC_TV ||
+						   priv->type == V4L2_TUNER_ATSC_TV )) {
+		printk("xc3028-tuner.c: sending extra call for DVB-T\n");
+		priv->cfg->gpio_reset(fe,2);
+	}
+
+	return 0;
+}
+
+/* dvb tuner api */
+static int xc3028_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static int xc3028_get_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+	struct xc3028_priv *priv = fe->tuner_priv;
+
+	*frequency = priv->frequency*1000/16*1000;
+	return 0;
+}
+
+static int xc3028_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
+{
+	struct xc3028_priv *priv = fe->tuner_priv;
+
+	*bandwidth = priv->bandwidth;
+	return 0;
+}
+
+static const struct dvb_tuner_ops xc3028_tuner_ops = {
+	.info = {
+		.name = "Xceive XC3028",
+#if 0
+		.frequency_min = ,
+		.frequency_max =
+#endif
+	},
+	.release       = xc3028_release,
+	.set_params    = xc3028_set_params,
+	.get_frequency = xc3028_get_frequency,
+	.get_bandwidth = xc3028_get_bandwidth,
+};
+
+struct dvb_frontend *xc3028_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, struct xc3028_config *cfg)
+{
+	struct xc3028_priv *priv = kzalloc(sizeof(struct xc3028_priv),GFP_KERNEL);
+
+	priv->i2c_adap = i2c;
+	priv->bandwidth=XC3028_BW8MHZ;
+	priv->cfg = cfg;
+
+	memcpy(&fe->ops.tuner_ops, &xc3028_tuner_ops, sizeof(struct dvb_tuner_ops));
+	fe->tuner_priv = priv;
+	return fe;
+}
+EXPORT_SYMBOL(xc3028_attach);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
--- /dev/null
+++ v4l-dvb/linux/drivers/media/dvb/frontends/xc3028.h
@@ -0,0 +1,56 @@
+  /*
+     Header for Xceive Silicon tuners
+
+     (c) 2007 Michael Krufky
+
+     This program is free software; you can redistribute it and/or modify
+     it under the terms of the GNU General Public License as published by
+     the Free Software Foundation; either version 2 of the License, or
+     (at your option) any later version.
+
+     This program is distributed in the hope that it will be useful,
+     but WITHOUT ANY WARRANTY; without even the implied warranty of
+     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+
+     GNU General Public License for more details.
+
+     You should have received a copy of the GNU General Public License
+     along with this program; if not, write to the Free Software
+     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+  */
+
+#ifndef __DVB_XC3028_H__
+#define __DVB_XC3028_H__
+
+#include <linux/i2c.h>
+#include "dvb_frontend.h"
+
+/**
+ * Attach a xc3028 tuner to the supplied frontend structure.
+ *
+ * @param fe Frontend to attach to.
+ * @param i2c i2c adapter to use.
+ * @param cfg config struct with gpio reset callback.
+ * @return FE pointer on success, NULL on failure.
+ */
+
+struct xc3028_config {
+	int (*gpio_reset) (struct dvb_frontend *fe, int ptr);
+};
+
+#if defined(CONFIG_DVB_XC3028) || (defined(CONFIG_DVB_XC3028_MODULE) && defined(MODULE))
+extern struct dvb_frontend* xc3028_attach(struct dvb_frontend *fe,
+					  struct i2c_adapter *i2c,
+					  struct xc3028_config *cfg);
+#else
+static inline struct dvb_frontend* xc3028_attach(struct dvb_frontend *fe,
+						 struct i2c_adapter *i2c,
+						 struct xc3028_config *cfg)
+{
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __FUNCTION__);
+	return NULL;
+}
+#endif // CONFIG_DVB_XC3028
+
+#endif // __DVB_XC3028_H__
--- v4l-dvb.orig/v4l/versions.txt
+++ v4l-dvb/v4l/versions.txt
@@ -228,3 +228,4 @@
 USB_ZC0301
 USB_ET61X251
 USB_ZR364XX
+DVB_XC3028