[1/3] TBS USB drivers (DVB-S/S2) - basic driver

Message ID 1395865966.23074.60.camel@host028-server-9.lan.domdv.de (mailing list archive)
State Changes Requested, archived
Headers

Commit Message

Andreas Steinmetz March 26, 2014, 8:32 p.m. UTC
  [Please CC me on replies, I'm not subscribed]

The patch adds a driver for TBS USB DVB-S/S2 devices for which complete
GPLv2 code exists. Code was taken from:

http://www.tbsdtv.com/download/
https://bitbucket.org/CrazyCat/linux-tbs-drivers/
https://bitbucket.org/updatelee/v4l-updatelee

Supported devices:
------------------

TBS5980 - complete open source by manufacturer, current device
TBS5928 - complete open source by manufacturer, old device
TBS5920 - complete open source by manufacturer, old device
TBS5910 - complete open source by manufacturer, old device
TBS5925 - open source by manufacturer except 13V/18V control,
          voltage switching of TBS5980 however works,
          current device
TBS5921 - open source from CrazyCat's bitbucket repository,
          old device

Unsupported devices:
--------------------

TBS5922 - mostly closed source, current device
TBS5990 - mostly closed source, current device
TBS???? with USB PID 0x2601 - very old device for which the actual
hardware used couldn't be determined.

General:
--------

I do not have any manufacturer relationship. I'm just a user of TBS5980
and TBS5925 which work quite well as far as the hardware is concerned.

I'm sufficiently annoyed, however, by the manufacturer's driver build
system which actually is an old v4l tree replacing the kernel's current
v4l tree. Thus I have reworked all available open source code for the
TBS USB DVB-S/S2 hardware into a combined driver that is working with
the current kernel.

Testing:
--------

TBS5925 gets used daily and is working fine.
TBS5980 gets used daily minus an actual CAM and is working fine without
a CAM.

The old devices can't be tested by me, I don't own them and they are not
sold anymore.

Maintenance:
------------

As a TBS5925/TBS5980 user I'm willing to maintain the driver as far as
this is possible (see below).

Regarding the older devices I'm willing to fix bugs for owners of these
devices as long as I did introduce them in my combined driver.

What I cannot do for any device is to fix any problems that would
require manufacturer support or manufacturer documentation.

As the TBS5980 manufacturer sources on which this driver is based didn't
change for more than one and a half years (since when I started to
collect manufacturer source archives) I'm quite confident that not much
maintenance will be necessary.

Firmware:
---------

All required firmware except for the TBS5921 is available at
http://www.tbsdtv.com/download/ (manufacturer website). The tda10071
firmware required additionally for the TBS5921 is already supported by
the get_dvb_firmware script of the kernel.

Due to the way the manufacturer handles software distribution I can't
include the TBS firmware in the get_dvb_firmware script (no archive url,
archive files updated every few months under a different file name).

Notes:
------

The ifdefs in the driver will go away by a followup patch after a
necessary patch to some frontends.

Please go on easy with me. I'm usually not writing kernel drivers. And
I'm neither young nor healthy enough for flame wars.
  

Comments

Mauro Carvalho Chehab Nov. 5, 2014, 9:16 a.m. UTC | #1
Hi Andreas,

(c/c the others that are listed as the authors of the TBS driver)

Em Wed, 26 Mar 2014 21:32:46 +0100
Andreas Steinmetz <ast@domdv.de> escreveu:

> [Please CC me on replies, I'm not subscribed]
> 
> The patch adds a driver for TBS USB DVB-S/S2 devices for which complete
> GPLv2 code exists. Code was taken from:
> 
> http://www.tbsdtv.com/download/
> https://bitbucket.org/CrazyCat/linux-tbs-drivers/
> https://bitbucket.org/updatelee/v4l-updatelee

We have already a driver for the chipsets used on this driver.
So, instead of adding a new driver, you should work on adding support
on the existing ones.
> 
> Supported devices:
> ------------------
> 
> TBS5980 - complete open source by manufacturer, current device
> TBS5928 - complete open source by manufacturer, old device
> TBS5920 - complete open source by manufacturer, old device
> TBS5910 - complete open source by manufacturer, old device
> TBS5925 - open source by manufacturer except 13V/18V control,
>           voltage switching of TBS5980 however works,
>           current device
> TBS5921 - open source from CrazyCat's bitbucket repository,
>           old device
> 
> Unsupported devices:
> --------------------
> 
> TBS5922 - mostly closed source, current device
> TBS5990 - mostly closed source, current device
> TBS???? with USB PID 0x2601 - very old device for which the actual
> hardware used couldn't be determined.
> 
> General:
> --------
> 
> I do not have any manufacturer relationship. I'm just a user of TBS5980
> and TBS5925 which work quite well as far as the hardware is concerned.
> 
> I'm sufficiently annoyed, however, by the manufacturer's driver build
> system which actually is an old v4l tree replacing the kernel's current
> v4l tree. Thus I have reworked all available open source code for the
> TBS USB DVB-S/S2 hardware into a combined driver that is working with
> the current kernel.
> 
> Testing:
> --------
> 
> TBS5925 gets used daily and is working fine.
> TBS5980 gets used daily minus an actual CAM and is working fine without
> a CAM.
> 
> The old devices can't be tested by me, I don't own them and they are not
> sold anymore.
> 
> Maintenance:
> ------------
> 
> As a TBS5925/TBS5980 user I'm willing to maintain the driver as far as
> this is possible (see below).
> 
> Regarding the older devices I'm willing to fix bugs for owners of these
> devices as long as I did introduce them in my combined driver.
> 
> What I cannot do for any device is to fix any problems that would
> require manufacturer support or manufacturer documentation.
> 
> As the TBS5980 manufacturer sources on which this driver is based didn't
> change for more than one and a half years (since when I started to
> collect manufacturer source archives) I'm quite confident that not much
> maintenance will be necessary.
> 
> Firmware:
> ---------
> 
> All required firmware except for the TBS5921 is available at
> http://www.tbsdtv.com/download/ (manufacturer website). The tda10071
> firmware required additionally for the TBS5921 is already supported by
> the get_dvb_firmware script of the kernel.
> 
> Due to the way the manufacturer handles software distribution I can't
> include the TBS firmware in the get_dvb_firmware script (no archive url,
> archive files updated every few months under a different file name).
> 
> Notes:
> ------
> 
> The ifdefs in the driver will go away by a followup patch after a
> necessary patch to some frontends.
> 
> Please go on easy with me. I'm usually not writing kernel drivers. And
> I'm neither young nor healthy enough for flame wars.
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
  

Patch

Signed-off-by: Andreas Steinmetz <ast@domdv.de>

diff -rNup v4l-dvb.orig/drivers/media/dvb-core/dvb-usb-ids.h v4l-dvb/drivers/media/dvb-core/dvb-usb-ids.h
--- v4l-dvb.orig/drivers/media/dvb-core/dvb-usb-ids.h	2014-03-26 15:03:13.399501288 +0100
+++ v4l-dvb/drivers/media/dvb-core/dvb-usb-ids.h	2014-03-26 15:19:08.658682532 +0100
@@ -70,6 +70,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
@@ -375,4 +376,10 @@ 
 #define USB_PID_CTVDIGDUAL_V2				0xe410
 #define USB_PID_PCTV_2002E                              0x025c
 #define USB_PID_PCTV_2002E_SE                           0x025d
+#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 -rNup v4l-dvb.orig/drivers/media/usb/dvb-usb/Kconfig v4l-dvb/drivers/media/usb/dvb-usb/Kconfig
--- v4l-dvb.orig/drivers/media/usb/dvb-usb/Kconfig	2014-03-26 14:00:26.134339135 +0100
+++ v4l-dvb/drivers/media/usb/dvb-usb/Kconfig	2014-03-26 15:23:38.585581745 +0100
@@ -317,3 +317,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 -rNup v4l-dvb.orig/drivers/media/usb/dvb-usb/Makefile v4l-dvb/drivers/media/usb/dvb-usb/Makefile
--- v4l-dvb.orig/drivers/media/usb/dvb-usb/Makefile	2014-03-26 14:00:26.134339135 +0100
+++ v4l-dvb/drivers/media/usb/dvb-usb/Makefile	2014-03-26 15:20:11.362891410 +0100
@@ -76,6 +76,9 @@  obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-
 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 -rNup v4l-dvb.orig/drivers/media/usb/dvb-usb/tbs-usb.h v4l-dvb/drivers/media/usb/dvb-usb/tbs-usb.h
--- v4l-dvb.orig/drivers/media/usb/dvb-usb/tbs-usb.h	1970-01-01 01:00:00.000000000 +0100
+++ v4l-dvb/drivers/media/usb/dvb-usb/tbs-usb.h	2014-03-26 15:25:34.793968908 +0100
@@ -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
diff -rNup v4l-dvb.orig/drivers/media/usb/dvb-usb/tbs-usb.c v4l-dvb/drivers/media/usb/dvb-usb/tbs-usb.c
--- v4l-dvb.orig/drivers/media/usb/dvb-usb/tbs-usb.c	1970-01-01 01:00:00.000000000 +0100
+++ v4l-dvb/drivers/media/usb/dvb-usb/tbs-usb.c	2014-03-26 17:04:52.861267173 +0100
@@ -0,0 +1,1089 @@ 
+/*
+ * 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);
+}
+
+#ifdef TBS_LOCKLED
+
+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);
+}
+
+#endif
+
+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,
+
+#ifdef TBS_LOCKLED
+	.set_lock_led		= tbsusb_led_ctrl,
+#endif
+};
+
+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,
+
+#ifdef TBS_LOCKLED
+	.set_lock_led		= tbsusb_led_ctrl,
+#endif
+};
+
+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,
+#ifdef TBS_LOCKLED
+	.set_lock_led   = tbsusb_led_ctrl,
+#endif
+};
+
+static const struct cx24116_config cx24116_config = {
+	.demod_address   = 0x55,
+	.mpg_clk_pos_pol = 0x01,
+#ifdef TBS_LOCKLED
+	.set_lock_led    = tbsusb_led_ctrl,
+#endif
+};
+
+static const struct stv0288_config stv0288_config = {
+	.demod_address = 0x68,
+#ifdef TBS_LOCKLED
+	.set_lock_led  = tbsusb_led_ctrl,
+#endif
+};
+
+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");