@@ -413,6 +413,17 @@ config DVB_USB_MXL111SF
help
Say Y here to support the MxL111SF USB2.0 DTV receiver.
+config DVB_USB_TBSDTB08
+ tristate "TBS-Tech ISDB-T Full Seg DTB08 USB2.0 support"
+ depends on DVB_USB
+ select DVB_MB86A20S
+ select MEDIA_TUNER_TDA18271 if !MEDIA_TUNER_CUSTOMISE
+ help
+ Say Y here to support the TBS-Tech Full Seg DTB08 ISDB-T USB2.0
+ receiver.
+
+ The receiver is also known as JH Full HD Digital TV Receiver.
+
config DVB_USB_RTL28XXU
tristate "Realtek RTL28xxU DVB USB support"
depends on DVB_USB && EXPERIMENTAL
@@ -107,6 +107,9 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += dvb-usb-mxl111sf.o
obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-demod.o
obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o
+dvb-usb-tbsdtb08-objs = tbs-dtb08.o tbs-dtb08-fe.o
+obj-$(CONFIG_DVB_USB_TBSDTB08) += dvb-usb-tbsdtb08.o
+
dvb-usb-rtl28xxu-objs = rtl28xxu.o
obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o
new file mode 100644
@@ -0,0 +1,451 @@
+/*
+ * TBS-Tech ISDB-T Full Seg DTB08 device driver
+ *
+ * Copyright (C) 2010-2012 Manoel Pinheiro <pinusdtv@hotmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+
+#define DVB_USB_LOG_PREFIX "tbs_dtb08"
+
+#include "dvb-usb.h"
+#include "tda18271.h"
+#include "tbs-dtb08-fe.h"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off module debugging (default:0).");
+
+#define dbg_info(format, args...) do { \
+ if (debug) \
+ info(format, ## args); \
+} while (0)
+
+#define DEMOD_I2C_ADDR 0x20
+#define TUNER_I2C_ADDR 0xc0
+
+#ifndef USB_PID_TBS_DTB08
+#define USB_PID_TBS_DTB08 0xdb08
+#endif
+
+#define USB_VID_TBS_734C 0x734c
+
+#define TBS_DTB08_LED_CONTROL 5
+
+#define FX2_IE_EX0 7
+#define FX2_EX0_ENABLE 1
+#define FX2_EX0_DISABLE 0
+#define FX2_I2CTL 6
+#define I2CTL_100Khz 0
+#define I2CTL_400Khz 1
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+static DEFINE_MUTEX(tbs_dtb08_usb_mutex);
+static int fx2_renum = -1;
+
+int tbs_dtb08_generic_read_addr(struct usb_device *udev, u8 req,
+ u16 addr, u8 *data, u16 len)
+{
+ int ret;
+
+ ret = usb_control_msg(udev,
+ usb_rcvctrlpipe(udev, 0),
+ req,
+ USB_TYPE_VENDOR | USB_DIR_IN,
+ addr, 0, data, len, 2000);
+ if (ret < 0)
+ err("%s: ret=%d", __func__, ret);
+
+ return ret;
+}
+
+int tbs_dtb08_generic_read(struct usb_device *udev,
+ u8 req, u8 *data, u16 len)
+{
+ return tbs_dtb08_generic_read_addr(udev, req, 0, data, len);
+}
+
+int tbs_dtb08_generic_write_addr(struct usb_device *udev, u8 req,
+ u16 addr, u8 *data, u16 len)
+{
+ int ret;
+
+ ret = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ req,
+ USB_TYPE_VENDOR | USB_DIR_OUT,
+ addr, 0, data, len, 2000);
+ if (ret < 0)
+ err("%s: ret=%d", __func__, ret);
+
+ return ret;
+}
+
+int tbs_dtb08_generic_write(struct usb_device *udev,
+ u8 req, u8 *data, u16 len)
+{
+ return tbs_dtb08_generic_write_addr(udev, req, 0, data, len);
+}
+
+static int tbs_dtb08_i2c_busy(struct usb_device *udev)
+{
+ u8 val;
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ int ret = tbs_dtb08_generic_read(udev, 0x81, &val, 1);
+ if (ret >= 0 && val == 0)
+ return 0;
+ msleep(1);
+ }
+
+ return 1;
+}
+
+static int tbs_dtb08_i2c_read(struct usb_device *udev, u8 addr,
+ u8 reg, u8 *data, u8 len)
+{
+ int ret;
+ u8 obuf[3];
+
+ if (len < 1) {
+ err("%s: len less than 1 bytes. Makes no sense.", __func__);
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&tbs_dtb08_usb_mutex);
+ ret = tbs_dtb08_i2c_busy(udev);
+ if (ret == 0) {
+ obuf[0] = len;
+ obuf[1] = addr;
+ obuf[2] = reg;
+
+ ret = tbs_dtb08_generic_write(udev, 0x90, &obuf[0], 3);
+ if (ret >= 0) {
+ ret = tbs_dtb08_i2c_busy(udev);
+ if (ret == 0)
+ ret = tbs_dtb08_generic_read(udev, 0x91,
+ data, len);
+ }
+ }
+ mutex_unlock(&tbs_dtb08_usb_mutex);
+
+ return ret;
+}
+
+static int tbs_dtb08_i2c_write(struct usb_device *udev, u8 addr,
+ u8 reg, u8 *data, u8 len)
+{
+ int i, ret;
+ u8 *buf;
+
+ if (len < 1) {
+ err("%s: len less than 1 bytes. Makes no sense.", __func__);
+ return -EOPNOTSUPP;
+ }
+ if (len > 61) {
+ err("%s: len more than 61 bytes. Not supported.", __func__);
+ return -EOPNOTSUPP;
+ }
+
+ buf = kmalloc(len + 3 , GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mutex_lock(&tbs_dtb08_usb_mutex);
+
+ buf[0] = len + 2;
+ buf[1] = addr;
+ buf[2] = reg;
+
+ for (i = 0; i < len; i++)
+ buf[i+3] = data[i];
+
+ ret = tbs_dtb08_i2c_busy(udev);
+ if (ret < 0)
+ goto ret_err;
+
+ ret = tbs_dtb08_generic_write(udev, 0x80, buf, len + 3);
+
+ret_err:
+ kfree(buf);
+ mutex_unlock(&tbs_dtb08_usb_mutex);
+ return ret;
+}
+
+static int tbs_dtb08_send_cmd_8a(struct usb_device *udev, u8 val1, u8 val2)
+{
+ int ret;
+ u8 obuf[2] = { val1, val2 };
+
+ ret = tbs_dtb08_generic_write(udev, 0x8a, &obuf[0], 2);
+
+ return (ret < 0) ? ret : 0;
+}
+
+int tbs_dtb08_fx2_ie_ex0(struct usb_device *udev, u8 enable)
+{
+ return tbs_dtb08_send_cmd_8a(udev, FX2_IE_EX0, enable ? 1 : 0);
+}
+
+static int tbs_dtb08_led_control(struct usb_device *udev, int onoff)
+{
+ return tbs_dtb08_send_cmd_8a(udev, TBS_DTB08_LED_CONTROL,
+ onoff ? 1 : 0);
+}
+
+int tbs_dtb08_fx2_i2ctl(struct usb_device *udev, u8 i2cfreq)
+{
+ return tbs_dtb08_send_cmd_8a(udev, FX2_I2CTL, i2cfreq);
+}
+
+static int tbs_dtb08_i2c_transfer(struct i2c_adapter *adap,
+ struct i2c_msg msg[], int num)
+{
+ struct dvb_usb_device *dev = i2c_get_adapdata(adap);
+ int ret = 0;
+
+ dbg_info("%s: num=%02x, msg[0].addr=%02x, jiffies=%ld",
+ __func__, num, msg[0].addr, jiffies);
+
+ if (!dev || !dev->udev)
+ return -ENODEV;
+
+ if (mutex_lock_interruptible(&dev->i2c_mutex) < 0)
+ return -EAGAIN;
+
+ switch (num) {
+ case 2:
+ ret = tbs_dtb08_i2c_read(dev->udev, msg[0].addr, msg[0].buf[0],
+ msg[1].buf, msg[1].len);
+ break;
+ case 1:
+ if (msg[0].flags == 0)
+ ret = tbs_dtb08_i2c_write(dev->udev, msg[0].addr,
+ msg[0].buf[0],
+ &msg[0].buf[1],
+ msg[0].len - 1);
+ else
+ ret = tbs_dtb08_i2c_read(dev->udev, msg[0].addr,
+ msg[0].buf[0],
+ msg[0].buf, msg[0].len);
+ break;
+ default:
+ err("%s:num == %d, not supported!", __func__, num);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ mutex_unlock(&dev->i2c_mutex);
+
+ return (ret < 0) ? ret : num;
+}
+
+static struct tda18271_std_map a20s_tda18271_config = {
+ .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4,
+ .if_lvl = 7, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config tbs_dtb08_tda18271_config = {
+ .std_map = &a20s_tda18271_config,
+ .gate = TDA18271_GATE_DIGITAL,
+};
+
+static int tbs_dtb08_tuner_attach(struct dvb_usb_adapter *adap)
+{
+ int ret;
+ struct dtb08_a20s_state *state = adap->fe_adap[0].priv;
+
+ if (fx2_renum < 0)
+ return -EINVAL;
+
+ if (adap == NULL || adap->fe_adap[0].fe == NULL)
+ return -ENODEV;
+
+ if (adap->fe_adap[0].fe->ops.tuner_ops.init != NULL)
+ return 0;
+
+ state->tuner_ctrl = true;
+ ret = dvb_attach(tda18271_attach, adap->fe_adap[0].fe,
+ TUNER_I2C_ADDR, &adap->dev->i2c_adap,
+ &tbs_dtb08_tda18271_config) == NULL ? -ENODEV : 0;
+ state->tuner_ctrl = false;
+ return ret;
+}
+
+static struct dtb08_a20s_reg_subreg_config dtb08_a20s_config_regs[] = {
+ { 0x3C, 0x00, 0x38 },
+ { 0x04, 0x00, 0x001e },
+ { 0x04, 0x0E, 0x0032 },
+ { 0x04, 0x15, 0x55 },
+ { 0x04, 0x16, 0x00 },
+ { 0x28, 0x20, 0x33dd00 },
+ { 0x28, 0x6A, 0x0002f0 },
+ { 0x28, 0x74, 0x0001f4 },
+ { 0x50, 0xD5, 0x00 },
+ { 0x50, 0xD6, 0x17 },
+ { 0x50, 0xDC, 0x3fff },
+ { 0x50, 0xDE, 0x3fff },
+ { 0x50, 0xE0, 0x3fff }
+};
+
+static struct mb86a20s_config mb86a20s_cfg = {
+ .demod_address = DEMOD_I2C_ADDR,
+};
+
+static int tbs_dtb08_frontend_attach(struct dvb_usb_adapter *adap)
+{
+ int ret;
+ struct dvb_usb_device *dev = adap->dev;
+ struct dtb08_a20s_state *state = adap->fe_adap[0].priv;
+
+ if (fx2_renum < 0)
+ return -EINVAL;
+
+ if (state == NULL) {
+ err("%s: adap->dev->priv == NULL!", __func__);
+ return -ENOMEM;
+ }
+
+ tbs_dtb08_fx2_ie_ex0(dev->udev, FX2_EX0_DISABLE);
+ tbs_dtb08_fx2_i2ctl(dev->udev, I2CTL_400Khz);
+
+ state->udev = dev->udev;
+ state->demod_addr = DEMOD_I2C_ADDR;
+ state->config_size = ARRAY_SIZE(dtb08_a20s_config_regs);
+ state->config_regs = dtb08_a20s_config_regs;
+ state->i2c_read = tbs_dtb08_i2c_read;
+ state->i2c_write = tbs_dtb08_i2c_write;
+ state->led_control = tbs_dtb08_led_control;
+
+ ret = dtb08_a20s_frontend_attach(state, adap, &mb86a20s_cfg);
+ if (ret != 0)
+ return ret;
+
+ if (tbs_dtb08_tuner_attach(adap) == 0) {
+ tbs_dtb08_fx2_ie_ex0(dev->udev, FX2_EX0_ENABLE);
+ return 0;
+ }
+
+ dvb_frontend_detach(adap->fe_adap[0].fe);
+ adap->fe_adap[0].fe = NULL;
+
+ return -ENODEV;
+}
+
+static int tbs_dtb08_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+ return tbs_dtb08_led_control(adap->dev->udev, onoff);
+}
+
+static u32 tbs_dtb08_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm tbs_dtb08_i2c_algo = {
+ .master_xfer = tbs_dtb08_i2c_transfer,
+ .functionality = tbs_dtb08_i2c_func,
+};
+
+static struct usb_device_id tbs_dtb08_table[] = {
+ { USB_DEVICE(USB_VID_TBS_734C, USB_PID_TBS_DTB08) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, tbs_dtb08_table);
+
+static struct dvb_usb_device_properties tbs_dtb08_properties = {
+ .caps = DVB_USB_IS_AN_I2C_ADAPTER,
+ .usb_ctrl = CYPRESS_FX2,
+ .firmware = "dvb-usb-tbs-dtb08.fw",
+ .no_reconnect = 1,
+ .num_adapters = 1,
+ .adapter = {{
+ .num_frontends = 1,
+ .fe = { {
+ .size_of_priv = sizeof(struct dtb08_a20s_state),
+ .streaming_ctrl = tbs_dtb08_streaming_ctrl,
+ .frontend_attach = tbs_dtb08_frontend_attach,
+ .tuner_attach = tbs_dtb08_tuner_attach,
+ .stream = {
+ .type = USB_BULK,
+ .count = 8,
+ .endpoint = 0x82,
+ .u = {
+ .bulk = {
+ .buffersize = 4096,
+ }
+ }
+ },
+ } } }
+ },
+
+ .i2c_algo = &tbs_dtb08_i2c_algo,
+
+ .num_device_descs = 1,
+ .devices = {
+ {
+ .name = "TBS-Tech ISDB-T USB 2.0 (DTB08)",
+ .cold_ids = { &tbs_dtb08_table[0], NULL },
+ .warm_ids = { NULL },
+ },
+ },
+};
+
+static int tbs_dtb08_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret;
+ u8 val;
+ struct usb_device *udev = interface_to_usbdev(intf);
+
+ dbg_info("%s: %d, num_altsetting=%d", __func__,
+ intf->cur_altsetting->desc.bInterfaceNumber,
+ intf->num_altsetting);
+
+ fx2_renum = tbs_dtb08_generic_read(udev, 0x81, &val, 1);
+
+ ret = dvb_usb_device_init(intf, &tbs_dtb08_properties,
+ THIS_MODULE, NULL, adapter_nr);
+
+ if (ret != 0) {
+ err("%s: failed err=%d", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void tbs_dtb08_usb_disconnect(struct usb_interface *intf)
+{
+ fx2_renum = -1;
+ dvb_usb_device_exit(intf);
+}
+
+static struct usb_driver tbs_dtb08_driver = {
+ .name = "dvb-usb-tbsdtb08",
+ .probe = tbs_dtb08_probe,
+ .disconnect = tbs_dtb08_usb_disconnect,
+ .id_table = tbs_dtb08_table,
+};
+
+module_usb_driver(tbs_dtb08_driver);
+
+MODULE_AUTHOR("Manoel Pinheiro <pinusdtv@hotmail.com>");
+MODULE_DESCRIPTION("Driver for TBS-Tech ISDB-T USB2.0 Receiver (DTB08 Full Seg)");
+MODULE_LICENSE("GPL");