You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1335 lines
34 KiB

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
@@ -70,6 +70,7 @@
#define USB_VID_TECHNISAT 0x14f7
#define USB_VID_HAMA 0x147f
#define USB_VID_MICROSOFT 0x045e
+#define USB_VID_TENOW 0x734c
/* Product IDs */
#define USB_PID_ADSTECH_USB2_COLD 0xa333
@@ -388,4 +389,10 @@
#define USB_PID_EVOLVEO_XTRATV_STICK 0xa115
#define USB_PID_HAMA_DVBT_HYBRID 0x2758
#define USB_PID_XBOX_ONE_TUNER 0x02d5
+#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
@@ -39,6 +39,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;
@@ -533,6 +539,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
@@ -41,6 +41,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
@@ -3559,6 +3559,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;
}
@@ -3907,6 +3910,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;
@@ -4138,6 +4144,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 tuner_i2c_addr;
+ /* Hook for Lock LED */
+ void (*set_lock_led)(struct dvb_frontend *fe, int offon);
+
/* Max bytes I2C provider can write at once.
* Note: Buffer is taken from the stack currently!
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
@@ -320,3 +320,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
@@ -76,6 +76,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