From 6f6cb195b8f7dbc2d0b4e31a2da4cd58bd69f6bf Mon Sep 17 00:00:00 2001
From: Athanasios Oikonomou <athoik@gmail.com>
Date: Sat, 5 Mar 2016 00:29:45 +0200
Subject: [PATCH] Support TBS USB drivers

This patch add supports for TBS USB drivers based on the following patches:

https://patchwork.linuxtv.org/patch/23244/
https://patchwork.linuxtv.org/patch/23243/
https://patchwork.linuxtv.org/patch/23242/

diff --git a/drivers/media/dvb-core/dvb-usb-ids.h b/drivers/media/dvb-core/dvb-usb-ids.h
index c117fb3..5e0735b 100644
--- a/drivers/media/dvb-core/dvb-usb-ids.h
+++ b/drivers/media/dvb-core/dvb-usb-ids.h
@@ -78,6 +78,7 @@
 #define USB_VID_EVOLUTEPC			0x1e59
 #define USB_VID_AZUREWAVE			0x13d3
 #define USB_VID_TECHNISAT			0x14f7
+#define USB_VID_TENOW				0x734c
 
 /* Product IDs */
 #define USB_PID_ADSTECH_USB2_COLD			0xa333
@@ -413,4 +414,10 @@
 #define USB_PID_TURBOX_DTT_2000                         0xd3a4
 #define USB_PID_EVOLVEO_XTRATV_STICK                   0xa115
 #define USB_PID_WINTV_SOLOHD                            0x0264
+#define USB_PID_TENOW_TBS5910				0x5910
+#define USB_PID_TENOW_TBS5920				0x5920
+#define USB_PID_TENOW_TBS5921				0x5921
+#define USB_PID_TENOW_TBS5925				0x5925
+#define USB_PID_TENOW_TBS5928				0x5928
+#define USB_PID_TENOW_TBS5980				0x5980
 #endif
diff --git a/drivers/media/dvb-frontends/cx24116.c b/drivers/media/dvb-frontends/cx24116.c
index 8814f36..5ccfeb3 100644
--- a/drivers/media/dvb-frontends/cx24116.c
+++ b/drivers/media/dvb-frontends/cx24116.c
@@ -705,6 +705,9 @@ static int cx24116_read_status(struct dvb_frontend *fe, enum fe_status *status)
 	if (lock & CX24116_HAS_SYNCLOCK)
 		*status |= FE_HAS_SYNC | FE_HAS_LOCK;
 
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, *status & FE_HAS_LOCK);
+
 	return 0;
 }
 
@@ -1113,6 +1116,10 @@ static void cx24116_release(struct dvb_frontend *fe)
 {
 	struct cx24116_state *state = fe->demodulator_priv;
 	dprintk("%s\n", __func__);
+
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, 0);
+
 	kfree(state);
 }
 
@@ -1198,6 +1205,9 @@ static int cx24116_sleep(struct dvb_frontend *fe)
 
 	dprintk("%s()\n", __func__);
 
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, 0);
+
 	/* Firmware CMD 36: Power config */
 	cmd.args[0x00] = CMD_TUNERSLEEP;
 	cmd.args[0x01] = 1;
diff --git a/drivers/media/dvb-frontends/cx24116.h b/drivers/media/dvb-frontends/cx24116.h
index f6dbabc..7cbb907 100644
--- a/drivers/media/dvb-frontends/cx24116.h
+++ b/drivers/media/dvb-frontends/cx24116.h
@@ -38,6 +38,9 @@ struct cx24116_config {
 
 	/* max bytes I2C provider can write at once */
 	u16 i2c_wr_max;
+
+	/* Hook for Lock LED */
+	void (*set_lock_led)(struct dvb_frontend *fe, int offon);
 };
 
 #if IS_REACHABLE(CONFIG_DVB_CX24116)
diff --git a/drivers/media/dvb-frontends/stv0288.c b/drivers/media/dvb-frontends/stv0288.c
index c93d9a4..9dcd922 100644
--- a/drivers/media/dvb-frontends/stv0288.c
+++ b/drivers/media/dvb-frontends/stv0288.c
@@ -382,6 +382,9 @@ static int stv0288_read_status(struct dvb_frontend *fe, enum fe_status *status)
 		dprintk("stv0288 has locked\n");
 	}
 
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, *status & FE_HAS_LOCK);
+
 	return 0;
 }
 
@@ -416,6 +419,9 @@ static int stv0288_sleep(struct dvb_frontend *fe)
 {
 	struct stv0288_state *state = fe->demodulator_priv;
 
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, 0);
+
 	stv0288_writeregI(state, 0x41, 0x84);
 	state->initialised = 0;
 
@@ -532,6 +538,10 @@ static int stv0288_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
 static void stv0288_release(struct dvb_frontend *fe)
 {
 	struct stv0288_state *state = fe->demodulator_priv;
+
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, 0);
+
 	kfree(state);
 }
 
diff --git a/drivers/media/dvb-frontends/stv0288.h b/drivers/media/dvb-frontends/stv0288.h
index b58603c..01d8481 100644
--- a/drivers/media/dvb-frontends/stv0288.h
+++ b/drivers/media/dvb-frontends/stv0288.h
@@ -40,6 +40,9 @@ struct stv0288_config {
 	int min_delay_ms;
 
 	int (*set_ts_params)(struct dvb_frontend *fe, int is_punctured);
+
+	/* Hook for Lock LED */
+	void (*set_lock_led)(struct dvb_frontend *fe, int offon);
 };
 
 #if IS_REACHABLE(CONFIG_DVB_STV0288)
diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c
index 25bdf6e..ce99b9d 100644
--- a/drivers/media/dvb-frontends/stv090x.c
+++ b/drivers/media/dvb-frontends/stv090x.c
@@ -3553,6 +3553,9 @@ static int stv090x_read_status(struct dvb_frontend *fe, enum fe_status *status)
 		break;
 	}
 
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, *status & FE_HAS_LOCK);
+
 	return 0;
 }
 
