From ab154471b0ec5137bf86b3f45b5644e72c18c3ad Mon Sep 17 00:00:00 2001 From: Athanasios Oikonomou Date: Sun, 7 Feb 2016 16:09:19 +0200 Subject: [PATCH] Support TBS USB drivers 3.9 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 399e104..16382e8 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_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 @@ -368,4 +369,10 @@ #define USB_PID_TECHNISAT_USB2_HDCI_V2 0x0002 #define USB_PID_TECHNISAT_AIRSTAR_TELESTICK_2 0x0004 #define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 +#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 2916d7c..462265c 100644 --- a/drivers/media/dvb-frontends/cx24116.c +++ b/drivers/media/dvb-frontends/cx24116.c @@ -703,6 +703,9 @@ static int cx24116_read_status(struct dvb_frontend *fe, fe_status_t *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; } @@ -1111,6 +1114,8 @@ 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); } @@ -1196,6 +1201,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 7d90ab9..fa90dfe 100644 --- a/drivers/media/dvb-frontends/cx24116.h +++ b/drivers/media/dvb-frontends/cx24116.h @@ -33,6 +33,9 @@ struct cx24116_config { /* Need to reset device during firmware loading */ int (*reset_device)(struct dvb_frontend *fe); + /* Hook for Lock LED */ + void (*set_lock_led)(struct dvb_frontend *fe, int offon); + /* Need to set MPEG parameters */ u8 mpg_clk_pos_pol:0x02; diff --git a/drivers/media/dvb-frontends/stv0288.c b/drivers/media/dvb-frontends/stv0288.c index 632b251..8ffd6d1 100644 --- a/drivers/media/dvb-frontends/stv0288.c +++ b/drivers/media/dvb-frontends/stv0288.c @@ -381,6 +381,9 @@ static int stv0288_read_status(struct dvb_frontend *fe, fe_status_t *status) dprintk("stv0288 has locked\n"); } + if (state->config->set_lock_led) + state->config->set_lock_led(fe, *status & FE_HAS_LOCK); + return 0; } @@ -415,6 +418,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,8 @@ 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 f2b53db..5aaa39f 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 defined(CONFIG_DVB_STV0288) || (defined(CONFIG_DVB_STV0288_MODULE) && \ diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c index f36eeef..970375a 100644 --- a/drivers/media/dvb-frontends/stv090x.c +++ b/drivers/media/dvb-frontends/stv090x.c @@ -3536,6 +3536,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; } @@ -3883,6 +3886,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; @@ -4112,6 +4118,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 0bd6adc..7842487 100644 --- a/drivers/media/dvb-frontends/stv090x.h +++ b/drivers/media/dvb-frontends/stv090x.h @@ -101,6 +101,9 @@ struct stv090x_config { int (*tuner_set_refclk) (struct dvb_frontend *fe, u32 refclk); int (*tuner_get_status) (struct dvb_frontend *fe, u32 *status); void (*tuner_i2c_lock) (struct dvb_frontend *fe, int lock); + + /* Hook for Lock LED */ + void (*set_lock_led)(struct dvb_frontend *fe, int offon); }; #if IS_ENABLED(CONFIG_DVB_STV090x) diff --git a/drivers/media/dvb-frontends/tda10071.c b/drivers/media/dvb-frontends/tda10071.c index 2521f7e..45eb231 100644 --- a/drivers/media/dvb-frontends/tda10071.c +++ b/drivers/media/dvb-frontends/tda10071.c @@ -483,6 +483,9 @@ static int tda10071_read_status(struct dvb_frontend *fe, fe_status_t *status) if (tmp & 0x08) /* RS or BCH */ *status |= FE_HAS_SYNC | FE_HAS_LOCK; + if (priv->cfg.set_lock_led) + priv->cfg.set_lock_led(fe, *status & FE_HAS_LOCK); + priv->fe_status = *status; return ret; @@ -1151,6 +1154,9 @@ static int tda10071_sleep(struct dvb_frontend *fe) goto error; } + if (priv->cfg.set_lock_led) + priv->cfg.set_lock_led(fe, 0); + cmd.args[0] = CMD_SET_SLEEP_MODE; cmd.args[1] = 0; cmd.args[2] = 1; @@ -1185,6 +1191,8 @@ static int tda10071_get_tune_settings(struct dvb_frontend *fe, static void tda10071_release(struct dvb_frontend *fe) { struct tda10071_priv *priv = fe->demodulator_priv; + if (priv->cfg.set_lock_led) + priv->cfg.set_lock_led(fe, 0); kfree(priv); } diff --git a/drivers/media/dvb-frontends/tda10071.h b/drivers/media/dvb-frontends/tda10071.h index bff1c38..ce3a624 100644 --- a/drivers/media/dvb-frontends/tda10071.h +++ b/drivers/media/dvb-frontends/tda10071.h @@ -68,6 +68,9 @@ struct tda10071_config { * Values: */ u8 pll_multiplier; + + /* Hook for Lock LED */ + void (*set_lock_led)(struct dvb_frontend *fe, int offon); }; diff --git a/drivers/media/usb/dvb-usb/Kconfig b/drivers/media/usb/dvb-usb/Kconfig index c5d9566..9a327fb 100644 --- a/drivers/media/usb/dvb-usb/Kconfig +++ b/drivers/media/usb/dvb-usb/Kconfig @@ -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 --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 + * Copyright (c) 2014 Andreas Steinmetz + * 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 +#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