@@ -3901,6 +3904,9 @@ static int stv090x_sleep(struct dvb_frontend *fe)
 	u32 reg;
 	u8 full_standby = 0;
 
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, 0);
+
 	if (stv090x_i2c_gate_ctrl(state, 1) < 0)
 		goto err;
 
@@ -4132,6 +4138,9 @@ static void stv090x_release(struct dvb_frontend *fe)
 {
 	struct stv090x_state *state = fe->demodulator_priv;
 
+	if (state->config->set_lock_led)
+		state->config->set_lock_led(fe, 0);
+
 	state->internal->num_used--;
 	if (state->internal->num_used <= 0) {
 
diff --git a/drivers/media/dvb-frontends/stv090x.h b/drivers/media/dvb-frontends/stv090x.h
index 012e55e..545f43a 100644
--- a/drivers/media/dvb-frontends/stv090x.h
+++ b/drivers/media/dvb-frontends/stv090x.h
@@ -89,6 +89,8 @@ struct stv090x_config {
 
 	bool diseqc_envelope_mode;
 
+	/* Hook for Lock LED */
+	void (*set_lock_led) (struct dvb_frontend *fe, int offon);
 	int (*tuner_init)(struct dvb_frontend *fe);
 	int (*tuner_sleep)(struct dvb_frontend *fe);
 	int (*tuner_set_mode)(struct dvb_frontend *fe, enum tuner_mode mode);
diff --git a/drivers/media/dvb-frontends/tda10071.h b/drivers/media/dvb-frontends/tda10071.h
index 8f18402..891e007 100644
--- a/drivers/media/dvb-frontends/tda10071.h
+++ b/drivers/media/dvb-frontends/tda10071.h
@@ -49,6 +49,9 @@ struct tda10071_platform_data {
 	u8 pll_multiplier;
 	u8 tuner_i2c_addr;
 
+	/* Hook for Lock LED */
+	void (*set_lock_led)(struct dvb_frontend *fe, int offon);
+
 	struct dvb_frontend* (*get_dvb_frontend)(struct i2c_client *);
 };
 
diff --git a/drivers/media/usb/dvb-usb/Kconfig b/drivers/media/usb/dvb-usb/Kconfig
index 128eee6..49c34f9 100644
--- a/drivers/media/usb/dvb-usb/Kconfig
+++ b/drivers/media/usb/dvb-usb/Kconfig
@@ -331,3 +331,23 @@ config DVB_USB_TECHNISAT_USB2
 	select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Say Y here to support the Technisat USB2 DVB-S/S2 device
+
+config DVB_USB_TBS
+	tristate "TurboSight DVB-S/S2 USB2.0 support"
+	depends on DVB_USB
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10071 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support TurboSight (TBS) DVB-S/S2 USB2.0 receivers.
+	  Required firmware can be found at http://www.tbsdtv.com/download/
+	  The tda10071 (TBS5921) firmware can be downloaded by executing:
+	  Documentation/dvb/get_dvb_firmware tda10071
+
+	  Supported devices are:
+	  TBS5980 TBS5928 TBS5925 TBS5921 TBS5920 TBS5910
diff --git a/drivers/media/usb/dvb-usb/Makefile b/drivers/media/usb/dvb-usb/Makefile
index acdd1ef..cb00137 100644
--- a/drivers/media/usb/dvb-usb/Makefile
+++ b/drivers/media/usb/dvb-usb/Makefile
@@ -79,6 +79,9 @@ obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-az6027.o
 dvb-usb-technisat-usb2-objs := technisat-usb2.o
 obj-$(CONFIG_DVB_USB_TECHNISAT_USB2) += dvb-usb-technisat-usb2.o
 
+dvb-usb-tbsusb-objs := tbs-usb.o
+obj-$(CONFIG_DVB_USB_TBS) += dvb-usb-tbsusb.o
+
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends/
 # due to tuner-xc3028
diff --git a/drivers/media/usb/dvb-usb/tbs-usb.c b/drivers/media/usb/dvb-usb/tbs-usb.c
new file mode 100644
index 0000000..f142be3
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/tbs-usb.c
@@ -0,0 +1,1075 @@
+/*
+ * TBS 5980/5928/5925/5921/5920/5910 DVB-S/S2 driver
+ *
+ * Copyright (c) 2008 Bob Liu (Bob@Turbosight.com)
+ *                    Igor M. Liplianin (liplianin@me.by)
+ * Copyright (c) 2009 Konstantin Dimitrov <kosio.dimitrov@gmail.com>
+ * Copyright (c) 2014 Andreas Steinmetz <ast@domdv.de>
+ *                    Lock LED and TBS5921 stuff shamelessly taken from
+ *                    CrazyCat's Bitbucket repository
+ *                    TBS5925 Open Source version shamelessly taken from
+ *                    UpdateLee's Bitbucket repository
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/version.h>
+#include "tbs-usb.h"
+
+#include "stv6110x.h"
+#include "stv090x.h"
+#include "stb6100.h"
+#include "stb6100_cfg.h"
+
+#include "cx24116.h"
+
+#include "stv0299.h"
+#include "stv0288.h"
+#include "stb6000.h"
+
+#include "tda10071.h"
+
+#include "dvb_ca_en50221.h"
+
+struct tbsusbci_state {
+	u8 buf[20];
+	struct dvb_ca_en50221 ca;
+	struct mutex ca_mutex;
+};
+
+struct tbsusb_state {
+	u8 buf[20];
+};
+
+static int dvb_usb_tbsusb_debug;
+module_param_named(debug, dvb_usb_tbsusb_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info 2=xfer (or-able))."
+							DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static struct rc_map_table tbsusb_rc_keys[] = {
+	{ 0xff84, KEY_POWER2},		/* power */
+	{ 0xff94, KEY_MUTE},		/* mute */
+	{ 0xff87, KEY_1},
+	{ 0xff86, KEY_2},
+	{ 0xff85, KEY_3},
+	{ 0xff8b, KEY_4},
+	{ 0xff8a, KEY_5},
+	{ 0xff89, KEY_6},
+	{ 0xff8f, KEY_7},
+	{ 0xff8e, KEY_8},
+	{ 0xff8d, KEY_9},
+	{ 0xff92, KEY_0},
+	{ 0xff96, KEY_CHANNELUP},	/* ch+ */
+	{ 0xff91, KEY_CHANNELDOWN},	/* ch- */
+	{ 0xff93, KEY_VOLUMEUP},	/* vol+ */
+	{ 0xff8c, KEY_VOLUMEDOWN},	/* vol- */
+	{ 0xff83, KEY_RECORD},		/* rec */
+	{ 0xff98, KEY_PAUSE},		/* pause, yellow */
+	{ 0xff99, KEY_OK},		/* ok */
+	{ 0xff9a, KEY_CAMERA},		/* snapshot */
+	{ 0xff81, KEY_UP},
+	{ 0xff90, KEY_LEFT},
+	{ 0xff82, KEY_RIGHT},
+	{ 0xff88, KEY_DOWN},
+	{ 0xff95, KEY_FAVORITES},	/* blue */
+	{ 0xff97, KEY_SUBTITLE},	/* green */
+	{ 0xff9d, KEY_ZOOM},
+	{ 0xff9f, KEY_EXIT},
+	{ 0xff9e, KEY_MENU},
+	{ 0xff9c, KEY_EPG},
+	{ 0xff80, KEY_PREVIOUS},	/* red */
+	{ 0xff9b, KEY_MODE},
+	{ 0xffdd, KEY_TV },
+	{ 0xffde, KEY_PLAY },
+	{ 0xffdc, KEY_STOP },
+	{ 0xffdb, KEY_REWIND },
+	{ 0xffda, KEY_FASTFORWARD },
+	{ 0xffd9, KEY_PREVIOUS },	/* replay */
+	{ 0xffd8, KEY_NEXT },		/* skip */
+	{ 0xffd1, KEY_NUMERIC_STAR },
+	{ 0xffd2, KEY_NUMERIC_POUND },
+	{ 0xffd4, KEY_DELETE },		/* clear */
+};
+
+static int tbsusb_op_r_unlocked(struct usb_device *dev, int num, u8 request,
+					u16 value, u16 index, u8 *data, int len)
+{
+	int ret;
+
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), request,
+		USB_TYPE_VENDOR | USB_DIR_IN, value, index, data, len, 2000);
+
+	/* oh well, rc poll returns -EOVERFLOW but also the required data */
+	if (ret == -EOVERFLOW && request == 0xb8)
+		ret = 0;
+	else if (ret < 0) {
+		warn("usb read 0x%02x/%d from adapter %d failed. (%d)",
+							request, len, num, ret);
+		ret = -EIO;
+	} else
+		ret = 0;
+
+	deb_xfer("read: adap.: %d, req. %02x, val: %04x, ind: %04x, buffer: ",
+		num, request, value, index);
+	debug_dump(data, len, deb_xfer);
+
+	return ret;
+}
+
+static int tbsusb_op_w_unlocked(struct usb_device *dev, int num, u8 request,
+					u16 value, u16 index, u8 *data, int len)
+{
+	int ret;
+
+	deb_xfer("write: adap.: %d, req. %02x, val: %04x, ind: %04x, buffer: ",
+		num, request, value, index);
+	debug_dump(data, len, deb_xfer);
+
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), request,
+		USB_TYPE_VENDOR | USB_DIR_OUT, value, index, data, len, 2000);
+
+	if (ret != len) {
+		warn("usb write 0x%02x/%d to adapter %d failed. (%d)",
+							request, len, num, ret);
+		ret = -EIO;
+	} else
+		ret = 0;
+
+	return ret;
+}
+
+static int tbsusb_load_firmware(struct usb_device *dev,
+			const struct firmware *frmwr)
+{
+	u8 *b;
+	int ret, i, j;
+
+	if (!dev || !frmwr)
+		return -ENODEV;
+
+	b = kmalloc(0x40, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	/*stop the CPU*/
+	b[0] = 1;
+	ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0x7f92, 0, b, 1);
+	if (!ret)
+		ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0xe600, 0, b, 1);
+	if (ret) {
+		err("could not stop the USB controller CPU.");
+		goto err;
+	}
+
+	for (i = 0; i < frmwr->size; i += 0x40) {
+		j = i & 0x3f;
+		if (!j)
+			j = 0x40;
+		memcpy(b, (u8 *) frmwr->data + i, j);
+		ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, i, 0, b, j);
+		if (ret) {
+			err("error while transferring firmware.");
+			goto err;
+		}
+	}
+
+	/* restart the CPU */
+	b[0] = 0;
+	ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0x7f92, 0, b, 1);
+	if (!ret)
+		ret = tbsusb_op_w_unlocked(dev, -1, 0xa0, 0xe600, 0, b, 1);
+	if (ret) {
+		err("could not restart the USB controller CPU.");
+		goto err;
+	}
+
+	msleep(100);
+
+err:	kfree(b);
+	return ret;
+}
+
+/* without copying the data back and forth usb transfers randomly fail... */
+
+static int tbsusb_op_wwr(struct dvb_usb_device *d, u8 wrq, u8 wrrq, int wwait,
+				int wrwait, u8 *wdata, int wlen, u8 *wrdata,
+				int wrlen, int wrop)
+{
+	struct tbsusb_state *s;
+	u8 *b;
+	int ret, len, num;
+
+	if (d && d->udev && d->priv)
+		s = (struct tbsusb_state *)d->priv;
+	else
+		return -ENODEV;
+
+	if (wrlen > wlen)
+		len = wrlen;
+	else
+		len = wlen;
+
+	if (len > sizeof(s->buf)) {
+		b = kmalloc(len, GFP_KERNEL);
+		if (!b)
+			return -ENOMEM;
+	} else
+		b = s->buf;
+
+	if (d->adapter[0].fe_adap[0].fe && d->adapter[0].fe_adap[0].fe->dvb)
+		num = d->adapter[0].fe_adap[0].fe->dvb->num;
+	else
+		num = -1;
+
+	mutex_lock(&d->usb_mutex);
+	if (wdata) {
+		memcpy(b, wdata, wlen);
+		ret = tbsusb_op_w_unlocked(d->udev, num, wrq, 0, 0, b, wlen);
+		if (ret)
+			goto err;
+		if (wwait)
+			usleep_range(wwait, wwait+1000);
+	}
+	if (wrdata) {
+		if (wrop) {
+			memcpy(b, wrdata, wrlen);
+			ret = tbsusb_op_w_unlocked(d->udev, num, wrrq, 0, 0, b,
+									wrlen);
+		} else
+			ret = tbsusb_op_r_unlocked(d->udev, num, wrrq, 0, 0, b,
+									wrlen);
+		if (ret)
+			goto err;
+		if (!wrop)
+			memcpy(wrdata, b, wrlen);
+		if (wrwait)
+			usleep_range(wrwait, wrwait+1000);
+	}
+err:	mutex_unlock(&d->usb_mutex);
+
+	if (len > sizeof(s->buf))
+		kfree(b);
+
+	return ret;
+}
+
+static int tbsusb_op_ww(struct dvb_usb_device *d, u8 wrq1, u8 wrq2, int wwait1,
+				int wwait2, u8 *wdata1, int wlen1, u8 *wdata2,
+				int wlen2)
+{
+	return tbsusb_op_wwr(d, wrq1, wrq2, wwait1, wwait2, wdata1, wlen1,
+							wdata2, wlen2, 1);
+}
+
+static int tbsusb_op_wr(struct dvb_usb_device *d, u8 wrq, u8 rrq, int wwait,
+				int rwait, u8 *wdata, int wlen, u8 *rdata,
+				int rlen)
+{
+	return tbsusb_op_wwr(d, wrq, rrq, wwait, rwait, wdata, wlen, rdata,
+								rlen, 0);
+}
+
+static int tbsusb_op_w(struct dvb_usb_device *d, u8 req, int uwait,
+							u8 *data, int len)
+{
+	return tbsusb_op_wwr(d, req, 0x00, uwait, 0, data, len, NULL, 0, 0);
+}
+
+static int tbsusb_op_r(struct dvb_usb_device *d, u8 req, int uwait,
+							u8 *data, int len)
+{
+	return tbsusb_op_wwr(d, 0x00, req, 0, uwait, NULL, 0, data, len, 0);
+}
+
+static int tbsusb_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
+{
+	int i, ret;
+	u8 buf[3];
+
+	for (i = 0; i < 6; i++) {
+		buf[0] = 1;		/* length */
+		buf[1] = 0xa0;		/* eeprom addr */
+		buf[2] = i + 16;	/* register (0-255) */
+		ret = tbsusb_op_wr(d, 0x90, 0x91, 0, 0, buf, 3, &mac[i], 1);
+		if (ret) {
+			err("eeprom read failed.");
+			return ret;
+		}
+	}
+	return 0;
+};
+
+static int tbsusb_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	int i;
+	u8 buf[4];
+
+	*state = REMOTE_NO_KEY_PRESSED;
+
+	i = tbsusb_op_r(d, 0xb8, 3000, buf, 4);
+	if (i)
+		goto out;
+
+	for (i = 0; i < ARRAY_SIZE(tbsusb_rc_keys); i++) {
+		if (rc5_data(&tbsusb_rc_keys[i]) == buf[3]) {
+			*state = REMOTE_KEY_PRESSED;
+			*event = tbsusb_rc_keys[i].keycode;
+			break;
+		}
+	}
+
+out:	return 0;
+}
+
+static int tbsusb_set_pin(struct dvb_frontend *fe, u8 *what)
+{
+	struct dvb_usb_device *d = NULL;
+
+	if (fe && fe->dvb && fe->dvb->priv)
+		d = ((struct dvb_usb_adapter *)fe->dvb->priv)->dev;
+	if (!d)
+		return -ENODEV;
+
+	return tbsusb_op_w(d, 0x8a, 0, what, 2);
+}
+
+static int tbsusb_set_voltage(struct dvb_frontend *fe,
+						fe_sec_voltage_t voltage)
+{
+	static u8 command_13v[2] = {0x03, 0x00};
+	static u8 command_18v[2] = {0x03, 0x01};
+
+	return tbsusb_set_pin(fe,
+			voltage == SEC_VOLTAGE_18 ? command_18v : command_13v);
+}
+
+static void tbsusb_led_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	static u8 led_off[2] = {0x05, 0x00};
+	static u8 led_on[2]  = {0x05, 0x01};
+
+	tbsusb_set_pin(fe, onoff ? led_on : led_off);
+}
+
+static int tbsusb_i2c_transfer(struct i2c_adapter *adap,
+					struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret = -EINVAL, j = 3000, i;
+	u8 buf[20];
+
+	if (!d || !d->desc)
+		return -ENODEV;
+
+	switch (num<<16 | d->desc->cold_ids[0]->idProduct) {
+	case 0x20000|USB_PID_TENOW_TBS5980:
+	case 0x20000|USB_PID_TENOW_TBS5925:
+	case 0x20000|USB_PID_TENOW_TBS5920:
+		buf[0] = msg[1].len;		/* length */
+		buf[1] = msg[0].addr<<1;	/* demod addr */
+		/* register */
+		buf[2] = msg[0].buf[0];
+		buf[3] = msg[0].buf[1];
+
+		ret = tbsusb_op_wr(d, 0x92, 0x91, 0, 0, buf, 4, msg[1].buf,
+								msg[1].len);
+		break;
+	case 0x20000|USB_PID_TENOW_TBS5928:
+	case 0x20000|USB_PID_TENOW_TBS5921:
+		/* read */
+		buf[0] = msg[0].len;		/* length */
+		goto wrop;
+	case 0x20000|USB_PID_TENOW_TBS5910:
+		/* read */
+		buf[0] = msg[1].len;		/* length */
+wrop:		buf[1] = msg[0].addr<<1;	/* demod addr */
+		/* register */
+		buf[2] = msg[0].buf[0];
+
+		ret = tbsusb_op_wr(d, 0x90, 0x91, 5000, 0, buf, 3, msg[1].buf,
+								msg[1].len);
+		break;
+	case 0x10000|USB_PID_TENOW_TBS5980:
+	case 0x10000|USB_PID_TENOW_TBS5925:
+	case 0x10000|USB_PID_TENOW_TBS5920:
+		switch (msg[0].addr) {
+		case 0x6a:
+		case 0x68:
+		case 0x61:
+		case 0x60:
+			if (!msg[0].flags) {
+				j = 0;
+				goto wraddr;
+			} else {
+				buf[0] = msg[0].len;		/* length */
+				buf[1] = msg[0].addr<<1;	/* addr */
+				buf[2] = 0x00;
+				ret = tbsusb_op_wr(d, 0x90, 0x91, 0, 0, buf, 3,
+							msg[0].buf, msg[0].len);
+			}
+			break;
+		}
+		break;
+	case 0x10000|USB_PID_TENOW_TBS5928:
+		switch (msg[0].addr) {
+		case 0x55:
+			if (msg[0].buf[0] == 0xf7) {
+				/* firmware */
+				/* Write in small blocks */
+				buf[2] = 0xf7;
+				goto wrfw;
+			} else {
+				/* write to register */
+				j = 0;
+				goto wraddr;
+			}
+			break;
+		case 0x60:
+			/* write to register */
+			goto wraddr;
+		}
+		break;
+	case 0x10000|USB_PID_TENOW_TBS5921:
+		switch (msg[0].addr) {
+		case 0x55:
+			if (msg[0].buf[0] == 0xfa) {
+				/* firmware */
+				/* Write in small blocks */
+				buf[2] = 0xfa;
+wrfw:				buf[0] = 0x12;
+				buf[1] = 0xaa;
+				j = msg[0].len - 1;
+				i = 1;
+				do {
+					memcpy(buf + 3, msg[0].buf + i,
+							j > 16 ? 16 : j);
+					ret = tbsusb_op_w(d, 0x80, 0, buf,
+							j > 16 ? 19 : j+3);
+					i += 16;
+					j -= 16;
+				} while (!ret && j > 0);
+			} else {
+				/* write to register */
+				j = 0;
+				goto wraddr;
+			}
+			break;
+		case 0x60:
+			/* write to register */
+			goto wraddr;
+			break;
+		}
+		break;
+	case 0x10000|USB_PID_TENOW_TBS5910:
+		switch (msg[0].addr) {
+		case 0x61:
+		case 0x60:
+			if (!msg[0].flags) {
+				/* write to tuner pll */
+				goto wraddr;
+			}
+			break;
+		case 0x68:
+			/* write to stv0299 register */
+wraddr:			if (msg[0].len+2 > sizeof(buf))
+				break;
+			buf[0] = msg[0].len+1;		/* length */
+			buf[1] = msg[0].addr<<1;	/* addr */
+			/* register */
+			memcpy(buf+2, msg[0].buf, msg[0].len);
+			ret = tbsusb_op_w(d, 0x80, j, buf, msg[0].len+2);
+			break;
+		}
+		break;
+	}
+
+	if (ret)
+		return ret;
+	return num;
+}
+
+static u32 tbsusb_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm tbsusb_i2c_algo = {
+	.master_xfer   = tbsusb_i2c_transfer,
+	.functionality = tbsusb_i2c_func,
+};
+
+static int tbsusb_ci_setup(struct dvb_ca_en50221 *ca, struct dvb_usb_device **d,
+					struct tbsusbci_state **state, int slot)
+{
+	if (slot)
+		return -EINVAL;
+	if (ca && ca->data) {
+		*d = (struct dvb_usb_device *)ca->data;
+		if (*d && (*d)->udev && (*d)->priv) {
+			*state = (struct tbsusbci_state *)(*d)->priv;
+			mutex_lock(&(*state)->ca_mutex);
+			return 0;
+		}
+	}
+	return -ENODEV;
+}
+
+static void tbsusb_ci_finish(struct tbsusbci_state *state)
+{
+	mutex_unlock(&state->ca_mutex);
+}
+
+static int tbsusb_slot_reset(struct dvb_ca_en50221 *ca, int slot)
+{
+	static u8 msg1[2] = {0x01, 0x00};
+	static u8 msg2[2] = {0x01, 0x01};
+	struct dvb_usb_device *d;
+	struct tbsusbci_state *state;
+	int ret;
+
+	ret = tbsusb_ci_setup(ca, &d, &state, slot);
+	if (ret)
+		return ret;
+
+	ret = tbsusb_op_ww(d, 0xa6, 0xa6, 5000, 0, msg1, 2, msg2, 2);
+	if (!ret)
+		msleep(1400);
+
+	tbsusb_ci_finish(state);
+
+	return ret;
+}
+
+static int tbsusb_cam_read(struct dvb_ca_en50221 *ca, int slot, int address,
+								u8 where)
+{
+	struct dvb_usb_device *d;
+	struct tbsusbci_state *state;
+	int ret;
+	u8 buf[4], rbuf[1];
+
+	buf[0] = 1;
+	buf[1] = where;
+	buf[2] = (address >> 8) & 0x0f;
+	buf[3] = address;
+
+	ret = tbsusb_ci_setup(ca, &d, &state, slot);
+	if (ret)
+		return ret;
+
+	ret = tbsusb_op_wr(d, 0xa4, 0xa5, 0, 0, buf, 4, rbuf, 1);
+
+	tbsusb_ci_finish(state);
+
+	if (ret)
+		return ret;
+
+	return rbuf[0];
+}
+
+static int tbsusb_cam_write(struct dvb_ca_en50221 *ca, int slot, int address,
+							u8 value, u8 where)
+{
+	struct dvb_usb_device *d;
+	struct tbsusbci_state *state;
+	int ret;
+	u8 buf[5];
+
+	buf[0] = 1;
+	buf[1] = where;
+	buf[2] = (address >> 8) & 0x0f;
+	buf[3] = address;
+	buf[4] = value;
+
+	ret = tbsusb_ci_setup(ca, &d, &state, slot);
+	if (ret)
+		return ret;
+
+	ret = tbsusb_op_w(d, 0xa2, 0, buf, 5);
+
+	tbsusb_ci_finish(state);
+
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int tbsusb_read_attribute_mem(struct dvb_ca_en50221 *ca,
+							int slot, int address)
+{
+	return tbsusb_cam_read(ca, slot, address, 0);
+}
+
+static int tbsusb_write_attribute_mem(struct dvb_ca_en50221 *ca,
+						int slot, int address, u8 value)
+{
+	return tbsusb_cam_write(ca, slot, address, value, 0);
+}
+
+static int tbsusb_read_cam_control(struct dvb_ca_en50221 *ca, int slot,
+								u8 address)
+{
+	return tbsusb_cam_read(ca, slot, address, 1);
+}
+
+static int tbsusb_write_cam_control(struct dvb_ca_en50221 *ca, int slot,
+							u8 address, u8 value)
+{
+	return tbsusb_cam_write(ca, slot, address, value, 1);
+}
+
+static int tbsusb_set_video_port(struct dvb_ca_en50221 *ca,
+							int slot, int enable)
+{
+	struct dvb_usb_device *d;
+	struct tbsusbci_state *state;
+	int ret;
+	u8 buf[2];
+
+	buf[0] = 2;
+	buf[1] = enable;
+
+	ret = tbsusb_ci_setup(ca, &d, &state, slot);
+	if (ret)
+		return ret;
+
+	ret = tbsusb_op_w(d, 0xa6, 0, buf, 2);
+
+	tbsusb_ci_finish(state);
+
+	if (ret) {
+		err("CI not %sabled.", enable ? "en" : "dis");
+		return ret;
+	}
+
+	deb_info("CI %sabled.", enable ? "en" : "dis");
+	return 0;
+}
+
+static int tbsusb_slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
+{
+	return tbsusb_set_video_port(ca, slot, 0);
+}
+
+static int tbsusb_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
+{
+	return tbsusb_set_video_port(ca, slot, 1);
+}
+
+static int tbsusb_poll_slot_status(struct dvb_ca_en50221 *ca,
+							int slot, int open)
+{
+	struct dvb_usb_device *d;
+	struct tbsusbci_state *state;
+	int ret;
+	u8 buf[3];
+
+	ret = tbsusb_ci_setup(ca, &d, &state, slot);
+	if (ret)
+		return 0;
+
+	ret = tbsusb_op_r(d, 0xa8, 0, buf, 3);
+
+	tbsusb_ci_finish(state);
+
+	if (ret || buf[0] != 0xa9 || buf[1] != 1 || buf[2] != 1)
+		return 0;
+
+	return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY;
+}
+
+static void tbsusbci_uninit(struct dvb_usb_device *d)
+{
+	struct tbsusbci_state *state;
+
+	if (d && d->priv)
+		state = (struct tbsusbci_state *)d->priv;
+	else
+		return;
+
+	if (!state || !state->ca.data)
+		return;
+
+	dvb_ca_en50221_release(&state->ca);
+	memset(&state->ca, 0, sizeof(state->ca));
+}
+
+static int tbsusbci_init(struct dvb_usb_adapter *a)
+{
+	struct tbsusbci_state *state;
+	int ret = -ENODEV;
+
+	if (a && a->dev && a->dev->priv)
+		state = (struct tbsusbci_state *)a->dev->priv;
+	else
+		goto err;
+
+	mutex_init(&state->ca_mutex);
+
+	state->ca.owner = THIS_MODULE;
+	state->ca.read_attribute_mem = tbsusb_read_attribute_mem;
+	state->ca.write_attribute_mem = tbsusb_write_attribute_mem;
+	state->ca.read_cam_control = tbsusb_read_cam_control;
+	state->ca.write_cam_control = tbsusb_write_cam_control;
+	state->ca.slot_reset = tbsusb_slot_reset;
+	state->ca.slot_shutdown = tbsusb_slot_shutdown;
+	state->ca.slot_ts_enable = tbsusb_slot_ts_enable;
+	state->ca.poll_slot_status = tbsusb_poll_slot_status;
+	state->ca.data = a->dev;
+
+	ret = dvb_ca_en50221_init(&a->dvb_adap, &state->ca, 0, 1);
+	if (ret)
+		goto err;
+
+	ret = tbsusb_poll_slot_status(&state->ca, 0, 0);
+	if (!ret)
+		ret = tbsusb_set_video_port(&state->ca, 0, 0);
+	if (ret < 0) {
+		dvb_ca_en50221_release(&state->ca);
+		goto err;
+	}
+	if (ret)
+		deb_info("CI initialized.");
+	return 0;
+
+err:	err("Cannot initialize CI: Error %d.", ret);
+	memset(&state->ca, 0, sizeof(state->ca));
+	return ret;
+}
+
+static const struct stv090x_config stv0903_config = {
+	.device			= STV0903,
+	.demod_mode		= STV090x_SINGLE,
+	.clk_mode		= STV090x_CLK_EXT,
+
+	.xtal			= 27000000,
+	.address		= 0x6a,
+
+	.ts1_mode		= STV090x_TSMODE_DVBCI,
+	.ts2_mode		= STV090x_TSMODE_SERIAL_CONTINUOUS,
+
+	.repeater_level         = STV090x_RPTLEVEL_16,
+	.adc1_range		= STV090x_ADC_2Vpp,
+
+	.tuner_get_frequency    = stb6100_get_frequency,
+	.tuner_set_frequency    = stb6100_set_frequency,
+	.tuner_set_bandwidth    = stb6100_set_bandwidth,
+	.tuner_get_bandwidth    = stb6100_get_bandwidth,
+
+	.set_lock_led		= tbsusb_led_ctrl,
+};
+
+static const struct stv090x_config stv0900_config = {
+	.device			= STV0900,
+	.demod_mode		= STV090x_SINGLE,
+	.clk_mode		= STV090x_CLK_EXT,
+
+	.xtal			= 27000000,
+	.address		= 0x68,
+
+	.ts1_mode		= STV090x_TSMODE_DVBCI,
+	.ts2_mode		= STV090x_TSMODE_SERIAL_CONTINUOUS,
+
+	.repeater_level         = STV090x_RPTLEVEL_16,
+	.adc1_range		= STV090x_ADC_2Vpp,
+
+	.tuner_get_frequency    = stb6100_get_frequency,
+	.tuner_set_frequency    = stb6100_set_frequency,
+	.tuner_set_bandwidth    = stb6100_set_bandwidth,
+	.tuner_get_bandwidth    = stb6100_get_bandwidth,
+
+	.set_lock_led		= tbsusb_led_ctrl,
+};
+
+static const struct tda10071_config tda10071_config = {
+	.demod_i2c_addr = 0x55, /* (0xaa >> 1) */
+	.tuner_i2c_addr = 0x14,
+	.i2c_wr_max     = 64,
+	.ts_mode        = TDA10071_TS_PARALLEL,
+	.spec_inv       = 0,
+	.xtal           = 40444000, /* 40.444 MHz */
+	.pll_multiplier = 20,
+	.set_lock_led   = tbsusb_led_ctrl,
+};
+
+static const struct cx24116_config cx24116_config = {
+	.demod_address   = 0x55,
+	.mpg_clk_pos_pol = 0x01,
+	.set_lock_led    = tbsusb_led_ctrl,
+};
+
+static const struct stv0288_config stv0288_config = {
+	.demod_address = 0x68,
+	.set_lock_led  = tbsusb_led_ctrl,
+};
+
+static int tbsusb_frontend_attach(struct dvb_usb_adapter *d)
+{
+	static u8 msg1[2] = {0x07, 0x01};
+	static u8 msg2[2] = {0x01, 0x01};
+	static u8 msg3[2] = {0x06, 0x01};
+	u8 *txt;
+	int ret = -ENODEV;
+
+	if (!d || !d->dev || !d->dev->desc) {
+		d->fe_adap[0].fe = NULL;
+		goto err;
+	}
+
+	switch (d->dev->desc->cold_ids[0]->idProduct) {
+	case USB_PID_TENOW_TBS5980:
+	case USB_PID_TENOW_TBS5920:
+		d->fe_adap[0].fe = dvb_attach(stv090x_attach, &stv0903_config,
+				&d->dev->i2c_adap, STV090x_DEMODULATOR_0);
+		break;
+	case USB_PID_TENOW_TBS5925:
+		d->fe_adap[0].fe = dvb_attach(stv090x_attach, &stv0900_config,
+				&d->dev->i2c_adap, STV090x_DEMODULATOR_0);
+		break;
+	case USB_PID_TENOW_TBS5928:
+		d->fe_adap[0].fe = dvb_attach(cx24116_attach, &cx24116_config,
+				&d->dev->i2c_adap);
+		break;
+	case USB_PID_TENOW_TBS5910:
+		d->fe_adap[0].fe = dvb_attach(stv0288_attach, &stv0288_config,
+				&d->dev->i2c_adap);
+		break;
+	case USB_PID_TENOW_TBS5921:
+		d->fe_adap[0].fe = dvb_attach(tda10071_attach, &tda10071_config,
+				&d->dev->i2c_adap);
+	}
+
+	if (!d->fe_adap[0].fe)
+		goto err;
+
+	d->fe_adap[0].fe->ops.set_voltage = tbsusb_set_voltage;
+
+	switch (d->dev->desc->cold_ids[0]->idProduct) {
+	case USB_PID_TENOW_TBS5980:
+		txt = "Attached stv0903!";
+		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg1, 2);
+		if (ret)
+			goto err;
+		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg2, 2);
+		if (ret)
+			goto err;
+		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg3, 2);
+		if (ret)
+			goto err;
+		ret = tbsusbci_init(d);
+		if (ret)
+			goto err;
+		break;
+	case USB_PID_TENOW_TBS5921:
+		txt = "Attached tda10071!";
+		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg1, 2);
+		if (ret)
+			goto err;
+		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg2, 2);
+		if (ret)
+			goto err;
+		break;
+	case USB_PID_TENOW_TBS5925:
+		txt = "Attached stv0900!";
+		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg3, 2);
+		if (ret)
+			goto err;
+		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg2, 2);
+		if (ret)
+			goto err;
+		goto wr0701;
+	case USB_PID_TENOW_TBS5920:
+		txt = "Attached stv0903!";
+		goto wr0701;
+	case USB_PID_TENOW_TBS5928:
+		txt = "Attached cx24116!";
+		goto wr0701;
+	case USB_PID_TENOW_TBS5910:
+		txt = "Attached stv0288!";
+wr0701:		ret = tbsusb_op_w(d->dev, 0x8a, 0, msg1, 2);
+		if (ret)
+			goto err;
+		break;
+	}
+
+	deb_info(txt);
+
+err:	if (ret && d->fe_adap[0].fe) {
+		dvb_frontend_detach(d->fe_adap[0].fe);
+		d->fe_adap[0].fe = NULL;
+	}
+
+	return ret;
+}
+
+static struct stb6100_config stb6100_config = {
+	.tuner_address  = 0x60,
+	.refclock       = 27000000,
+};
+
+static int tbsusb_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	int ret = -EIO;
+
+	if (!adap || !adap->dev || !adap->dev->desc || !adap->fe_adap[0].fe)
+		return -ENODEV;
+
+	switch (adap->dev->desc->cold_ids[0]->idProduct) {
+	case USB_PID_TENOW_TBS5980:
+	case USB_PID_TENOW_TBS5925:
+	case USB_PID_TENOW_TBS5920:
+		if (!dvb_attach(stb6100_attach, adap->fe_adap[0].fe,
+				&stb6100_config, &adap->dev->i2c_adap))
+			goto err;
+		else
+			ret = 0;
+		deb_info("Attached stb6100!");
+		if (adap->dev->desc->cold_ids[0]->idProduct !=
+							USB_PID_TENOW_TBS5925)
+			break;
+		/* call the init function once to initialize
+		   tuner's clock output divider and demod's
+		   master clock */
+		if (adap->fe_adap[0].fe->ops.init)
+			adap->fe_adap[0].fe->ops.init(adap->fe_adap[0].fe);
+		break;
+	case USB_PID_TENOW_TBS5910:
+		if (!dvb_attach(stb6000_attach, adap->fe_adap[0].fe, 0x61,
+			&adap->dev->i2c_adap))
+			goto err;
+		else
+			ret = 0;
+		deb_info("Attached stb6000!");
+		break;
+	}
+
+err:	return ret;
+}
+
+enum tbsusb_index {
+	TBS5980_INDEX = 0,
+	TBS5925_INDEX,
+	TBS5920_INDEX,
+	TBS5928_INDEX,
+	TBS5910_INDEX,
+	TBS5921_INDEX,
+	TBSMAX_INDEX
+};
+
+static struct usb_device_id tbsusb_table[] = {
+	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5980)},
+	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5925)},
+	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5920)},
+	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5928)},
+	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5910)},
+	{USB_DEVICE(USB_VID_TENOW, USB_PID_TENOW_TBS5921)},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, tbsusb_table);
+
+#define TBSUSB_DEVICE_PROPERTIES(priv, fw, intvl, tunerproc, devname, index) { \
+	.caps         = DVB_USB_IS_AN_I2C_ADAPTER, \
+	.usb_ctrl     = DEVICE_SPECIFIC, \
+	.size_of_priv = sizeof(struct priv), \
+	.firmware     = fw, \
+	.no_reconnect = 1, \
+	.i2c_algo     = &tbsusb_i2c_algo, \
+	.rc.legacy    = { \
+		.rc_map_table = tbsusb_rc_keys, \
+		.rc_map_size  = ARRAY_SIZE(tbsusb_rc_keys), \
+		.rc_interval  = intvl, \
+		.rc_query     = tbsusb_rc_query, \
+	}, \
+	.generic_bulk_ctrl_endpoint = 0x81, \
+	.num_adapters               = 1, \
+	.download_firmware          = tbsusb_load_firmware, \
+	.read_mac_address           = tbsusb_read_mac_address, \
+	.adapter                    = { { \
+		.num_frontends = 1, \
+		.fe            = { { \
+			.frontend_attach = tbsusb_frontend_attach, \
+			.streaming_ctrl  = NULL, \
+			.tuner_attach    = tunerproc, \
+			.stream          = { \
+				.type     = USB_BULK, \
+				.count    = 8, \
+				.endpoint = 0x82, \
+				.u        = { \
+					.bulk = { \
+						.buffersize = 4096, \
+					} \
+				} \
+			}, \
+		} }, \
+	} }, \
+	.num_device_descs = 1, \
+	.devices          = { { \
+		.name     = devname, \
+		.cold_ids = {&tbsusb_table[index], NULL}, \
+		.warm_ids = {NULL}, \
+	} } \
+}
+
+static struct dvb_usb_device_properties tbsusb_properties[] = {
+	TBSUSB_DEVICE_PROPERTIES(tbsusbci_state, "dvb-usb-tbsqbox-id5980.fw",
+		450, tbsusb_tuner_attach, "TBS Qbox DVB-S2 CI USB2.0 (TBS5980)",
+		TBS5980_INDEX),
+	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5925.fw",
+		250, tbsusb_tuner_attach, "TBS 5925 DVB-S2 USB2.0",
+		TBS5925_INDEX),
+	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5920.fw",
+		150, tbsusb_tuner_attach, "TBS QBOX2 DVBS USB2.0 (TBS5920)",
+		TBS5920_INDEX),
+	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5928.fw",
+		150, NULL, "TBS QBOXS2 DVBS2 USB2.0 (TBS5928)",
+		TBS5928_INDEX),
+	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5910.fw",
+		150, tbsusb_tuner_attach, "TBS QBOX DVBS USB2.0 (TBS5910)",
+		TBS5910_INDEX),
+	TBSUSB_DEVICE_PROPERTIES(tbsusb_state, "dvb-usb-tbsqbox-id5921.fw",
+		150, NULL, "TBS QBOXS3 DVBS2 USB2.0 (TBS5921)",
+		TBS5921_INDEX)
+};
+
+static int tbsusb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	int i;
+
+	for (i = 0; i < TBSMAX_INDEX; i++)
+		if (!dvb_usb_device_init(intf, &tbsusb_properties[i],
+						THIS_MODULE, NULL, adapter_nr))
+			return 0;
+	return -ENODEV;
+}
+
+static void tbsusb_usb_disconnect(struct usb_interface *dev)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(dev);
+
+	if (d && d->desc && d->desc->cold_ids[0]->idProduct ==
+							USB_PID_TENOW_TBS5980)
+		tbsusbci_uninit(d);
+	dvb_usb_device_exit(dev);
+}
+
+static struct usb_driver tbsusb_driver = {
+	.name       = "tbsusb",
+	.probe      = tbsusb_probe,
+	.disconnect = tbsusb_usb_disconnect,
+	.id_table   = tbsusb_table,
+};
+
+module_usb_driver(tbsusb_driver);
+
+MODULE_AUTHOR("Various Authors");
+MODULE_DESCRIPTION("TBS 5980/5928/5925/5921/5920/5910 USB2.0 DVB-S/S2 driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/tbs-usb.h b/drivers/media/usb/dvb-usb/tbs-usb.h
new file mode 100644
index 0000000..2a0a112
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/tbs-usb.h
@@ -0,0 +1,9 @@
+#ifndef _DVB_USB_TBSUSB_H_
+#define _DVB_USB_TBSUSB_H_
+
+#define DVB_USB_LOG_PREFIX "tbsusb"
+#include "dvb-usb.h"
+
+#define deb_xfer(args...) dprintk(dvb_usb_tbsusb_debug, 0x02, args)
+#define deb_info(args...) dprintk(dvb_usb_tbsusb_debug, 0x01, args)
+#endif
-- 
2.1.4