diff options
Diffstat (limited to 'drivers/media/usb/dvb-usb-v2')
42 files changed, 18060 insertions, 0 deletions
diff --git a/drivers/media/usb/dvb-usb-v2/Kconfig b/drivers/media/usb/dvb-usb-v2/Kconfig new file mode 100644 index 000000000000..834bfecbed73 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/Kconfig @@ -0,0 +1,149 @@ +config DVB_USB_V2 + tristate "Support for various USB DVB devices v2" + depends on DVB_CORE && USB && I2C && RC_CORE + help + By enabling this you will be able to choose the various supported + USB1.1 and USB2.0 DVB devices. + + Almost every USB device needs a firmware, please look into + <file:Documentation/dvb/README.dvb-usb>. + + For a complete list of supported USB devices see the LinuxTV DVB Wiki: + <http://www.linuxtv.org/wiki/index.php/DVB_USB> + + Say Y if you own a USB DVB device. + +config DVB_USB_CYPRESS_FIRMWARE + tristate "Cypress firmware helper routines" + depends on DVB_USB_V2 + +config DVB_USB_AF9015 + tristate "Afatech AF9015 DVB-T USB2.0 support" + depends on DVB_USB_V2 + select DVB_AF9013 + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MC44S803 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18218 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL5007T if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the Afatech AF9015 based DVB-T USB2.0 receiver + +config DVB_USB_AF9035 + tristate "Afatech AF9035 DVB-T USB2.0 support" + depends on DVB_USB_V2 + select DVB_AF9033 + select MEDIA_TUNER_TUA9001 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_FC0011 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL5007T if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18218 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_FC2580 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the Afatech AF9035 based DVB USB receiver. + +config DVB_USB_ANYSEE + tristate "Anysee DVB-T/C USB2.0 support" + depends on DVB_USB_V2 + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6423 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CXD2820R if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the Anysee E30, Anysee E30 Plus or + Anysee E30 C Plus DVB USB2.0 receiver. + +config DVB_USB_AU6610 + tristate "Alcor Micro AU6610 USB2.0 support" + depends on DVB_USB_V2 + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the Sigmatek DVB-110 DVB-T USB2.0 receiver. + +config DVB_USB_AZ6007 + tristate "AzureWave 6007 and clones DVB-T/C USB2.0 support" + depends on DVB_USB_V2 + select DVB_USB_CYPRESS_FIRMWARE + select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2063 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the AZ6007 receivers like Terratec H7. + +config DVB_USB_CE6230 + tristate "Intel CE6230 DVB-T USB2.0 support" + depends on DVB_USB_V2 + select DVB_ZL10353 + select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the Intel CE6230 DVB-T USB2.0 receiver + +config DVB_USB_EC168 + tristate "E3C EC168 DVB-T USB2.0 support" + depends on DVB_USB_V2 + select DVB_EC100 + select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the E3C EC168 DVB-T USB2.0 receiver. + +config DVB_USB_GL861 + tristate "Genesys Logic GL861 USB2.0 support" + depends on DVB_USB_V2 + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the MSI Megasky 580 (55801) DVB-T USB2.0 + receiver with USB ID 0db0:5581. + +config DVB_USB_IT913X + tristate "ITE IT913X DVB-T USB2.0 support" + depends on DVB_USB_V2 + select DVB_IT913X_FE + help + Say Y here to support the ITE IT913X DVB-T USB2.0 + +config DVB_USB_LME2510 + tristate "LME DM04/QQBOX DVB-S USB2.0 support" + depends on DVB_USB_V2 + select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_IX2505V if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_M88RS2000 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the LME DM04/QQBOX DVB-S USB2.0 + +config DVB_USB_MXL111SF + tristate "MxL111SF DTV USB2.0 support" + depends on DVB_USB_V2 + select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LG2160 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TVEEPROM + help + Say Y here to support the MxL111SF USB2.0 DTV receiver. + +config DVB_USB_RTL28XXU + tristate "Realtek RTL28xxU DVB USB support" + depends on DVB_USB_V2 && EXPERIMENTAL + select DVB_RTL2830 + select DVB_RTL2832 + select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_FC0012 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_FC0013 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_E4000 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_FC2580 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the Realtek RTL28xxU DVB USB receiver. + diff --git a/drivers/media/usb/dvb-usb-v2/Makefile b/drivers/media/usb/dvb-usb-v2/Makefile new file mode 100644 index 000000000000..b76f58e6c64f --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/Makefile @@ -0,0 +1,49 @@ +dvb_usb_v2-objs := dvb_usb_core.o dvb_usb_urb.o usb_urb.o +obj-$(CONFIG_DVB_USB_V2) += dvb_usb_v2.o + +dvb_usb_cypress_firmware-objs := cypress_firmware.o +obj-$(CONFIG_DVB_USB_CYPRESS_FIRMWARE) += dvb_usb_cypress_firmware.o + +dvb-usb-af9015-objs := af9015.o +obj-$(CONFIG_DVB_USB_AF9015) += dvb-usb-af9015.o + +dvb-usb-af9035-objs := af9035.o +obj-$(CONFIG_DVB_USB_AF9035) += dvb-usb-af9035.o + +dvb-usb-anysee-objs := anysee.o +obj-$(CONFIG_DVB_USB_ANYSEE) += dvb-usb-anysee.o + +dvb-usb-au6610-objs := au6610.o +obj-$(CONFIG_DVB_USB_AU6610) += dvb-usb-au6610.o + +dvb-usb-az6007-objs := az6007.o +obj-$(CONFIG_DVB_USB_AZ6007) += dvb-usb-az6007.o + +dvb-usb-ce6230-objs := ce6230.o +obj-$(CONFIG_DVB_USB_CE6230) += dvb-usb-ce6230.o + +dvb-usb-ec168-objs := ec168.o +obj-$(CONFIG_DVB_USB_EC168) += dvb-usb-ec168.o + +dvb-usb-it913x-objs := it913x.o +obj-$(CONFIG_DVB_USB_IT913X) += dvb-usb-it913x.o + +dvb-usb-lmedm04-objs := lmedm04.o +obj-$(CONFIG_DVB_USB_LME2510) += dvb-usb-lmedm04.o + +dvb-usb-gl861-objs := gl861.o +obj-$(CONFIG_DVB_USB_GL861) += dvb-usb-gl861.o + +dvb-usb-mxl111sf-objs += mxl111sf.o mxl111sf-phy.o mxl111sf-i2c.o +dvb-usb-mxl111sf-objs += mxl111sf-gpio.o +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-rtl28xxu-objs := rtl28xxu.o +obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o + +ccflags-y += -I$(srctree)/drivers/media/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb-frontends +ccflags-y += -I$(srctree)/drivers/media/tuners + diff --git a/drivers/media/usb/dvb-usb-v2/af9015.c b/drivers/media/usb/dvb-usb-v2/af9015.c new file mode 100644 index 000000000000..824f1911ee21 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/af9015.c @@ -0,0 +1,1454 @@ +/* + * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver + * + * Copyright (C) 2007 Antti Palosaari <crope@iki.fi> + * + * Thanks to Afatech who kindly provided information. + * + * 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 "af9015.h" + +static int dvb_usb_af9015_remote; +module_param_named(remote, dvb_usb_af9015_remote, int, 0644); +MODULE_PARM_DESC(remote, "select remote"); +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int af9015_ctrl_msg(struct dvb_usb_device *d, struct req_t *req) +{ +#define BUF_LEN 63 +#define REQ_HDR_LEN 8 /* send header size */ +#define ACK_HDR_LEN 2 /* rece header size */ + struct af9015_state *state = d_to_priv(d); + int ret, wlen, rlen; + u8 buf[BUF_LEN]; + u8 write = 1; + + buf[0] = req->cmd; + buf[1] = state->seq++; + buf[2] = req->i2c_addr; + buf[3] = req->addr >> 8; + buf[4] = req->addr & 0xff; + buf[5] = req->mbox; + buf[6] = req->addr_len; + buf[7] = req->data_len; + + switch (req->cmd) { + case GET_CONFIG: + case READ_MEMORY: + case RECONNECT_USB: + write = 0; + break; + case READ_I2C: + write = 0; + buf[2] |= 0x01; /* set I2C direction */ + case WRITE_I2C: + buf[0] = READ_WRITE_I2C; + break; + case WRITE_MEMORY: + if (((req->addr & 0xff00) == 0xff00) || + ((req->addr & 0xff00) == 0xae00)) + buf[0] = WRITE_VIRTUAL_MEMORY; + case WRITE_VIRTUAL_MEMORY: + case COPY_FIRMWARE: + case DOWNLOAD_FIRMWARE: + case BOOT: + break; + default: + dev_err(&d->udev->dev, "%s: unknown command=%d\n", + KBUILD_MODNAME, req->cmd); + ret = -EIO; + goto error; + } + + /* buffer overflow check */ + if ((write && (req->data_len > BUF_LEN - REQ_HDR_LEN)) || + (!write && (req->data_len > BUF_LEN - ACK_HDR_LEN))) { + dev_err(&d->udev->dev, "%s: too much data; cmd=%d len=%d\n", + KBUILD_MODNAME, req->cmd, req->data_len); + ret = -EINVAL; + goto error; + } + + /* write receives seq + status = 2 bytes + read receives seq + status + data = 2 + N bytes */ + wlen = REQ_HDR_LEN; + rlen = ACK_HDR_LEN; + if (write) { + wlen += req->data_len; + memcpy(&buf[REQ_HDR_LEN], req->data, req->data_len); + } else { + rlen += req->data_len; + } + + /* no ack for these packets */ + if (req->cmd == DOWNLOAD_FIRMWARE || req->cmd == RECONNECT_USB) + rlen = 0; + + ret = dvb_usbv2_generic_rw(d, buf, wlen, buf, rlen); + if (ret) + goto error; + + /* check status */ + if (rlen && buf[1]) { + dev_err(&d->udev->dev, "%s: command failed=%d\n", + KBUILD_MODNAME, buf[1]); + ret = -EIO; + goto error; + } + + /* read request, copy returned data to return buf */ + if (!write) + memcpy(req->data, &buf[ACK_HDR_LEN], req->data_len); +error: + return ret; +} + +static int af9015_write_regs(struct dvb_usb_device *d, u16 addr, u8 *val, + u8 len) +{ + struct req_t req = {WRITE_MEMORY, AF9015_I2C_DEMOD, addr, 0, 0, len, + val}; + return af9015_ctrl_msg(d, &req); +} + +static int af9015_read_regs(struct dvb_usb_device *d, u16 addr, u8 *val, u8 len) +{ + struct req_t req = {READ_MEMORY, AF9015_I2C_DEMOD, addr, 0, 0, len, + val}; + return af9015_ctrl_msg(d, &req); +} + +static int af9015_write_reg(struct dvb_usb_device *d, u16 addr, u8 val) +{ + return af9015_write_regs(d, addr, &val, 1); +} + +static int af9015_read_reg(struct dvb_usb_device *d, u16 addr, u8 *val) +{ + return af9015_read_regs(d, addr, val, 1); +} + +static int af9015_write_reg_i2c(struct dvb_usb_device *d, u8 addr, u16 reg, + u8 val) +{ + struct af9015_state *state = d_to_priv(d); + struct req_t req = {WRITE_I2C, addr, reg, 1, 1, 1, &val}; + + if (addr == state->af9013_config[0].i2c_addr || + addr == state->af9013_config[1].i2c_addr) + req.addr_len = 3; + + return af9015_ctrl_msg(d, &req); +} + +static int af9015_read_reg_i2c(struct dvb_usb_device *d, u8 addr, u16 reg, + u8 *val) +{ + struct af9015_state *state = d_to_priv(d); + struct req_t req = {READ_I2C, addr, reg, 0, 1, 1, val}; + + if (addr == state->af9013_config[0].i2c_addr || + addr == state->af9013_config[1].i2c_addr) + req.addr_len = 3; + + return af9015_ctrl_msg(d, &req); +} + +static int af9015_do_reg_bit(struct dvb_usb_device *d, u16 addr, u8 bit, u8 op) +{ + int ret; + u8 val, mask = 0x01; + + ret = af9015_read_reg(d, addr, &val); + if (ret) + return ret; + + mask <<= bit; + if (op) { + /* set bit */ + val |= mask; + } else { + /* clear bit */ + mask ^= 0xff; + val &= mask; + } + + return af9015_write_reg(d, addr, val); +} + +static int af9015_set_reg_bit(struct dvb_usb_device *d, u16 addr, u8 bit) +{ + return af9015_do_reg_bit(d, addr, bit, 1); +} + +static int af9015_clear_reg_bit(struct dvb_usb_device *d, u16 addr, u8 bit) +{ + return af9015_do_reg_bit(d, addr, bit, 0); +} + +static int af9015_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct af9015_state *state = d_to_priv(d); + int ret = 0, i = 0; + u16 addr; + u8 uninitialized_var(mbox), addr_len; + struct req_t req; + +/* +The bus lock is needed because there is two tuners both using same I2C-address. +Due to that the only way to select correct tuner is use demodulator I2C-gate. + +................................................ +. AF9015 includes integrated AF9013 demodulator. +. ____________ ____________ . ____________ +.| uC | | demod | . | tuner | +.|------------| |------------| . |------------| +.| AF9015 | | AF9013/5 | . | MXL5003 | +.| |--+----I2C-------|-----/ -----|-.-----I2C-------| | +.| | | | addr 0x38 | . | addr 0xc6 | +.|____________| | |____________| . |____________| +.................|.............................. + | ____________ ____________ + | | demod | | tuner | + | |------------| |------------| + | | AF9013 | | MXL5003 | + +----I2C-------|-----/ -----|-------I2C-------| | + | addr 0x3a | | addr 0xc6 | + |____________| |____________| +*/ + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + while (i < num) { + if (msg[i].addr == state->af9013_config[0].i2c_addr || + msg[i].addr == state->af9013_config[1].i2c_addr) { + addr = msg[i].buf[0] << 8; + addr += msg[i].buf[1]; + mbox = msg[i].buf[2]; + addr_len = 3; + } else { + addr = msg[i].buf[0]; + addr_len = 1; + /* mbox is don't care in that case */ + } + + if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { + if (msg[i].len > 3 || msg[i+1].len > 61) { + ret = -EOPNOTSUPP; + goto error; + } + if (msg[i].addr == state->af9013_config[0].i2c_addr) + req.cmd = READ_MEMORY; + else + req.cmd = READ_I2C; + req.i2c_addr = msg[i].addr; + req.addr = addr; + req.mbox = mbox; + req.addr_len = addr_len; + req.data_len = msg[i+1].len; + req.data = &msg[i+1].buf[0]; + ret = af9015_ctrl_msg(d, &req); + i += 2; + } else if (msg[i].flags & I2C_M_RD) { + if (msg[i].len > 61) { + ret = -EOPNOTSUPP; + goto error; + } + if (msg[i].addr == state->af9013_config[0].i2c_addr) { + ret = -EINVAL; + goto error; + } + req.cmd = READ_I2C; + req.i2c_addr = msg[i].addr; + req.addr = addr; + req.mbox = mbox; + req.addr_len = addr_len; + req.data_len = msg[i].len; + req.data = &msg[i].buf[0]; + ret = af9015_ctrl_msg(d, &req); + i += 1; + } else { + if (msg[i].len > 21) { + ret = -EOPNOTSUPP; + goto error; + } + if (msg[i].addr == state->af9013_config[0].i2c_addr) + req.cmd = WRITE_MEMORY; + else + req.cmd = WRITE_I2C; + req.i2c_addr = msg[i].addr; + req.addr = addr; + req.mbox = mbox; + req.addr_len = addr_len; + req.data_len = msg[i].len-addr_len; + req.data = &msg[i].buf[addr_len]; + ret = af9015_ctrl_msg(d, &req); + i += 1; + } + if (ret) + goto error; + + } + ret = i; + +error: + mutex_unlock(&d->i2c_mutex); + + return ret; +} + +static u32 af9015_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm af9015_i2c_algo = { + .master_xfer = af9015_i2c_xfer, + .functionality = af9015_i2c_func, +}; + +static int af9015_identify_state(struct dvb_usb_device *d, const char **name) +{ + int ret; + u8 reply; + struct req_t req = {GET_CONFIG, 0, 0, 0, 0, 1, &reply}; + + ret = af9015_ctrl_msg(d, &req); + if (ret) + return ret; + + dev_dbg(&d->udev->dev, "%s: reply=%02x\n", __func__, reply); + + if (reply == 0x02) + ret = WARM; + else + ret = COLD; + + return ret; +} + +static int af9015_download_firmware(struct dvb_usb_device *d, + const struct firmware *fw) +{ + struct af9015_state *state = d_to_priv(d); + int i, len, remaining, ret; + struct req_t req = {DOWNLOAD_FIRMWARE, 0, 0, 0, 0, 0, NULL}; + u16 checksum = 0; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* calc checksum */ + for (i = 0; i < fw->size; i++) + checksum += fw->data[i]; + + state->firmware_size = fw->size; + state->firmware_checksum = checksum; + + #define FW_ADDR 0x5100 /* firmware start address */ + #define LEN_MAX 55 /* max packet size */ + for (remaining = fw->size; remaining > 0; remaining -= LEN_MAX) { + len = remaining; + if (len > LEN_MAX) + len = LEN_MAX; + + req.data_len = len; + req.data = (u8 *) &fw->data[fw->size - remaining]; + req.addr = FW_ADDR + fw->size - remaining; + + ret = af9015_ctrl_msg(d, &req); + if (ret) { + dev_err(&d->udev->dev, + "%s: firmware download failed=%d\n", + KBUILD_MODNAME, ret); + goto error; + } + } + + /* firmware loaded, request boot */ + req.cmd = BOOT; + req.data_len = 0; + ret = af9015_ctrl_msg(d, &req); + if (ret) { + dev_err(&d->udev->dev, "%s: firmware boot failed=%d\n", + KBUILD_MODNAME, ret); + goto error; + } + +error: + return ret; +} + +/* hash (and dump) eeprom */ +static int af9015_eeprom_hash(struct dvb_usb_device *d) +{ + struct af9015_state *state = d_to_priv(d); + int ret, i; + static const unsigned int AF9015_EEPROM_SIZE = 256; + u8 buf[AF9015_EEPROM_SIZE]; + struct req_t req = {READ_I2C, AF9015_I2C_EEPROM, 0, 0, 1, 1, NULL}; + + /* read eeprom */ + for (i = 0; i < AF9015_EEPROM_SIZE; i++) { + req.addr = i; + req.data = &buf[i]; + ret = af9015_ctrl_msg(d, &req); + if (ret < 0) + goto err; + } + + /* calculate checksum */ + for (i = 0; i < AF9015_EEPROM_SIZE / sizeof(u32); i++) { + state->eeprom_sum *= GOLDEN_RATIO_PRIME_32; + state->eeprom_sum += le32_to_cpu(((u32 *)buf)[i]); + } + + for (i = 0; i < AF9015_EEPROM_SIZE; i += 16) + dev_dbg(&d->udev->dev, "%s: %*ph\n", __func__, 16, buf + i); + + dev_dbg(&d->udev->dev, "%s: eeprom sum=%.8x\n", + __func__, state->eeprom_sum); + return 0; +err: + dev_err(&d->udev->dev, "%s: eeprom failed=%d\n", KBUILD_MODNAME, ret); + return ret; +} + +static int af9015_read_config(struct dvb_usb_device *d) +{ + struct af9015_state *state = d_to_priv(d); + int ret; + u8 val, i, offset = 0; + struct req_t req = {READ_I2C, AF9015_I2C_EEPROM, 0, 0, 1, 1, &val}; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* IR remote controller */ + req.addr = AF9015_EEPROM_IR_MODE; + /* first message will timeout often due to possible hw bug */ + for (i = 0; i < 4; i++) { + ret = af9015_ctrl_msg(d, &req); + if (!ret) + break; + } + if (ret) + goto error; + + ret = af9015_eeprom_hash(d); + if (ret) + goto error; + + state->ir_mode = val; + dev_dbg(&d->udev->dev, "%s: IR mode=%d\n", __func__, val); + + /* TS mode - one or two receivers */ + req.addr = AF9015_EEPROM_TS_MODE; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + + state->dual_mode = val; + dev_dbg(&d->udev->dev, "%s: TS mode=%d\n", __func__, state->dual_mode); + + /* disable 2nd adapter because we don't have PID-filters */ + if (d->udev->speed == USB_SPEED_FULL) + state->dual_mode = 0; + + if (state->dual_mode) { + /* read 2nd demodulator I2C address */ + req.addr = AF9015_EEPROM_DEMOD2_I2C; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + + state->af9013_config[1].i2c_addr = val; + } + + for (i = 0; i < state->dual_mode + 1; i++) { + if (i == 1) + offset = AF9015_EEPROM_OFFSET; + /* xtal */ + req.addr = AF9015_EEPROM_XTAL_TYPE1 + offset; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + switch (val) { + case 0: + state->af9013_config[i].clock = 28800000; + break; + case 1: + state->af9013_config[i].clock = 20480000; + break; + case 2: + state->af9013_config[i].clock = 28000000; + break; + case 3: + state->af9013_config[i].clock = 25000000; + break; + }; + dev_dbg(&d->udev->dev, "%s: [%d] xtal=%d set clock=%d\n", + __func__, i, val, + state->af9013_config[i].clock); + + /* IF frequency */ + req.addr = AF9015_EEPROM_IF1H + offset; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + + state->af9013_config[i].if_frequency = val << 8; + + req.addr = AF9015_EEPROM_IF1L + offset; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + + state->af9013_config[i].if_frequency += val; + state->af9013_config[i].if_frequency *= 1000; + dev_dbg(&d->udev->dev, "%s: [%d] IF frequency=%d\n", __func__, + i, state->af9013_config[i].if_frequency); + + /* MT2060 IF1 */ + req.addr = AF9015_EEPROM_MT2060_IF1H + offset; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + state->mt2060_if1[i] = val << 8; + req.addr = AF9015_EEPROM_MT2060_IF1L + offset; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + state->mt2060_if1[i] += val; + dev_dbg(&d->udev->dev, "%s: [%d] MT2060 IF1=%d\n", __func__, i, + state->mt2060_if1[i]); + + /* tuner */ + req.addr = AF9015_EEPROM_TUNER_ID1 + offset; + ret = af9015_ctrl_msg(d, &req); + if (ret) + goto error; + switch (val) { + case AF9013_TUNER_ENV77H11D5: + case AF9013_TUNER_MT2060: + case AF9013_TUNER_QT1010: + case AF9013_TUNER_UNKNOWN: + case AF9013_TUNER_MT2060_2: + case AF9013_TUNER_TDA18271: + case AF9013_TUNER_QT1010A: + case AF9013_TUNER_TDA18218: + state->af9013_config[i].spec_inv = 1; + break; + case AF9013_TUNER_MXL5003D: + case AF9013_TUNER_MXL5005D: + case AF9013_TUNER_MXL5005R: + case AF9013_TUNER_MXL5007T: + state->af9013_config[i].spec_inv = 0; + break; + case AF9013_TUNER_MC44S803: + state->af9013_config[i].gpio[1] = AF9013_GPIO_LO; + state->af9013_config[i].spec_inv = 1; + break; + default: + dev_err(&d->udev->dev, "%s: tuner id=%d not " \ + "supported, please report!\n", + KBUILD_MODNAME, val); + return -ENODEV; + }; + + state->af9013_config[i].tuner = val; + dev_dbg(&d->udev->dev, "%s: [%d] tuner id=%d\n", + __func__, i, val); + } + +error: + if (ret) + dev_err(&d->udev->dev, "%s: eeprom read failed=%d\n", + KBUILD_MODNAME, ret); + + /* AverMedia AVerTV Volar Black HD (A850) device have bad EEPROM + content :-( Override some wrong values here. Ditto for the + AVerTV Red HD+ (A850T) device. */ + if (le16_to_cpu(d->udev->descriptor.idVendor) == USB_VID_AVERMEDIA && + ((le16_to_cpu(d->udev->descriptor.idProduct) == + USB_PID_AVERMEDIA_A850) || + (le16_to_cpu(d->udev->descriptor.idProduct) == + USB_PID_AVERMEDIA_A850T))) { + dev_dbg(&d->udev->dev, + "%s: AverMedia A850: overriding config\n", + __func__); + /* disable dual mode */ + state->dual_mode = 0; + + /* set correct IF */ + state->af9013_config[0].if_frequency = 4570000; + } + + return ret; +} + +static int af9015_get_stream_config(struct dvb_frontend *fe, u8 *ts_type, + struct usb_data_stream_properties *stream) +{ + struct dvb_usb_device *d = fe_to_d(fe); + dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, fe_to_adap(fe)->id); + + if (d->udev->speed == USB_SPEED_FULL) + stream->u.bulk.buffersize = TS_USB11_FRAME_SIZE; + + return 0; +} + +static int af9015_get_adapter_count(struct dvb_usb_device *d) +{ + struct af9015_state *state = d_to_priv(d); + return state->dual_mode + 1; +} + +/* override demod callbacks for resource locking */ +static int af9015_af9013_set_frontend(struct dvb_frontend *fe) +{ + int ret; + struct af9015_state *state = fe_to_priv(fe); + + if (mutex_lock_interruptible(&state->fe_mutex)) + return -EAGAIN; + + ret = state->set_frontend[fe_to_adap(fe)->id](fe); + + mutex_unlock(&state->fe_mutex); + + return ret; +} + +/* override demod callbacks for resource locking */ +static int af9015_af9013_read_status(struct dvb_frontend *fe, + fe_status_t *status) +{ + int ret; + struct af9015_state *state = fe_to_priv(fe); + + if (mutex_lock_interruptible(&state->fe_mutex)) + return -EAGAIN; + + ret = state->read_status[fe_to_adap(fe)->id](fe, status); + + mutex_unlock(&state->fe_mutex); + + return ret; +} + +/* override demod callbacks for resource locking */ +static int af9015_af9013_init(struct dvb_frontend *fe) +{ + int ret; + struct af9015_state *state = fe_to_priv(fe); + + if (mutex_lock_interruptible(&state->fe_mutex)) + return -EAGAIN; + + ret = state->init[fe_to_adap(fe)->id](fe); + + mutex_unlock(&state->fe_mutex); + + return ret; +} + +/* override demod callbacks for resource locking */ +static int af9015_af9013_sleep(struct dvb_frontend *fe) +{ + int ret; + struct af9015_state *state = fe_to_priv(fe); + + if (mutex_lock_interruptible(&state->fe_mutex)) + return -EAGAIN; + + ret = state->sleep[fe_to_adap(fe)->id](fe); + + mutex_unlock(&state->fe_mutex); + + return ret; +} + +/* override tuner callbacks for resource locking */ +static int af9015_tuner_init(struct dvb_frontend *fe) +{ + int ret; + struct af9015_state *state = fe_to_priv(fe); + + if (mutex_lock_interruptible(&state->fe_mutex)) + return -EAGAIN; + + ret = state->tuner_init[fe_to_adap(fe)->id](fe); + + mutex_unlock(&state->fe_mutex); + + return ret; +} + +/* override tuner callbacks for resource locking */ +static int af9015_tuner_sleep(struct dvb_frontend *fe) +{ + int ret; + struct af9015_state *state = fe_to_priv(fe); + + if (mutex_lock_interruptible(&state->fe_mutex)) + return -EAGAIN; + + ret = state->tuner_sleep[fe_to_adap(fe)->id](fe); + + mutex_unlock(&state->fe_mutex); + + return ret; +} + +static int af9015_copy_firmware(struct dvb_usb_device *d) +{ + struct af9015_state *state = d_to_priv(d); + int ret; + u8 fw_params[4]; + u8 val, i; + struct req_t req = {COPY_FIRMWARE, 0, 0x5100, 0, 0, sizeof(fw_params), + fw_params }; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + fw_params[0] = state->firmware_size >> 8; + fw_params[1] = state->firmware_size & 0xff; + fw_params[2] = state->firmware_checksum >> 8; + fw_params[3] = state->firmware_checksum & 0xff; + + /* wait 2nd demodulator ready */ + msleep(100); + + ret = af9015_read_reg_i2c(d, state->af9013_config[1].i2c_addr, + 0x98be, &val); + if (ret) + goto error; + else + dev_dbg(&d->udev->dev, "%s: firmware status=%02x\n", + __func__, val); + + if (val == 0x0c) /* fw is running, no need for download */ + goto exit; + + /* set I2C master clock to fast (to speed up firmware copy) */ + ret = af9015_write_reg(d, 0xd416, 0x04); /* 0x04 * 400ns */ + if (ret) + goto error; + + msleep(50); + + /* copy firmware */ + ret = af9015_ctrl_msg(d, &req); + if (ret) + dev_err(&d->udev->dev, "%s: firmware copy cmd failed=%d\n", + KBUILD_MODNAME, ret); + + dev_dbg(&d->udev->dev, "%s: firmware copy done\n", __func__); + + /* set I2C master clock back to normal */ + ret = af9015_write_reg(d, 0xd416, 0x14); /* 0x14 * 400ns */ + if (ret) + goto error; + + /* request boot firmware */ + ret = af9015_write_reg_i2c(d, state->af9013_config[1].i2c_addr, + 0xe205, 1); + dev_dbg(&d->udev->dev, "%s: firmware boot cmd status=%d\n", + __func__, ret); + if (ret) + goto error; + + for (i = 0; i < 15; i++) { + msleep(100); + + /* check firmware status */ + ret = af9015_read_reg_i2c(d, state->af9013_config[1].i2c_addr, + 0x98be, &val); + dev_dbg(&d->udev->dev, "%s: firmware status cmd status=%d " \ + "firmware status=%02x\n", __func__, ret, val); + if (ret) + goto error; + + if (val == 0x0c || val == 0x04) /* success or fail */ + break; + } + + if (val == 0x04) { + dev_err(&d->udev->dev, "%s: firmware did not run\n", + KBUILD_MODNAME); + ret = -ETIMEDOUT; + } else if (val != 0x0c) { + dev_err(&d->udev->dev, "%s: firmware boot timeout\n", + KBUILD_MODNAME); + ret = -ETIMEDOUT; + } + +error: +exit: + return ret; +} + +static int af9015_af9013_frontend_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct af9015_state *state = adap_to_priv(adap); + + if (adap->id == 0) { + state->af9013_config[0].ts_mode = AF9013_TS_USB; + memcpy(state->af9013_config[0].api_version, "\x0\x1\x9\x0", 4); + state->af9013_config[0].gpio[0] = AF9013_GPIO_HI; + state->af9013_config[0].gpio[3] = AF9013_GPIO_TUNER_ON; + } else if (adap->id == 1) { + state->af9013_config[1].ts_mode = AF9013_TS_SERIAL; + memcpy(state->af9013_config[1].api_version, "\x0\x1\x9\x0", 4); + state->af9013_config[1].gpio[0] = AF9013_GPIO_TUNER_ON; + state->af9013_config[1].gpio[1] = AF9013_GPIO_LO; + + /* copy firmware to 2nd demodulator */ + if (state->dual_mode) { + ret = af9015_copy_firmware(adap_to_d(adap)); + if (ret) { + dev_err(&adap_to_d(adap)->udev->dev, + "%s: firmware copy to 2nd " \ + "frontend failed, will " \ + "disable it\n", KBUILD_MODNAME); + state->dual_mode = 0; + return -ENODEV; + } + } else { + return -ENODEV; + } + } + + /* attach demodulator */ + adap->fe[0] = dvb_attach(af9013_attach, + &state->af9013_config[adap->id], &adap_to_d(adap)->i2c_adap); + + /* + * AF9015 firmware does not like if it gets interrupted by I2C adapter + * request on some critical phases. During normal operation I2C adapter + * is used only 2nd demodulator and tuner on dual tuner devices. + * Override demodulator callbacks and use mutex for limit access to + * those "critical" paths to keep AF9015 happy. + */ + if (adap->fe[0]) { + state->set_frontend[adap->id] = + adap->fe[0]->ops.set_frontend; + adap->fe[0]->ops.set_frontend = + af9015_af9013_set_frontend; + + state->read_status[adap->id] = + adap->fe[0]->ops.read_status; + adap->fe[0]->ops.read_status = + af9015_af9013_read_status; + + state->init[adap->id] = adap->fe[0]->ops.init; + adap->fe[0]->ops.init = af9015_af9013_init; + + state->sleep[adap->id] = adap->fe[0]->ops.sleep; + adap->fe[0]->ops.sleep = af9015_af9013_sleep; + } + + return adap->fe[0] == NULL ? -ENODEV : 0; +} + +static struct mt2060_config af9015_mt2060_config = { + .i2c_address = 0xc0, + .clock_out = 0, +}; + +static struct qt1010_config af9015_qt1010_config = { + .i2c_address = 0xc4, +}; + +static struct tda18271_config af9015_tda18271_config = { + .gate = TDA18271_GATE_DIGITAL, + .small_i2c = TDA18271_16_BYTE_CHUNK_INIT, +}; + +static struct mxl5005s_config af9015_mxl5003_config = { + .i2c_address = 0xc6, + .if_freq = IF_FREQ_4570000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_DEFAULT, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .AgcMasterByte = 0x00, +}; + +static struct mxl5005s_config af9015_mxl5005_config = { + .i2c_address = 0xc6, + .if_freq = IF_FREQ_4570000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_OFF, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .AgcMasterByte = 0x00, +}; + +static struct mc44s803_config af9015_mc44s803_config = { + .i2c_address = 0xc0, + .dig_out = 1, +}; + +static struct tda18218_config af9015_tda18218_config = { + .i2c_address = 0xc0, + .i2c_wr_max = 21, /* max wr bytes AF9015 I2C adap can handle at once */ +}; + +static struct mxl5007t_config af9015_mxl5007t_config = { + .xtal_freq_hz = MxL_XTAL_24_MHZ, + .if_freq_hz = MxL_IF_4_57_MHZ, +}; + +static int af9015_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct af9015_state *state = d_to_priv(d); + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + switch (state->af9013_config[adap->id].tuner) { + case AF9013_TUNER_MT2060: + case AF9013_TUNER_MT2060_2: + ret = dvb_attach(mt2060_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, &af9015_mt2060_config, + state->mt2060_if1[adap->id]) + == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_QT1010: + case AF9013_TUNER_QT1010A: + ret = dvb_attach(qt1010_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, + &af9015_qt1010_config) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_TDA18271: + ret = dvb_attach(tda18271_attach, adap->fe[0], 0xc0, + &adap_to_d(adap)->i2c_adap, + &af9015_tda18271_config) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_TDA18218: + ret = dvb_attach(tda18218_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, + &af9015_tda18218_config) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_MXL5003D: + ret = dvb_attach(mxl5005s_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, + &af9015_mxl5003_config) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_MXL5005D: + case AF9013_TUNER_MXL5005R: + ret = dvb_attach(mxl5005s_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, + &af9015_mxl5005_config) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_ENV77H11D5: + ret = dvb_attach(dvb_pll_attach, adap->fe[0], 0xc0, + &adap_to_d(adap)->i2c_adap, + DVB_PLL_TDA665X) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_MC44S803: + ret = dvb_attach(mc44s803_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, + &af9015_mc44s803_config) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_MXL5007T: + ret = dvb_attach(mxl5007t_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, + 0xc0, &af9015_mxl5007t_config) == NULL ? -ENODEV : 0; + break; + case AF9013_TUNER_UNKNOWN: + default: + dev_err(&d->udev->dev, "%s: unknown tuner id=%d\n", + KBUILD_MODNAME, + state->af9013_config[adap->id].tuner); + ret = -ENODEV; + } + + if (adap->fe[0]->ops.tuner_ops.init) { + state->tuner_init[adap->id] = + adap->fe[0]->ops.tuner_ops.init; + adap->fe[0]->ops.tuner_ops.init = af9015_tuner_init; + } + + if (adap->fe[0]->ops.tuner_ops.sleep) { + state->tuner_sleep[adap->id] = + adap->fe[0]->ops.tuner_ops.sleep; + adap->fe[0]->ops.tuner_ops.sleep = af9015_tuner_sleep; + } + + return ret; +} + +static int af9015_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) +{ + struct dvb_usb_device *d = adap_to_d(adap); + int ret; + dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff); + + if (onoff) + ret = af9015_set_reg_bit(d, 0xd503, 0); + else + ret = af9015_clear_reg_bit(d, 0xd503, 0); + + return ret; +} + +static int af9015_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, + int onoff) +{ + struct dvb_usb_device *d = adap_to_d(adap); + int ret; + u8 idx; + dev_dbg(&d->udev->dev, "%s: index=%d pid=%04x onoff=%d\n", + __func__, index, pid, onoff); + + ret = af9015_write_reg(d, 0xd505, (pid & 0xff)); + if (ret) + goto error; + + ret = af9015_write_reg(d, 0xd506, (pid >> 8)); + if (ret) + goto error; + + idx = ((index & 0x1f) | (1 << 5)); + ret = af9015_write_reg(d, 0xd504, idx); + +error: + return ret; +} + +static int af9015_init_endpoint(struct dvb_usb_device *d) +{ + struct af9015_state *state = d_to_priv(d); + int ret; + u16 frame_size; + u8 packet_size; + dev_dbg(&d->udev->dev, "%s: USB speed=%d\n", __func__, d->udev->speed); + + if (d->udev->speed == USB_SPEED_FULL) { + frame_size = TS_USB11_FRAME_SIZE/4; + packet_size = TS_USB11_MAX_PACKET_SIZE/4; + } else { + frame_size = TS_USB20_FRAME_SIZE/4; + packet_size = TS_USB20_MAX_PACKET_SIZE/4; + } + + ret = af9015_set_reg_bit(d, 0xd507, 2); /* assert EP4 reset */ + if (ret) + goto error; + ret = af9015_set_reg_bit(d, 0xd50b, 1); /* assert EP5 reset */ + if (ret) + goto error; + ret = af9015_clear_reg_bit(d, 0xdd11, 5); /* disable EP4 */ + if (ret) + goto error; + ret = af9015_clear_reg_bit(d, 0xdd11, 6); /* disable EP5 */ + if (ret) + goto error; + ret = af9015_set_reg_bit(d, 0xdd11, 5); /* enable EP4 */ + if (ret) + goto error; + if (state->dual_mode) { + ret = af9015_set_reg_bit(d, 0xdd11, 6); /* enable EP5 */ + if (ret) + goto error; + } + ret = af9015_clear_reg_bit(d, 0xdd13, 5); /* disable EP4 NAK */ + if (ret) + goto error; + if (state->dual_mode) { + ret = af9015_clear_reg_bit(d, 0xdd13, 6); /* disable EP5 NAK */ + if (ret) + goto error; + } + /* EP4 xfer length */ + ret = af9015_write_reg(d, 0xdd88, frame_size & 0xff); + if (ret) + goto error; + ret = af9015_write_reg(d, 0xdd89, frame_size >> 8); + if (ret) + goto error; + /* EP5 xfer length */ + ret = af9015_write_reg(d, 0xdd8a, frame_size & 0xff); + if (ret) + goto error; + ret = af9015_write_reg(d, 0xdd8b, frame_size >> 8); + if (ret) + goto error; + ret = af9015_write_reg(d, 0xdd0c, packet_size); /* EP4 packet size */ + if (ret) + goto error; + ret = af9015_write_reg(d, 0xdd0d, packet_size); /* EP5 packet size */ + if (ret) + goto error; + ret = af9015_clear_reg_bit(d, 0xd507, 2); /* negate EP4 reset */ + if (ret) + goto error; + if (state->dual_mode) { + ret = af9015_clear_reg_bit(d, 0xd50b, 1); /* negate EP5 reset */ + if (ret) + goto error; + } + + /* enable / disable mp2if2 */ + if (state->dual_mode) + ret = af9015_set_reg_bit(d, 0xd50b, 0); + else + ret = af9015_clear_reg_bit(d, 0xd50b, 0); + +error: + if (ret) + dev_err(&d->udev->dev, "%s: endpoint init failed=%d\n", + KBUILD_MODNAME, ret); + + return ret; +} + +static int af9015_init(struct dvb_usb_device *d) +{ + struct af9015_state *state = d_to_priv(d); + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + mutex_init(&state->fe_mutex); + + /* init RC canary */ + ret = af9015_write_reg(d, 0x98e9, 0xff); + if (ret) + goto error; + + ret = af9015_init_endpoint(d); + if (ret) + goto error; + +error: + return ret; +} + +struct af9015_rc_setup { + unsigned int id; + char *rc_codes; +}; + +static char *af9015_rc_setup_match(unsigned int id, + const struct af9015_rc_setup *table) +{ + for (; table->rc_codes; table++) + if (table->id == id) + return table->rc_codes; + return NULL; +} + +static const struct af9015_rc_setup af9015_rc_setup_modparam[] = { + { AF9015_REMOTE_A_LINK_DTU_M, RC_MAP_ALINK_DTU_M }, + { AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3, RC_MAP_MSI_DIGIVOX_II }, + { AF9015_REMOTE_MYGICTV_U718, RC_MAP_TOTAL_MEDIA_IN_HAND }, + { AF9015_REMOTE_DIGITTRADE_DVB_T, RC_MAP_DIGITTRADE }, + { AF9015_REMOTE_AVERMEDIA_KS, RC_MAP_AVERMEDIA_RM_KS }, + { } +}; + +static const struct af9015_rc_setup af9015_rc_setup_hashes[] = { + { 0xb8feb708, RC_MAP_MSI_DIGIVOX_II }, + { 0xa3703d00, RC_MAP_ALINK_DTU_M }, + { 0x9b7dc64e, RC_MAP_TOTAL_MEDIA_IN_HAND }, /* MYGICTV U718 */ + { 0x5d49e3db, RC_MAP_DIGITTRADE }, /* LC-Power LC-USB-DVBT */ + { } +}; + +static int af9015_rc_query(struct dvb_usb_device *d) +{ + struct af9015_state *state = d_to_priv(d); + int ret; + u8 buf[17]; + + /* read registers needed to detect remote controller code */ + ret = af9015_read_regs(d, 0x98d9, buf, sizeof(buf)); + if (ret) + goto error; + + /* If any of these are non-zero, assume invalid data */ + if (buf[1] || buf[2] || buf[3]) { + dev_dbg(&d->udev->dev, "%s: invalid data\n", __func__); + return ret; + } + + /* Check for repeat of previous code */ + if ((state->rc_repeat != buf[6] || buf[0]) && + !memcmp(&buf[12], state->rc_last, 4)) { + dev_dbg(&d->udev->dev, "%s: key repeated\n", __func__); + rc_keydown(d->rc_dev, state->rc_keycode, 0); + state->rc_repeat = buf[6]; + return ret; + } + + /* Only process key if canary killed */ + if (buf[16] != 0xff && buf[0] != 0x01) { + dev_dbg(&d->udev->dev, "%s: key pressed %*ph\n", + __func__, 4, buf + 12); + + /* Reset the canary */ + ret = af9015_write_reg(d, 0x98e9, 0xff); + if (ret) + goto error; + + /* Remember this key */ + memcpy(state->rc_last, &buf[12], 4); + if (buf[14] == (u8) ~buf[15]) { + if (buf[12] == (u8) ~buf[13]) { + /* NEC */ + state->rc_keycode = buf[12] << 8 | buf[14]; + } else { + /* NEC extended*/ + state->rc_keycode = buf[12] << 16 | + buf[13] << 8 | buf[14]; + } + } else { + /* 32 bit NEC */ + state->rc_keycode = buf[12] << 24 | buf[13] << 16 | + buf[14] << 8 | buf[15]; + } + rc_keydown(d->rc_dev, state->rc_keycode, 0); + } else { + dev_dbg(&d->udev->dev, "%s: no key press\n", __func__); + /* Invalidate last keypress */ + /* Not really needed, but helps with debug */ + state->rc_last[2] = state->rc_last[3]; + } + + state->rc_repeat = buf[6]; + state->rc_failed = false; + +error: + if (ret) { + dev_warn(&d->udev->dev, "%s: rc query failed=%d\n", + KBUILD_MODNAME, ret); + + /* allow random errors as dvb-usb will stop polling on error */ + if (!state->rc_failed) + ret = 0; + + state->rc_failed = true; + } + + return ret; +} + +static int af9015_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) +{ + struct af9015_state *state = d_to_priv(d); + u16 vid = le16_to_cpu(d->udev->descriptor.idVendor); + + if (state->ir_mode == AF9015_IR_MODE_DISABLED) + return 0; + + /* try to load remote based module param */ + if (!rc->map_name) + rc->map_name = af9015_rc_setup_match(dvb_usb_af9015_remote, + af9015_rc_setup_modparam); + + /* try to load remote based eeprom hash */ + if (!rc->map_name) + rc->map_name = af9015_rc_setup_match(state->eeprom_sum, + af9015_rc_setup_hashes); + + /* try to load remote based USB iManufacturer string */ + if (!rc->map_name && vid == USB_VID_AFATECH) { + /* Check USB manufacturer and product strings and try + to determine correct remote in case of chip vendor + reference IDs are used. + DO NOT ADD ANYTHING NEW HERE. Use hashes instead. */ + char manufacturer[10]; + memset(manufacturer, 0, sizeof(manufacturer)); + usb_string(d->udev, d->udev->descriptor.iManufacturer, + manufacturer, sizeof(manufacturer)); + if (!strcmp("MSI", manufacturer)) { + /* iManufacturer 1 MSI + iProduct 2 MSI K-VOX */ + rc->map_name = af9015_rc_setup_match( + AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3, + af9015_rc_setup_modparam); + } + } + + /* load empty to enable rc */ + if (!rc->map_name) + rc->map_name = RC_MAP_EMPTY; + + rc->allowed_protos = RC_TYPE_NEC; + rc->query = af9015_rc_query; + rc->interval = 500; + + return 0; +} + +/* interface 0 is used by DVB-T receiver and + interface 1 is for remote controller (HID) */ +static struct dvb_usb_device_properties af9015_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct af9015_state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .identify_state = af9015_identify_state, + .firmware = AF9015_FIRMWARE, + .download_firmware = af9015_download_firmware, + + .i2c_algo = &af9015_i2c_algo, + .read_config = af9015_read_config, + .frontend_attach = af9015_af9013_frontend_attach, + .tuner_attach = af9015_tuner_attach, + .init = af9015_init, + .get_rc_config = af9015_get_rc_config, + .get_stream_config = af9015_get_stream_config, + + .get_adapter_count = af9015_get_adapter_count, + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER | + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = af9015_pid_filter, + .pid_filter_ctrl = af9015_pid_filter_ctrl, + + .stream = DVB_USB_STREAM_BULK(0x84, 8, TS_USB20_FRAME_SIZE), + }, { + .stream = DVB_USB_STREAM_BULK(0x85, 8, TS_USB20_FRAME_SIZE), + }, + }, +}; + +static const struct usb_device_id af9015_id_table[] = { + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9015, + &af9015_props, "Afatech AF9015 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9016, + &af9015_props, "Afatech AF9015 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV_DONGLE_GOLD, + &af9015_props, "Leadtek WinFast DTV Dongle Gold", RC_MAP_LEADTEK_Y04G0051) }, + { DVB_USB_DEVICE(USB_VID_PINNACLE, USB_PID_PINNACLE_PCTV71E, + &af9015_props, "Pinnacle PCTV 71e", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U, + &af9015_props, "KWorld PlusTV Dual DVB-T Stick (DVB-T 399U)", NULL) }, + { DVB_USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TINYTWIN, + &af9015_props, "DigitalNow TinyTwin", RC_MAP_AZUREWAVE_AD_TU700) }, + { DVB_USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_AZUREWAVE_AD_TU700, + &af9015_props, "TwinHan AzureWave AD-TU700(704J)", RC_MAP_AZUREWAVE_AD_TU700) }, + { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_USB_XE_REV2, + &af9015_props, "TerraTec Cinergy T USB XE", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_2T, + &af9015_props, "KWorld PlusTV Dual DVB-T PCI (DVB-T PC160-2T)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X, + &af9015_props, "AVerMedia AVerTV DVB-T Volar X", RC_MAP_AVERMEDIA_M135A) }, + { DVB_USB_DEVICE(USB_VID_XTENSIONS, USB_PID_XTENSIONS_XD_380, + &af9015_props, "Xtensions XD-380", NULL) }, + { DVB_USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGIVOX_DUO, + &af9015_props, "MSI DIGIVOX Duo", RC_MAP_MSI_DIGIVOX_III) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X_2, + &af9015_props, "Fujitsu-Siemens Slim Mobile USB DVB-T", NULL) }, + { DVB_USB_DEVICE(USB_VID_TELESTAR, USB_PID_TELESTAR_STARSTICK_2, + &af9015_props, "Telestar Starstick 2", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A309, + &af9015_props, "AVerMedia A309", NULL) }, + { DVB_USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGI_VOX_MINI_III, + &af9015_props, "MSI Digi VOX mini III", RC_MAP_MSI_DIGIVOX_III) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U, + &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_2, + &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_3, + &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_TREKSTOR_DVBT, + &af9015_props, "TrekStor DVB-T USB Stick", RC_MAP_TREKSTOR) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850, + &af9015_props, "AverMedia AVerTV Volar Black HD (A850)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A805, + &af9015_props, "AverMedia AVerTV Volar GPS 805 (A805)", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_CONCEPTRONIC_CTVDIGRCU, + &af9015_props, "Conceptronic USB2.0 DVB-T CTVDIGRCU V3.0", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_MC810, + &af9015_props, "KWorld Digial MC-810", NULL) }, + { DVB_USB_DEVICE(USB_VID_KYE, USB_PID_GENIUS_TVGO_DVB_T03, + &af9015_props, "Genius TVGo DVB-T03", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U_2, + &af9015_props, "KWorld PlusTV Dual DVB-T Stick (DVB-T 399U)", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_T, + &af9015_props, "KWorld PlusTV DVB-T PCI Pro Card (DVB-T PC160-T)", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV20, + &af9015_props, "Sveon STV20 Tuner USB DVB-T HDTV", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_TINYTWIN_2, + &af9015_props, "DigitalNow TinyTwin v2", RC_MAP_DIGITALNOW_TINYTWIN) }, + { DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV2000DS, + &af9015_props, "Leadtek WinFast DTV2000DS", RC_MAP_LEADTEK_Y04G0051) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB383_T, + &af9015_props, "KWorld USB DVB-T Stick Mobile (UB383-T)", NULL) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_4, + &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A815M, + &af9015_props, "AverMedia AVerTV Volar M (A815Mac)", NULL) }, + { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_RC, + &af9015_props, "TerraTec Cinergy T Stick RC", RC_MAP_TERRATEC_SLIM_2) }, + { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC, + &af9015_props, "TerraTec Cinergy T Stick Dual RC", RC_MAP_TERRATEC_SLIM) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850T, + &af9015_props, "AverMedia AVerTV Red HD+ (A850T)", NULL) }, + { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_TINYTWIN_3, + &af9015_props, "DigitalNow TinyTwin v3", RC_MAP_DIGITALNOW_TINYTWIN) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22, + &af9015_props, "Sveon STV22 Dual USB DVB-T Tuner HDTV", RC_MAP_MSI_DIGIVOX_III) }, + { } +}; +MODULE_DEVICE_TABLE(usb, af9015_id_table); + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver af9015_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = af9015_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(af9015_usb_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Afatech AF9015 driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(AF9015_FIRMWARE); diff --git a/drivers/media/usb/dvb-usb-v2/af9015.h b/drivers/media/usb/dvb-usb-v2/af9015.h new file mode 100644 index 000000000000..533637dedd23 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/af9015.h @@ -0,0 +1,151 @@ +/* + * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver + * + * Copyright (C) 2007 Antti Palosaari <crope@iki.fi> + * + * Thanks to Afatech who kindly provided information. + * + * 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. + * + */ + +#ifndef AF9015_H +#define AF9015_H + +#include <linux/hash.h> +#include "dvb_usb.h" +#include "af9013.h" +#include "dvb-pll.h" +#include "mt2060.h" +#include "qt1010.h" +#include "tda18271.h" +#include "mxl5005s.h" +#include "mc44s803.h" +#include "tda18218.h" +#include "mxl5007t.h" + +#define AF9015_FIRMWARE "dvb-usb-af9015.fw" + +/* Windows driver uses packet count 21 for USB1.1 and 348 for USB2.0. + We use smaller - about 1/4 from the original, 5 and 87. */ +#define TS_PACKET_SIZE 188 + +#define TS_USB20_PACKET_COUNT 87 +#define TS_USB20_FRAME_SIZE (TS_PACKET_SIZE*TS_USB20_PACKET_COUNT) + +#define TS_USB11_PACKET_COUNT 5 +#define TS_USB11_FRAME_SIZE (TS_PACKET_SIZE*TS_USB11_PACKET_COUNT) + +#define TS_USB20_MAX_PACKET_SIZE 512 +#define TS_USB11_MAX_PACKET_SIZE 64 + +#define AF9015_I2C_EEPROM 0xa0 +#define AF9015_I2C_DEMOD 0x38 +#define AF9015_USB_TIMEOUT 2000 + +/* EEPROM locations */ +#define AF9015_EEPROM_IR_MODE 0x18 +#define AF9015_EEPROM_IR_REMOTE_TYPE 0x34 +#define AF9015_EEPROM_TS_MODE 0x31 +#define AF9015_EEPROM_DEMOD2_I2C 0x32 + +#define AF9015_EEPROM_SAW_BW1 0x35 +#define AF9015_EEPROM_XTAL_TYPE1 0x36 +#define AF9015_EEPROM_SPEC_INV1 0x37 +#define AF9015_EEPROM_IF1L 0x38 +#define AF9015_EEPROM_IF1H 0x39 +#define AF9015_EEPROM_MT2060_IF1L 0x3a +#define AF9015_EEPROM_MT2060_IF1H 0x3b +#define AF9015_EEPROM_TUNER_ID1 0x3c + +#define AF9015_EEPROM_SAW_BW2 0x45 +#define AF9015_EEPROM_XTAL_TYPE2 0x46 +#define AF9015_EEPROM_SPEC_INV2 0x47 +#define AF9015_EEPROM_IF2L 0x48 +#define AF9015_EEPROM_IF2H 0x49 +#define AF9015_EEPROM_MT2060_IF2L 0x4a +#define AF9015_EEPROM_MT2060_IF2H 0x4b +#define AF9015_EEPROM_TUNER_ID2 0x4c + +#define AF9015_EEPROM_OFFSET (AF9015_EEPROM_SAW_BW2 - AF9015_EEPROM_SAW_BW1) + +struct req_t { + u8 cmd; /* [0] */ + /* seq */ /* [1] */ + u8 i2c_addr; /* [2] */ + u16 addr; /* [3|4] */ + u8 mbox; /* [5] */ + u8 addr_len; /* [6] */ + u8 data_len; /* [7] */ + u8 *data; +}; + +enum af9015_cmd { + GET_CONFIG = 0x10, + DOWNLOAD_FIRMWARE = 0x11, + BOOT = 0x13, + READ_MEMORY = 0x20, + WRITE_MEMORY = 0x21, + READ_WRITE_I2C = 0x22, + COPY_FIRMWARE = 0x23, + RECONNECT_USB = 0x5a, + WRITE_VIRTUAL_MEMORY = 0x26, + GET_IR_CODE = 0x27, + READ_I2C, + WRITE_I2C, +}; + +enum af9015_ir_mode { + AF9015_IR_MODE_DISABLED = 0, + AF9015_IR_MODE_HID, + AF9015_IR_MODE_RLC, + AF9015_IR_MODE_RC6, + AF9015_IR_MODE_POLLING, /* just guess */ +}; + +struct af9015_state { + u8 ir_mode; + u8 rc_repeat; + u32 rc_keycode; + u8 rc_last[4]; + bool rc_failed; + u8 dual_mode; + u8 seq; /* packet sequence number */ + u16 mt2060_if1[2]; + u16 firmware_size; + u16 firmware_checksum; + u32 eeprom_sum; + struct af9013_config af9013_config[2]; + + /* for demod callback override */ + int (*set_frontend[2]) (struct dvb_frontend *fe); + int (*read_status[2]) (struct dvb_frontend *fe, fe_status_t *status); + int (*init[2]) (struct dvb_frontend *fe); + int (*sleep[2]) (struct dvb_frontend *fe); + int (*tuner_init[2]) (struct dvb_frontend *fe); + int (*tuner_sleep[2]) (struct dvb_frontend *fe); + struct mutex fe_mutex; +}; + +enum af9015_remote { + AF9015_REMOTE_NONE = 0, +/* 1 */ AF9015_REMOTE_A_LINK_DTU_M, + AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3, + AF9015_REMOTE_MYGICTV_U718, + AF9015_REMOTE_DIGITTRADE_DVB_T, +/* 5 */ AF9015_REMOTE_AVERMEDIA_KS, +}; + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/af9035.c b/drivers/media/usb/dvb-usb-v2/af9035.c new file mode 100644 index 000000000000..aabd3fc03ea7 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/af9035.c @@ -0,0 +1,1158 @@ +/* + * Afatech AF9035 DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2012 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "af9035.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static u16 af9035_checksum(const u8 *buf, size_t len) +{ + size_t i; + u16 checksum = 0; + + for (i = 1; i < len; i++) { + if (i % 2) + checksum += buf[i] << 8; + else + checksum += buf[i]; + } + checksum = ~checksum; + + return checksum; +} + +static int af9035_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) +{ +#define BUF_LEN 64 +#define REQ_HDR_LEN 4 /* send header size */ +#define ACK_HDR_LEN 3 /* rece header size */ +#define CHECKSUM_LEN 2 +#define USB_TIMEOUT 2000 + struct state *state = d_to_priv(d); + int ret, wlen, rlen; + u8 buf[BUF_LEN]; + u16 checksum, tmp_checksum; + + /* buffer overflow check */ + if (req->wlen > (BUF_LEN - REQ_HDR_LEN - CHECKSUM_LEN) || + req->rlen > (BUF_LEN - ACK_HDR_LEN - CHECKSUM_LEN)) { + dev_err(&d->udev->dev, "%s: too much data wlen=%d rlen=%d\n", + __func__, req->wlen, req->rlen); + return -EINVAL; + } + + buf[0] = REQ_HDR_LEN + req->wlen + CHECKSUM_LEN - 1; + buf[1] = req->mbox; + buf[2] = req->cmd; + buf[3] = state->seq++; + memcpy(&buf[REQ_HDR_LEN], req->wbuf, req->wlen); + + wlen = REQ_HDR_LEN + req->wlen + CHECKSUM_LEN; + rlen = ACK_HDR_LEN + req->rlen + CHECKSUM_LEN; + + /* calc and add checksum */ + checksum = af9035_checksum(buf, buf[0] - 1); + buf[buf[0] - 1] = (checksum >> 8); + buf[buf[0] - 0] = (checksum & 0xff); + + /* no ack for these packets */ + if (req->cmd == CMD_FW_DL) + rlen = 0; + + ret = dvb_usbv2_generic_rw(d, buf, wlen, buf, rlen); + if (ret) + goto err; + + /* no ack for those packets */ + if (req->cmd == CMD_FW_DL) + goto exit; + + /* verify checksum */ + checksum = af9035_checksum(buf, rlen - 2); + tmp_checksum = (buf[rlen - 2] << 8) | buf[rlen - 1]; + if (tmp_checksum != checksum) { + dev_err(&d->udev->dev, "%s: command=%02x checksum mismatch " \ + "(%04x != %04x)\n", KBUILD_MODNAME, req->cmd, + tmp_checksum, checksum); + ret = -EIO; + goto err; + } + + /* check status */ + if (buf[2]) { + dev_dbg(&d->udev->dev, "%s: command=%02x failed fw error=%d\n", + __func__, req->cmd, buf[2]); + ret = -EIO; + goto err; + } + + /* read request, copy returned data to return buf */ + if (req->rlen) + memcpy(req->rbuf, &buf[ACK_HDR_LEN], req->rlen); + +exit: + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +/* write multiple registers */ +static int af9035_wr_regs(struct dvb_usb_device *d, u32 reg, u8 *val, int len) +{ + u8 wbuf[6 + len]; + u8 mbox = (reg >> 16) & 0xff; + struct usb_req req = { CMD_MEM_WR, mbox, sizeof(wbuf), wbuf, 0, NULL }; + + wbuf[0] = len; + wbuf[1] = 2; + wbuf[2] = 0; + wbuf[3] = 0; + wbuf[4] = (reg >> 8) & 0xff; + wbuf[5] = (reg >> 0) & 0xff; + memcpy(&wbuf[6], val, len); + + return af9035_ctrl_msg(d, &req); +} + +/* read multiple registers */ +static int af9035_rd_regs(struct dvb_usb_device *d, u32 reg, u8 *val, int len) +{ + u8 wbuf[] = { len, 2, 0, 0, (reg >> 8) & 0xff, reg & 0xff }; + u8 mbox = (reg >> 16) & 0xff; + struct usb_req req = { CMD_MEM_RD, mbox, sizeof(wbuf), wbuf, len, val }; + + return af9035_ctrl_msg(d, &req); +} + +/* write single register */ +static int af9035_wr_reg(struct dvb_usb_device *d, u32 reg, u8 val) +{ + return af9035_wr_regs(d, reg, &val, 1); +} + +/* read single register */ +static int af9035_rd_reg(struct dvb_usb_device *d, u32 reg, u8 *val) +{ + return af9035_rd_regs(d, reg, val, 1); +} + +/* write single register with mask */ +static int af9035_wr_reg_mask(struct dvb_usb_device *d, u32 reg, u8 val, + u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = af9035_rd_regs(d, reg, &tmp, 1); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return af9035_wr_regs(d, reg, &val, 1); +} + +static int af9035_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg msg[], int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct state *state = d_to_priv(d); + int ret; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + /* + * I2C sub header is 5 bytes long. Meaning of those bytes are: + * 0: data len + * 1: I2C addr << 1 + * 2: reg addr len + * byte 3 and 4 can be used as reg addr + * 3: reg addr MSB + * used when reg addr len is set to 2 + * 4: reg addr LSB + * used when reg addr len is set to 1 or 2 + * + * For the simplify we do not use register addr at all. + * NOTE: As a firmware knows tuner type there is very small possibility + * there could be some tuner I2C hacks done by firmware and this may + * lead problems if firmware expects those bytes are used. + */ + if (num == 2 && !(msg[0].flags & I2C_M_RD) && + (msg[1].flags & I2C_M_RD)) { + if (msg[0].len > 40 || msg[1].len > 40) { + /* TODO: correct limits > 40 */ + ret = -EOPNOTSUPP; + } else if (msg[0].addr == state->af9033_config[0].i2c_addr) { + /* integrated demod */ + u32 reg = msg[0].buf[0] << 16 | msg[0].buf[1] << 8 | + msg[0].buf[2]; + ret = af9035_rd_regs(d, reg, &msg[1].buf[0], + msg[1].len); + } else { + /* I2C */ + u8 buf[5 + msg[0].len]; + struct usb_req req = { CMD_I2C_RD, 0, sizeof(buf), + buf, msg[1].len, msg[1].buf }; + buf[0] = msg[1].len; + buf[1] = msg[0].addr << 1; + buf[2] = 0x00; /* reg addr len */ + buf[3] = 0x00; /* reg addr MSB */ + buf[4] = 0x00; /* reg addr LSB */ + memcpy(&buf[5], msg[0].buf, msg[0].len); + ret = af9035_ctrl_msg(d, &req); + } + } else if (num == 1 && !(msg[0].flags & I2C_M_RD)) { + if (msg[0].len > 40) { + /* TODO: correct limits > 40 */ + ret = -EOPNOTSUPP; + } else if (msg[0].addr == state->af9033_config[0].i2c_addr) { + /* integrated demod */ + u32 reg = msg[0].buf[0] << 16 | msg[0].buf[1] << 8 | + msg[0].buf[2]; + ret = af9035_wr_regs(d, reg, &msg[0].buf[3], + msg[0].len - 3); + } else { + /* I2C */ + u8 buf[5 + msg[0].len]; + struct usb_req req = { CMD_I2C_WR, 0, sizeof(buf), buf, + 0, NULL }; + buf[0] = msg[0].len; + buf[1] = msg[0].addr << 1; + buf[2] = 0x00; /* reg addr len */ + buf[3] = 0x00; /* reg addr MSB */ + buf[4] = 0x00; /* reg addr LSB */ + memcpy(&buf[5], msg[0].buf, msg[0].len); + ret = af9035_ctrl_msg(d, &req); + } + } else { + /* + * We support only two kind of I2C transactions: + * 1) 1 x read + 1 x write + * 2) 1 x write + */ + ret = -EOPNOTSUPP; + } + + mutex_unlock(&d->i2c_mutex); + + if (ret < 0) + return ret; + else + return num; +} + +static u32 af9035_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm af9035_i2c_algo = { + .master_xfer = af9035_i2c_master_xfer, + .functionality = af9035_i2c_functionality, +}; + +static int af9035_identify_state(struct dvb_usb_device *d, const char **name) +{ + int ret; + u8 wbuf[1] = { 1 }; + u8 rbuf[4]; + struct usb_req req = { CMD_FW_QUERYINFO, 0, sizeof(wbuf), wbuf, + sizeof(rbuf), rbuf }; + + ret = af9035_ctrl_msg(d, &req); + if (ret < 0) + goto err; + + dev_dbg(&d->udev->dev, "%s: reply=%*ph\n", __func__, 4, rbuf); + if (rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3]) + ret = WARM; + else + ret = COLD; + + return ret; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_download_firmware(struct dvb_usb_device *d, + const struct firmware *fw) +{ + int ret, i, j, len; + u8 wbuf[1]; + u8 rbuf[4]; + struct usb_req req = { 0, 0, 0, NULL, 0, NULL }; + struct usb_req req_fw_dl = { CMD_FW_DL, 0, 0, wbuf, 0, NULL }; + struct usb_req req_fw_ver = { CMD_FW_QUERYINFO, 0, 1, wbuf, 4, rbuf } ; + u8 hdr_core; + u16 hdr_addr, hdr_data_len, hdr_checksum; + #define MAX_DATA 58 + #define HDR_SIZE 7 + + /* + * Thanks to Daniel Glöckner <daniel-gl@gmx.net> about that info! + * + * byte 0: MCS 51 core + * There are two inside the AF9035 (1=Link and 2=OFDM) with separate + * address spaces + * byte 1-2: Big endian destination address + * byte 3-4: Big endian number of data bytes following the header + * byte 5-6: Big endian header checksum, apparently ignored by the chip + * Calculated as ~(h[0]*256+h[1]+h[2]*256+h[3]+h[4]*256) + */ + + for (i = fw->size; i > HDR_SIZE;) { + hdr_core = fw->data[fw->size - i + 0]; + hdr_addr = fw->data[fw->size - i + 1] << 8; + hdr_addr |= fw->data[fw->size - i + 2] << 0; + hdr_data_len = fw->data[fw->size - i + 3] << 8; + hdr_data_len |= fw->data[fw->size - i + 4] << 0; + hdr_checksum = fw->data[fw->size - i + 5] << 8; + hdr_checksum |= fw->data[fw->size - i + 6] << 0; + + dev_dbg(&d->udev->dev, "%s: core=%d addr=%04x data_len=%d " \ + "checksum=%04x\n", __func__, hdr_core, hdr_addr, + hdr_data_len, hdr_checksum); + + if (((hdr_core != 1) && (hdr_core != 2)) || + (hdr_data_len > i)) { + dev_dbg(&d->udev->dev, "%s: bad firmware\n", __func__); + break; + } + + /* download begin packet */ + req.cmd = CMD_FW_DL_BEGIN; + ret = af9035_ctrl_msg(d, &req); + if (ret < 0) + goto err; + + /* download firmware packet(s) */ + for (j = HDR_SIZE + hdr_data_len; j > 0; j -= MAX_DATA) { + len = j; + if (len > MAX_DATA) + len = MAX_DATA; + req_fw_dl.wlen = len; + req_fw_dl.wbuf = (u8 *) &fw->data[fw->size - i + + HDR_SIZE + hdr_data_len - j]; + ret = af9035_ctrl_msg(d, &req_fw_dl); + if (ret < 0) + goto err; + } + + /* download end packet */ + req.cmd = CMD_FW_DL_END; + ret = af9035_ctrl_msg(d, &req); + if (ret < 0) + goto err; + + i -= hdr_data_len + HDR_SIZE; + + dev_dbg(&d->udev->dev, "%s: data uploaded=%zu\n", + __func__, fw->size - i); + } + + /* firmware loaded, request boot */ + req.cmd = CMD_FW_BOOT; + ret = af9035_ctrl_msg(d, &req); + if (ret < 0) + goto err; + + /* ensure firmware starts */ + wbuf[0] = 1; + ret = af9035_ctrl_msg(d, &req_fw_ver); + if (ret < 0) + goto err; + + if (!(rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3])) { + dev_err(&d->udev->dev, "%s: firmware did not run\n", + KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } + + dev_info(&d->udev->dev, "%s: firmware version=%d.%d.%d.%d", + KBUILD_MODNAME, rbuf[0], rbuf[1], rbuf[2], rbuf[3]); + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_download_firmware_it9135(struct dvb_usb_device *d, + const struct firmware *fw) +{ + int ret, i, i_prev; + u8 wbuf[1]; + u8 rbuf[4]; + struct usb_req req = { 0, 0, 0, NULL, 0, NULL }; + struct usb_req req_fw_dl = { CMD_FW_SCATTER_WR, 0, 0, NULL, 0, NULL }; + struct usb_req req_fw_ver = { CMD_FW_QUERYINFO, 0, 1, wbuf, 4, rbuf } ; + #define HDR_SIZE 7 + + /* + * There seems to be following firmware header. Meaning of bytes 0-3 + * is unknown. + * + * 0: 3 + * 1: 0, 1 + * 2: 0 + * 3: 1, 2, 3 + * 4: addr MSB + * 5: addr LSB + * 6: count of data bytes ? + */ + + for (i = HDR_SIZE, i_prev = 0; i <= fw->size; i++) { + if (i == fw->size || + (fw->data[i + 0] == 0x03 && + (fw->data[i + 1] == 0x00 || + fw->data[i + 1] == 0x01) && + fw->data[i + 2] == 0x00)) { + req_fw_dl.wlen = i - i_prev; + req_fw_dl.wbuf = (u8 *) &fw->data[i_prev]; + i_prev = i; + ret = af9035_ctrl_msg(d, &req_fw_dl); + if (ret < 0) + goto err; + + dev_dbg(&d->udev->dev, "%s: data uploaded=%d\n", + __func__, i); + } + } + + /* firmware loaded, request boot */ + req.cmd = CMD_FW_BOOT; + ret = af9035_ctrl_msg(d, &req); + if (ret < 0) + goto err; + + /* ensure firmware starts */ + wbuf[0] = 1; + ret = af9035_ctrl_msg(d, &req_fw_ver); + if (ret < 0) + goto err; + + if (!(rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3])) { + dev_err(&d->udev->dev, "%s: firmware did not run\n", + KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } + + dev_info(&d->udev->dev, "%s: firmware version=%d.%d.%d.%d", + KBUILD_MODNAME, rbuf[0], rbuf[1], rbuf[2], rbuf[3]); + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_read_config(struct dvb_usb_device *d) +{ + struct state *state = d_to_priv(d); + int ret, i, eeprom_shift = 0; + u8 tmp; + u16 tmp16; + + /* check if there is dual tuners */ + ret = af9035_rd_reg(d, EEPROM_DUAL_MODE, &tmp); + if (ret < 0) + goto err; + + state->dual_mode = tmp; + dev_dbg(&d->udev->dev, "%s: dual mode=%d\n", + __func__, state->dual_mode); + + for (i = 0; i < state->dual_mode + 1; i++) { + /* tuner */ + ret = af9035_rd_reg(d, EEPROM_1_TUNER_ID + eeprom_shift, &tmp); + if (ret < 0) + goto err; + + state->af9033_config[i].tuner = tmp; + dev_dbg(&d->udev->dev, "%s: [%d]tuner=%02x\n", + __func__, i, tmp); + + switch (tmp) { + case AF9033_TUNER_TUA9001: + case AF9033_TUNER_FC0011: + case AF9033_TUNER_MXL5007T: + case AF9033_TUNER_TDA18218: + case AF9033_TUNER_FC2580: + state->af9033_config[i].spec_inv = 1; + break; + default: + dev_warn(&d->udev->dev, "%s: tuner id=%02x not " \ + "supported, please report!", + KBUILD_MODNAME, tmp); + }; + + /* tuner IF frequency */ + ret = af9035_rd_reg(d, EEPROM_1_IFFREQ_L + eeprom_shift, &tmp); + if (ret < 0) + goto err; + + tmp16 = tmp; + + ret = af9035_rd_reg(d, EEPROM_1_IFFREQ_H + eeprom_shift, &tmp); + if (ret < 0) + goto err; + + tmp16 |= tmp << 8; + + dev_dbg(&d->udev->dev, "%s: [%d]IF=%d\n", __func__, i, tmp16); + + eeprom_shift = 0x10; /* shift for the 2nd tuner params */ + } + + /* get demod clock */ + ret = af9035_rd_reg(d, 0x00d800, &tmp); + if (ret < 0) + goto err; + + tmp = (tmp >> 0) & 0x0f; + + for (i = 0; i < ARRAY_SIZE(state->af9033_config); i++) + state->af9033_config[i].clock = clock_lut[tmp]; + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_read_config_it9135(struct dvb_usb_device *d) +{ + struct state *state = d_to_priv(d); + int ret, i; + u8 tmp; + + state->dual_mode = false; + + /* get demod clock */ + ret = af9035_rd_reg(d, 0x00d800, &tmp); + if (ret < 0) + goto err; + + tmp = (tmp >> 0) & 0x0f; + + for (i = 0; i < ARRAY_SIZE(state->af9033_config); i++) + state->af9033_config[i].clock = clock_lut_it9135[tmp]; + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_tua9001_tuner_callback(struct dvb_usb_device *d, + int cmd, int arg) +{ + int ret; + u8 val; + + dev_dbg(&d->udev->dev, "%s: cmd=%d arg=%d\n", __func__, cmd, arg); + + /* + * CEN always enabled by hardware wiring + * RESETN GPIOT3 + * RXEN GPIOT2 + */ + + switch (cmd) { + case TUA9001_CMD_RESETN: + if (arg) + val = 0x00; + else + val = 0x01; + + ret = af9035_wr_reg_mask(d, 0x00d8e7, val, 0x01); + if (ret < 0) + goto err; + break; + case TUA9001_CMD_RXEN: + if (arg) + val = 0x01; + else + val = 0x00; + + ret = af9035_wr_reg_mask(d, 0x00d8eb, val, 0x01); + if (ret < 0) + goto err; + break; + } + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + + +static int af9035_fc0011_tuner_callback(struct dvb_usb_device *d, + int cmd, int arg) +{ + int ret; + + switch (cmd) { + case FC0011_FE_CALLBACK_POWER: + /* Tuner enable */ + ret = af9035_wr_reg_mask(d, 0xd8eb, 1, 1); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0xd8ec, 1, 1); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0xd8ed, 1, 1); + if (ret < 0) + goto err; + + /* LED */ + ret = af9035_wr_reg_mask(d, 0xd8d0, 1, 1); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0xd8d1, 1, 1); + if (ret < 0) + goto err; + + usleep_range(10000, 50000); + break; + case FC0011_FE_CALLBACK_RESET: + ret = af9035_wr_reg(d, 0xd8e9, 1); + if (ret < 0) + goto err; + + ret = af9035_wr_reg(d, 0xd8e8, 1); + if (ret < 0) + goto err; + + ret = af9035_wr_reg(d, 0xd8e7, 1); + if (ret < 0) + goto err; + + usleep_range(10000, 20000); + + ret = af9035_wr_reg(d, 0xd8e7, 0); + if (ret < 0) + goto err; + + usleep_range(10000, 20000); + break; + default: + ret = -EINVAL; + goto err; + } + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_tuner_callback(struct dvb_usb_device *d, int cmd, int arg) +{ + struct state *state = d_to_priv(d); + + switch (state->af9033_config[0].tuner) { + case AF9033_TUNER_FC0011: + return af9035_fc0011_tuner_callback(d, cmd, arg); + case AF9033_TUNER_TUA9001: + return af9035_tua9001_tuner_callback(d, cmd, arg); + default: + break; + } + + return 0; +} + +static int af9035_frontend_callback(void *adapter_priv, int component, + int cmd, int arg) +{ + struct i2c_adapter *adap = adapter_priv; + struct dvb_usb_device *d = i2c_get_adapdata(adap); + + dev_dbg(&d->udev->dev, "%s: component=%d cmd=%d arg=%d\n", + __func__, component, cmd, arg); + + switch (component) { + case DVB_FRONTEND_COMPONENT_TUNER: + return af9035_tuner_callback(d, cmd, arg); + default: + break; + } + + return 0; +} + +static int af9035_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret; + + if (!state->af9033_config[adap->id].tuner) { + /* unsupported tuner */ + ret = -ENODEV; + goto err; + } + + if (adap->id == 0) { + state->af9033_config[0].ts_mode = AF9033_TS_MODE_USB; + state->af9033_config[1].ts_mode = AF9033_TS_MODE_SERIAL; + + ret = af9035_wr_reg(d, 0x00417f, + state->af9033_config[1].i2c_addr); + if (ret < 0) + goto err; + + ret = af9035_wr_reg(d, 0x00d81a, + state->dual_mode); + if (ret < 0) + goto err; + } + + /* attach demodulator */ + adap->fe[0] = dvb_attach(af9033_attach, + &state->af9033_config[adap->id], &d->i2c_adap); + if (adap->fe[0] == NULL) { + ret = -ENODEV; + goto err; + } + + /* disable I2C-gate */ + adap->fe[0]->ops.i2c_gate_ctrl = NULL; + adap->fe[0]->callback = af9035_frontend_callback; + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static struct tua9001_config af9035_tua9001_config = { + .i2c_addr = 0x60, +}; + +static const struct fc0011_config af9035_fc0011_config = { + .i2c_address = 0x60, +}; + +static struct mxl5007t_config af9035_mxl5007t_config = { + .xtal_freq_hz = MxL_XTAL_24_MHZ, + .if_freq_hz = MxL_IF_4_57_MHZ, + .invert_if = 0, + .loop_thru_enable = 0, + .clk_out_enable = 0, + .clk_out_amp = MxL_CLKOUT_AMP_0_94V, +}; + +static struct tda18218_config af9035_tda18218_config = { + .i2c_address = 0x60, + .i2c_wr_max = 21, +}; + +static const struct fc2580_config af9035_fc2580_config = { + .i2c_addr = 0x56, + .clock = 16384000, +}; + +static int af9035_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret; + struct dvb_frontend *fe; + + switch (state->af9033_config[adap->id].tuner) { + case AF9033_TUNER_TUA9001: + /* AF9035 gpiot3 = TUA9001 RESETN + AF9035 gpiot2 = TUA9001 RXEN */ + + /* configure gpiot2 and gpiot2 as output */ + ret = af9035_wr_reg_mask(d, 0x00d8ec, 0x01, 0x01); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0x00d8ed, 0x01, 0x01); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0x00d8e8, 0x01, 0x01); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0x00d8e9, 0x01, 0x01); + if (ret < 0) + goto err; + + /* attach tuner */ + fe = dvb_attach(tua9001_attach, adap->fe[0], + &d->i2c_adap, &af9035_tua9001_config); + break; + case AF9033_TUNER_FC0011: + fe = dvb_attach(fc0011_attach, adap->fe[0], + &d->i2c_adap, &af9035_fc0011_config); + break; + case AF9033_TUNER_MXL5007T: + ret = af9035_wr_reg(d, 0x00d8e0, 1); + if (ret < 0) + goto err; + ret = af9035_wr_reg(d, 0x00d8e1, 1); + if (ret < 0) + goto err; + ret = af9035_wr_reg(d, 0x00d8df, 0); + if (ret < 0) + goto err; + + msleep(30); + + ret = af9035_wr_reg(d, 0x00d8df, 1); + if (ret < 0) + goto err; + + msleep(300); + + ret = af9035_wr_reg(d, 0x00d8c0, 1); + if (ret < 0) + goto err; + ret = af9035_wr_reg(d, 0x00d8c1, 1); + if (ret < 0) + goto err; + ret = af9035_wr_reg(d, 0x00d8bf, 0); + if (ret < 0) + goto err; + ret = af9035_wr_reg(d, 0x00d8b4, 1); + if (ret < 0) + goto err; + ret = af9035_wr_reg(d, 0x00d8b5, 1); + if (ret < 0) + goto err; + ret = af9035_wr_reg(d, 0x00d8b3, 1); + if (ret < 0) + goto err; + + /* attach tuner */ + fe = dvb_attach(mxl5007t_attach, adap->fe[0], + &d->i2c_adap, 0x60, &af9035_mxl5007t_config); + break; + case AF9033_TUNER_TDA18218: + /* attach tuner */ + fe = dvb_attach(tda18218_attach, adap->fe[0], + &d->i2c_adap, &af9035_tda18218_config); + break; + case AF9033_TUNER_FC2580: + /* Tuner enable using gpiot2_o, gpiot2_en and gpiot2_on */ + ret = af9035_wr_reg_mask(d, 0xd8eb, 0x01, 0x01); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0xd8ec, 0x01, 0x01); + if (ret < 0) + goto err; + + ret = af9035_wr_reg_mask(d, 0xd8ed, 0x01, 0x01); + if (ret < 0) + goto err; + + usleep_range(10000, 50000); + /* attach tuner */ + fe = dvb_attach(fc2580_attach, adap->fe[0], + &d->i2c_adap, &af9035_fc2580_config); + break; + default: + fe = NULL; + } + + if (fe == NULL) { + ret = -ENODEV; + goto err; + } + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_init(struct dvb_usb_device *d) +{ + struct state *state = d_to_priv(d); + int ret, i; + u16 frame_size = 87 * 188 / 4; + u8 packet_size = 512 / 4; + struct reg_val_mask tab[] = { + { 0x80f99d, 0x01, 0x01 }, + { 0x80f9a4, 0x01, 0x01 }, + { 0x00dd11, 0x00, 0x20 }, + { 0x00dd11, 0x00, 0x40 }, + { 0x00dd13, 0x00, 0x20 }, + { 0x00dd13, 0x00, 0x40 }, + { 0x00dd11, 0x20, 0x20 }, + { 0x00dd88, (frame_size >> 0) & 0xff, 0xff}, + { 0x00dd89, (frame_size >> 8) & 0xff, 0xff}, + { 0x00dd0c, packet_size, 0xff}, + { 0x00dd11, state->dual_mode << 6, 0x40 }, + { 0x00dd8a, (frame_size >> 0) & 0xff, 0xff}, + { 0x00dd8b, (frame_size >> 8) & 0xff, 0xff}, + { 0x00dd0d, packet_size, 0xff }, + { 0x80f9a3, 0x00, 0x01 }, + { 0x80f9cd, 0x00, 0x01 }, + { 0x80f99d, 0x00, 0x01 }, + { 0x80f9a4, 0x00, 0x01 }, + }; + + dev_dbg(&d->udev->dev, "%s: USB speed=%d frame_size=%04x " \ + "packet_size=%02x\n", __func__, + d->udev->speed, frame_size, packet_size); + + /* init endpoints */ + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = af9035_wr_reg_mask(d, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret < 0) + goto err; + } + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9035_rc_query(struct dvb_usb_device *d) +{ + unsigned int key; + unsigned char b[4]; + int ret; + struct usb_req req = { CMD_IR_GET, 0, 0, NULL, 4, b }; + + ret = af9035_ctrl_msg(d, &req); + if (ret < 0) + goto err; + + if ((b[2] + b[3]) == 0xff) { + if ((b[0] + b[1]) == 0xff) { + /* NEC */ + key = b[0] << 8 | b[2]; + } else { + /* ext. NEC */ + key = b[0] << 16 | b[1] << 8 | b[2]; + } + } else { + key = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; + } + + rc_keydown(d->rc_dev, key, 0); + +err: + /* ignore errors */ + return 0; +} + +static int af9035_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) +{ + int ret; + u8 tmp; + + ret = af9035_rd_reg(d, EEPROM_IR_MODE, &tmp); + if (ret < 0) + goto err; + + dev_dbg(&d->udev->dev, "%s: ir_mode=%02x\n", __func__, tmp); + + /* don't activate rc if in HID mode or if not available */ + if (tmp == 5) { + ret = af9035_rd_reg(d, EEPROM_IR_TYPE, &tmp); + if (ret < 0) + goto err; + + dev_dbg(&d->udev->dev, "%s: ir_type=%02x\n", __func__, tmp); + + switch (tmp) { + case 0: /* NEC */ + default: + rc->allowed_protos = RC_TYPE_NEC; + break; + case 1: /* RC6 */ + rc->allowed_protos = RC_TYPE_RC6; + break; + } + + rc->query = af9035_rc_query; + rc->interval = 500; + + /* load empty to enable rc */ + if (!rc->map_name) + rc->map_name = RC_MAP_EMPTY; + } + + return 0; + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +/* interface 0 is used by DVB-T receiver and + interface 1 is for remote controller (HID) */ +static const struct dvb_usb_device_properties af9035_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .identify_state = af9035_identify_state, + .firmware = AF9035_FIRMWARE_AF9035, + .download_firmware = af9035_download_firmware, + + .i2c_algo = &af9035_i2c_algo, + .read_config = af9035_read_config, + .frontend_attach = af9035_frontend_attach, + .tuner_attach = af9035_tuner_attach, + .init = af9035_init, + .get_rc_config = af9035_get_rc_config, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x84, 6, 87 * 188), + }, { + .stream = DVB_USB_STREAM_BULK(0x85, 6, 87 * 188), + }, + }, +}; + +static const struct dvb_usb_device_properties it9135_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .identify_state = af9035_identify_state, + .firmware = AF9035_FIRMWARE_IT9135, + .download_firmware = af9035_download_firmware_it9135, + + .i2c_algo = &af9035_i2c_algo, + .read_config = af9035_read_config_it9135, + .frontend_attach = af9035_frontend_attach, + .tuner_attach = af9035_tuner_attach, + .init = af9035_init, + .get_rc_config = af9035_get_rc_config, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x84, 6, 87 * 188), + }, { + .stream = DVB_USB_STREAM_BULK(0x85, 6, 87 * 188), + }, + }, +}; + +static const struct usb_device_id af9035_id_table[] = { + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_9035, + &af9035_props, "Afatech AF9035 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1000, + &af9035_props, "Afatech AF9035 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1001, + &af9035_props, "Afatech AF9035 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1002, + &af9035_props, "Afatech AF9035 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1003, + &af9035_props, "Afatech AF9035 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK, + &af9035_props, "TerraTec Cinergy T Stick", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835, + &af9035_props, "AVerMedia AVerTV Volar HD/PRO (A835)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_B835, + &af9035_props, "AVerMedia AVerTV Volar HD/PRO (A835)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_1867, + &af9035_props, "AVerMedia HD Volar (A867)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A867, + &af9035_props, "AVerMedia HD Volar (A867)", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_TWINSTAR, + &af9035_props, "AVerMedia Twinstar (A825)", NULL) }, + { DVB_USB_DEVICE(USB_VID_ASUS, USB_PID_ASUS_U3100MINI_PLUS, + &af9035_props, "Asus U3100Mini Plus", NULL) }, + { } +}; +MODULE_DEVICE_TABLE(usb, af9035_id_table); + +static struct usb_driver af9035_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = af9035_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(af9035_usb_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Afatech AF9035 driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(AF9035_FIRMWARE_AF9035); +MODULE_FIRMWARE(AF9035_FIRMWARE_IT9135); diff --git a/drivers/media/usb/dvb-usb-v2/af9035.h b/drivers/media/usb/dvb-usb-v2/af9035.h new file mode 100644 index 000000000000..75ef1ec13fbf --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/af9035.h @@ -0,0 +1,115 @@ +/* + * Afatech AF9035 DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2012 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef AF9035_H +#define AF9035_H + +#include "dvb_usb.h" +#include "af9033.h" +#include "tua9001.h" +#include "fc0011.h" +#include "mxl5007t.h" +#include "tda18218.h" +#include "fc2580.h" + +struct reg_val { + u32 reg; + u8 val; +}; + +struct reg_val_mask { + u32 reg; + u8 val; + u8 mask; +}; + +struct usb_req { + u8 cmd; + u8 mbox; + u8 wlen; + u8 *wbuf; + u8 rlen; + u8 *rbuf; +}; + +struct state { + u8 seq; /* packet sequence number */ + bool dual_mode; + + struct af9033_config af9033_config[2]; +}; + +u32 clock_lut[] = { + 20480000, /* FPGA */ + 16384000, /* 16.38 MHz */ + 20480000, /* 20.48 MHz */ + 36000000, /* 36.00 MHz */ + 30000000, /* 30.00 MHz */ + 26000000, /* 26.00 MHz */ + 28000000, /* 28.00 MHz */ + 32000000, /* 32.00 MHz */ + 34000000, /* 34.00 MHz */ + 24000000, /* 24.00 MHz */ + 22000000, /* 22.00 MHz */ + 12000000, /* 12.00 MHz */ +}; + +u32 clock_lut_it9135[] = { + 12000000, /* 12.00 MHz */ + 20480000, /* 20.48 MHz */ + 36000000, /* 36.00 MHz */ + 30000000, /* 30.00 MHz */ + 26000000, /* 26.00 MHz */ + 28000000, /* 28.00 MHz */ + 32000000, /* 32.00 MHz */ + 34000000, /* 34.00 MHz */ + 24000000, /* 24.00 MHz */ + 22000000, /* 22.00 MHz */ +}; + +#define AF9035_FIRMWARE_AF9035 "dvb-usb-af9035-02.fw" +#define AF9035_FIRMWARE_IT9135 "dvb-usb-it9135-01.fw" + +/* EEPROM locations */ +#define EEPROM_IR_MODE 0x430d +#define EEPROM_DUAL_MODE 0x4326 +#define EEPROM_IR_TYPE 0x4329 +#define EEPROM_1_IFFREQ_L 0x432d +#define EEPROM_1_IFFREQ_H 0x432e +#define EEPROM_1_TUNER_ID 0x4331 +#define EEPROM_2_IFFREQ_L 0x433d +#define EEPROM_2_IFFREQ_H 0x433e +#define EEPROM_2_TUNER_ID 0x4341 + +/* USB commands */ +#define CMD_MEM_RD 0x00 +#define CMD_MEM_WR 0x01 +#define CMD_I2C_RD 0x02 +#define CMD_I2C_WR 0x03 +#define CMD_IR_GET 0x18 +#define CMD_FW_DL 0x21 +#define CMD_FW_QUERYINFO 0x22 +#define CMD_FW_BOOT 0x23 +#define CMD_FW_DL_BEGIN 0x24 +#define CMD_FW_DL_END 0x25 +#define CMD_FW_SCATTER_WR 0x29 + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/anysee.c b/drivers/media/usb/dvb-usb-v2/anysee.c new file mode 100644 index 000000000000..ec540140c810 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/anysee.c @@ -0,0 +1,1327 @@ +/* + * DVB USB Linux driver for Anysee E30 DVB-C & DVB-T USB2.0 receiver + * + * Copyright (C) 2007 Antti Palosaari <crope@iki.fi> + * + * 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. + * + * TODO: + * - add smart card reader support for Conditional Access (CA) + * + * Card reader in Anysee is nothing more than ISO 7816 card reader. + * There is no hardware CAM in any Anysee device sold. + * In my understanding it should be implemented by making own module + * for ISO 7816 card reader, like dvb_ca_en50221 is implemented. This + * module registers serial interface that can be used to communicate + * with any ISO 7816 smart card. + * + * Any help according to implement serial smart card reader support + * is highly welcome! + */ + +#include "anysee.h" +#include "dvb-pll.h" +#include "tda1002x.h" +#include "mt352.h" +#include "mt352_priv.h" +#include "zl10353.h" +#include "tda18212.h" +#include "cx24116.h" +#include "stv0900.h" +#include "stv6110.h" +#include "isl6423.h" +#include "cxd2820r.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); +static DEFINE_MUTEX(anysee_usb_mutex); + +static int anysee_ctrl_msg(struct dvb_usb_device *d, u8 *sbuf, u8 slen, + u8 *rbuf, u8 rlen) +{ + struct anysee_state *state = d_to_priv(d); + int act_len, ret, i; + u8 buf[64]; + + memcpy(&buf[0], sbuf, slen); + buf[60] = state->seq++; + + mutex_lock(&anysee_usb_mutex); + + dev_dbg(&d->udev->dev, "%s: >>> %*ph\n", __func__, slen, buf); + + /* We need receive one message more after dvb_usb_generic_rw due + to weird transaction flow, which is 1 x send + 2 x receive. */ + ret = dvb_usbv2_generic_rw(d, buf, sizeof(buf), buf, sizeof(buf)); + if (ret) + goto error_unlock; + + /* TODO FIXME: dvb_usb_generic_rw() fails rarely with error code -32 + * (EPIPE, Broken pipe). Function supports currently msleep() as a + * parameter but I would not like to use it, since according to + * Documentation/timers/timers-howto.txt it should not be used such + * short, under < 20ms, sleeps. Repeating failed message would be + * better choice as not to add unwanted delays... + * Fixing that correctly is one of those or both; + * 1) use repeat if possible + * 2) add suitable delay + */ + + /* get answer, retry few times if error returned */ + for (i = 0; i < 3; i++) { + /* receive 2nd answer */ + ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev, + d->props->generic_bulk_ctrl_endpoint), buf, sizeof(buf), + &act_len, 2000); + + if (ret) { + dev_dbg(&d->udev->dev, "%s: recv bulk message " \ + "failed=%d\n", __func__, ret); + } else { + dev_dbg(&d->udev->dev, "%s: <<< %*ph\n", __func__, + rlen, buf); + + if (buf[63] != 0x4f) + dev_dbg(&d->udev->dev, "%s: cmd failed\n", + __func__); + + break; + } + } + + if (ret) { + /* all retries failed, it is fatal */ + dev_err(&d->udev->dev, "%s: recv bulk message failed=%d\n", + KBUILD_MODNAME, ret); + goto error_unlock; + } + + /* read request, copy returned data to return buf */ + if (rbuf && rlen) + memcpy(rbuf, buf, rlen); + +error_unlock: + mutex_unlock(&anysee_usb_mutex); + + return ret; +} + +static int anysee_read_reg(struct dvb_usb_device *d, u16 reg, u8 *val) +{ + u8 buf[] = {CMD_REG_READ, reg >> 8, reg & 0xff, 0x01}; + int ret; + ret = anysee_ctrl_msg(d, buf, sizeof(buf), val, 1); + dev_dbg(&d->udev->dev, "%s: reg=%04x val=%02x\n", __func__, reg, *val); + return ret; +} + +static int anysee_write_reg(struct dvb_usb_device *d, u16 reg, u8 val) +{ + u8 buf[] = {CMD_REG_WRITE, reg >> 8, reg & 0xff, 0x01, val}; + dev_dbg(&d->udev->dev, "%s: reg=%04x val=%02x\n", __func__, reg, val); + return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); +} + +/* write single register with mask */ +static int anysee_wr_reg_mask(struct dvb_usb_device *d, u16 reg, u8 val, + u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = anysee_read_reg(d, reg, &tmp); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return anysee_write_reg(d, reg, val); +} + +/* read single register with mask */ +static int anysee_rd_reg_mask(struct dvb_usb_device *d, u16 reg, u8 *val, + u8 mask) +{ + int ret, i; + u8 tmp; + + ret = anysee_read_reg(d, reg, &tmp); + if (ret) + return ret; + + tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = tmp >> i; + + return 0; +} + +static int anysee_get_hw_info(struct dvb_usb_device *d, u8 *id) +{ + u8 buf[] = {CMD_GET_HW_INFO}; + return anysee_ctrl_msg(d, buf, sizeof(buf), id, 3); +} + +static int anysee_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + u8 buf[] = {CMD_STREAMING_CTRL, (u8)onoff, 0x00}; + dev_dbg(&fe_to_d(fe)->udev->dev, "%s: onoff=%d\n", __func__, onoff); + return anysee_ctrl_msg(fe_to_d(fe), buf, sizeof(buf), NULL, 0); +} + +static int anysee_led_ctrl(struct dvb_usb_device *d, u8 mode, u8 interval) +{ + u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x01, mode, interval}; + dev_dbg(&d->udev->dev, "%s: state=%d interval=%d\n", __func__, + mode, interval); + return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); +} + +static int anysee_ir_ctrl(struct dvb_usb_device *d, u8 onoff) +{ + u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x02, onoff}; + dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff); + return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); +} + +/* I2C */ +static int anysee_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret = 0, inc, i = 0; + u8 buf[52]; /* 4 + 48 (I2C WR USB command header + I2C WR max) */ + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + while (i < num) { + if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { + if (msg[i].len > 2 || msg[i+1].len > 60) { + ret = -EOPNOTSUPP; + break; + } + buf[0] = CMD_I2C_READ; + buf[1] = (msg[i].addr << 1) | 0x01; + buf[2] = msg[i].buf[0]; + buf[3] = msg[i].buf[1]; + buf[4] = msg[i].len-1; + buf[5] = msg[i+1].len; + ret = anysee_ctrl_msg(d, buf, 6, msg[i+1].buf, + msg[i+1].len); + inc = 2; + } else { + if (msg[i].len > 48) { + ret = -EOPNOTSUPP; + break; + } + buf[0] = CMD_I2C_WRITE; + buf[1] = (msg[i].addr << 1); + buf[2] = msg[i].len; + buf[3] = 0x01; + memcpy(&buf[4], msg[i].buf, msg[i].len); + ret = anysee_ctrl_msg(d, buf, 4 + msg[i].len, NULL, 0); + inc = 1; + } + if (ret) + break; + + i += inc; + } + + mutex_unlock(&d->i2c_mutex); + + return ret ? ret : i; +} + +static u32 anysee_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm anysee_i2c_algo = { + .master_xfer = anysee_master_xfer, + .functionality = anysee_i2c_func, +}; + +static int anysee_mt352_demod_init(struct dvb_frontend *fe) +{ + static u8 clock_config[] = { CLOCK_CTL, 0x38, 0x28 }; + static u8 reset[] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg[] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg[] = { AGC_TARGET, 0x28, 0x20 }; + static u8 gpp_ctl_cfg[] = { GPP_CTL, 0x33 }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +/* Callbacks for DVB USB */ +static struct tda10023_config anysee_tda10023_config = { + .demod_address = (0x1a >> 1), + .invert = 0, + .xtal = 16000000, + .pll_m = 11, + .pll_p = 3, + .pll_n = 1, + .output_mode = TDA10023_OUTPUT_MODE_PARALLEL_C, + .deltaf = 0xfeeb, +}; + +static struct mt352_config anysee_mt352_config = { + .demod_address = (0x1e >> 1), + .demod_init = anysee_mt352_demod_init, +}; + +static struct zl10353_config anysee_zl10353_config = { + .demod_address = (0x1e >> 1), + .parallel_ts = 1, +}; + +static struct zl10353_config anysee_zl10353_tda18212_config2 = { + .demod_address = (0x1e >> 1), + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, + .no_tuner = 1, + .if2 = 41500, +}; + +static struct zl10353_config anysee_zl10353_tda18212_config = { + .demod_address = (0x18 >> 1), + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, + .no_tuner = 1, + .if2 = 41500, +}; + +static struct tda10023_config anysee_tda10023_tda18212_config = { + .demod_address = (0x1a >> 1), + .xtal = 16000000, + .pll_m = 12, + .pll_p = 3, + .pll_n = 1, + .output_mode = TDA10023_OUTPUT_MODE_PARALLEL_B, + .deltaf = 0xba02, +}; + +static struct tda18212_config anysee_tda18212_config = { + .i2c_address = (0xc0 >> 1), + .if_dvbt_6 = 4150, + .if_dvbt_7 = 4150, + .if_dvbt_8 = 4150, + .if_dvbc = 5000, +}; + +static struct tda18212_config anysee_tda18212_config2 = { + .i2c_address = 0x60 /* (0xc0 >> 1) */, + .if_dvbt_6 = 3550, + .if_dvbt_7 = 3700, + .if_dvbt_8 = 4150, + .if_dvbt2_6 = 3250, + .if_dvbt2_7 = 4000, + .if_dvbt2_8 = 4000, + .if_dvbc = 5000, +}; + +static struct cx24116_config anysee_cx24116_config = { + .demod_address = (0xaa >> 1), + .mpg_clk_pos_pol = 0x00, + .i2c_wr_max = 48, +}; + +static struct stv0900_config anysee_stv0900_config = { + .demod_address = (0xd0 >> 1), + .demod_mode = 0, + .xtal = 8000000, + .clkmode = 3, + .diseqc_mode = 2, + .tun1_maddress = 0, + .tun1_adc = 1, /* 1 Vpp */ + .path1_mode = 3, +}; + +static struct stv6110_config anysee_stv6110_config = { + .i2c_address = (0xc0 >> 1), + .mclk = 16000000, + .clk_div = 1, +}; + +static struct isl6423_config anysee_isl6423_config = { + .current_max = SEC_CURRENT_800m, + .curlim = SEC_CURRENT_LIM_OFF, + .mod_extern = 1, + .addr = (0x10 >> 1), +}; + +static struct cxd2820r_config anysee_cxd2820r_config = { + .i2c_address = 0x6d, /* (0xda >> 1) */ + .ts_mode = 0x38, +}; + +/* + * New USB device strings: Mfr=1, Product=2, SerialNumber=0 + * Manufacturer: AMT.CO.KR + * + * E30 VID=04b4 PID=861f HW=2 FW=2.1 Product=???????? + * PCB: ? + * parts: DNOS404ZH102A(MT352, DTT7579(?)) + * + * E30 VID=04b4 PID=861f HW=2 FW=2.1 "anysee-T(LP)" + * PCB: PCB 507T (rev1.61) + * parts: DNOS404ZH103A(ZL10353, DTT7579(?)) + * OEA=0a OEB=00 OEC=00 OED=ff OEE=00 + * IOA=45 IOB=ff IOC=00 IOD=ff IOE=00 + * + * E30 Plus VID=04b4 PID=861f HW=6 FW=1.0 "anysee" + * PCB: 507CD (rev1.1) + * parts: DNOS404ZH103A(ZL10353, DTT7579(?)), CST56I01 + * OEA=80 OEB=00 OEC=00 OED=ff OEE=fe + * IOA=4f IOB=ff IOC=00 IOD=06 IOE=01 + * IOD[0] ZL10353 1=enabled + * IOA[7] TS 0=enabled + * tuner is not behind ZL10353 I2C-gate (no care if gate disabled or not) + * + * E30 C Plus VID=04b4 PID=861f HW=10 FW=1.0 "anysee-DC(LP)" + * PCB: 507DC (rev0.2) + * parts: TDA10023, DTOS403IH102B TM, CST56I01 + * OEA=80 OEB=00 OEC=00 OED=ff OEE=fe + * IOA=4f IOB=ff IOC=00 IOD=26 IOE=01 + * IOD[0] TDA10023 1=enabled + * + * E30 S2 Plus VID=04b4 PID=861f HW=11 FW=0.1 "anysee-S2(LP)" + * PCB: 507SI (rev2.1) + * parts: BS2N10WCC01(CX24116, CX24118), ISL6423, TDA8024 + * OEA=80 OEB=00 OEC=ff OED=ff OEE=fe + * IOA=4d IOB=ff IOC=00 IOD=26 IOE=01 + * IOD[0] CX24116 1=enabled + * + * E30 C Plus VID=1c73 PID=861f HW=15 FW=1.2 "anysee-FA(LP)" + * PCB: 507FA (rev0.4) + * parts: TDA10023, DTOS403IH102B TM, TDA8024 + * OEA=80 OEB=00 OEC=ff OED=ff OEE=ff + * IOA=4d IOB=ff IOC=00 IOD=00 IOE=c0 + * IOD[5] TDA10023 1=enabled + * IOE[0] tuner 1=enabled + * + * E30 Combo Plus VID=1c73 PID=861f HW=15 FW=1.2 "anysee-FA(LP)" + * PCB: 507FA (rev1.1) + * parts: ZL10353, TDA10023, DTOS403IH102B TM, TDA8024 + * OEA=80 OEB=00 OEC=ff OED=ff OEE=ff + * IOA=4d IOB=ff IOC=00 IOD=00 IOE=c0 + * DVB-C: + * IOD[5] TDA10023 1=enabled + * IOE[0] tuner 1=enabled + * DVB-T: + * IOD[0] ZL10353 1=enabled + * IOE[0] tuner 0=enabled + * tuner is behind ZL10353 I2C-gate + * + * E7 TC VID=1c73 PID=861f HW=18 FW=0.7 AMTCI=0.5 "anysee-E7TC(LP)" + * PCB: 508TC (rev0.6) + * parts: ZL10353, TDA10023, DNOD44CDH086A(TDA18212) + * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff + * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4 + * IOA[7] TS 1=enabled + * IOE[4] TDA18212 1=enabled + * DVB-C: + * IOD[6] ZL10353 0=disabled + * IOD[5] TDA10023 1=enabled + * IOE[0] IF 1=enabled + * DVB-T: + * IOD[5] TDA10023 0=disabled + * IOD[6] ZL10353 1=enabled + * IOE[0] IF 0=enabled + * + * E7 S2 VID=1c73 PID=861f HW=19 FW=0.4 AMTCI=0.5 "anysee-E7S2(LP)" + * PCB: 508S2 (rev0.7) + * parts: DNBU10512IST(STV0903, STV6110), ISL6423 + * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff + * IOA=4d IOB=00 IOC=c4 IOD=08 IOE=e4 + * IOA[7] TS 1=enabled + * IOE[5] STV0903 1=enabled + * + * E7 T2C VID=1c73 PID=861f HW=20 FW=0.1 AMTCI=0.5 "anysee-E7T2C(LP)" + * PCB: 508T2C (rev0.3) + * parts: DNOQ44QCH106A(CXD2820R, TDA18212), TDA8024 + * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff + * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4 + * IOA[7] TS 1=enabled + * IOE[5] CXD2820R 1=enabled + * + * E7 PTC VID=1c73 PID=861f HW=21 FW=0.1 AMTCI=?? "anysee-E7PTC(LP)" + * PCB: 508PTC (rev0.5) + * parts: ZL10353, TDA10023, DNOD44CDH086A(TDA18212) + * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff + * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4 + * IOA[7] TS 1=enabled + * IOE[4] TDA18212 1=enabled + * DVB-C: + * IOD[6] ZL10353 0=disabled + * IOD[5] TDA10023 1=enabled + * IOE[0] IF 1=enabled + * DVB-T: + * IOD[5] TDA10023 0=disabled + * IOD[6] ZL10353 1=enabled + * IOE[0] IF 0=enabled + * + * E7 PS2 VID=1c73 PID=861f HW=22 FW=0.1 AMTCI=?? "anysee-E7PS2(LP)" + * PCB: 508PS2 (rev0.4) + * parts: DNBU10512IST(STV0903, STV6110), ISL6423 + * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff + * IOA=4d IOB=00 IOC=c4 IOD=08 IOE=e4 + * IOA[7] TS 1=enabled + * IOE[5] STV0903 1=enabled + */ + +static int anysee_read_config(struct dvb_usb_device *d) +{ + struct anysee_state *state = d_to_priv(d); + int ret; + u8 hw_info[3]; + + /* + * Check which hardware we have. + * We must do this call two times to get reliable values (hw/fw bug). + */ + ret = anysee_get_hw_info(d, hw_info); + if (ret) + goto error; + + ret = anysee_get_hw_info(d, hw_info); + if (ret) + goto error; + + /* + * Meaning of these info bytes are guessed. + */ + dev_info(&d->udev->dev, "%s: firmware version %d.%d hardware id %d\n", + KBUILD_MODNAME, hw_info[1], hw_info[2], hw_info[0]); + + state->hw = hw_info[0]; +error: + return ret; +} + +/* external I2C gate used for DNOD44CDH086A(TDA18212) tuner module */ +static int anysee_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + /* enable / disable tuner access on IOE[4] */ + return anysee_wr_reg_mask(fe_to_d(fe), REG_IOE, (enable << 4), 0x10); +} + +static int anysee_frontend_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct anysee_state *state = fe_to_priv(fe); + struct dvb_usb_device *d = fe_to_d(fe); + int ret; + dev_dbg(&d->udev->dev, "%s: fe=%d onoff=%d\n", __func__, fe->id, onoff); + + /* no frontend sleep control */ + if (onoff == 0) + return 0; + + switch (state->hw) { + case ANYSEE_HW_507FA: /* 15 */ + /* E30 Combo Plus */ + /* E30 C Plus */ + + if (fe->id == 0) { + /* disable DVB-T demod on IOD[0] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 0), 0x01); + if (ret) + goto error; + + /* enable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20); + if (ret) + goto error; + + /* enable DVB-C tuner on IOE[0] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 0), 0x01); + if (ret) + goto error; + } else { + /* disable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20); + if (ret) + goto error; + + /* enable DVB-T demod on IOD[0] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01); + if (ret) + goto error; + + /* enable DVB-T tuner on IOE[0] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (0 << 0), 0x01); + if (ret) + goto error; + } + + break; + case ANYSEE_HW_508TC: /* 18 */ + case ANYSEE_HW_508PTC: /* 21 */ + /* E7 TC */ + /* E7 PTC */ + + if (fe->id == 0) { + /* disable DVB-T demod on IOD[6] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 6), 0x40); + if (ret) + goto error; + + /* enable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20); + if (ret) + goto error; + + /* enable IF route on IOE[0] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 0), 0x01); + if (ret) + goto error; + } else { + /* disable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20); + if (ret) + goto error; + + /* enable DVB-T demod on IOD[6] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 6), 0x40); + if (ret) + goto error; + + /* enable IF route on IOE[0] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (0 << 0), 0x01); + if (ret) + goto error; + } + + break; + default: + ret = 0; + } + +error: + return ret; +} + +static int anysee_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct anysee_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret; + u8 tmp; + struct i2c_msg msg[2] = { + { + .addr = anysee_tda18212_config.i2c_address, + .flags = 0, + .len = 1, + .buf = "\x00", + }, { + .addr = anysee_tda18212_config.i2c_address, + .flags = I2C_M_RD, + .len = 1, + .buf = &tmp, + } + }; + + switch (state->hw) { + case ANYSEE_HW_507T: /* 2 */ + /* E30 */ + + /* attach demod */ + adap->fe[0] = dvb_attach(mt352_attach, &anysee_mt352_config, + &d->i2c_adap); + if (adap->fe[0]) + break; + + /* attach demod */ + adap->fe[0] = dvb_attach(zl10353_attach, &anysee_zl10353_config, + &d->i2c_adap); + + break; + case ANYSEE_HW_507CD: /* 6 */ + /* E30 Plus */ + + /* enable DVB-T demod on IOD[0] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01); + if (ret) + goto error; + + /* enable transport stream on IOA[7] */ + ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80); + if (ret) + goto error; + + /* attach demod */ + adap->fe[0] = dvb_attach(zl10353_attach, &anysee_zl10353_config, + &d->i2c_adap); + + break; + case ANYSEE_HW_507DC: /* 10 */ + /* E30 C Plus */ + + /* enable DVB-C demod on IOD[0] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01); + if (ret) + goto error; + + /* attach demod */ + adap->fe[0] = dvb_attach(tda10023_attach, + &anysee_tda10023_config, &d->i2c_adap, 0x48); + + break; + case ANYSEE_HW_507SI: /* 11 */ + /* E30 S2 Plus */ + + /* enable DVB-S/S2 demod on IOD[0] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01); + if (ret) + goto error; + + /* attach demod */ + adap->fe[0] = dvb_attach(cx24116_attach, &anysee_cx24116_config, + &d->i2c_adap); + + break; + case ANYSEE_HW_507FA: /* 15 */ + /* E30 Combo Plus */ + /* E30 C Plus */ + + /* enable tuner on IOE[4] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 4), 0x10); + if (ret) + goto error; + + /* probe TDA18212 */ + tmp = 0; + ret = i2c_transfer(&d->i2c_adap, msg, 2); + if (ret == 2 && tmp == 0xc7) + dev_dbg(&d->udev->dev, "%s: TDA18212 found\n", + __func__); + else + tmp = 0; + + /* disable tuner on IOE[4] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (0 << 4), 0x10); + if (ret) + goto error; + + /* disable DVB-T demod on IOD[0] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 0), 0x01); + if (ret) + goto error; + + /* enable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20); + if (ret) + goto error; + + /* attach demod */ + if (tmp == 0xc7) { + /* TDA18212 config */ + adap->fe[0] = dvb_attach(tda10023_attach, + &anysee_tda10023_tda18212_config, + &d->i2c_adap, 0x48); + + /* I2C gate for DNOD44CDH086A(TDA18212) tuner module */ + if (adap->fe[0]) + adap->fe[0]->ops.i2c_gate_ctrl = + anysee_i2c_gate_ctrl; + } else { + /* PLL config */ + adap->fe[0] = dvb_attach(tda10023_attach, + &anysee_tda10023_config, + &d->i2c_adap, 0x48); + } + + /* break out if first frontend attaching fails */ + if (!adap->fe[0]) + break; + + /* disable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20); + if (ret) + goto error; + + /* enable DVB-T demod on IOD[0] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01); + if (ret) + goto error; + + /* attach demod */ + if (tmp == 0xc7) { + /* TDA18212 config */ + adap->fe[1] = dvb_attach(zl10353_attach, + &anysee_zl10353_tda18212_config2, + &d->i2c_adap); + + /* I2C gate for DNOD44CDH086A(TDA18212) tuner module */ + if (adap->fe[1]) + adap->fe[1]->ops.i2c_gate_ctrl = + anysee_i2c_gate_ctrl; + } else { + /* PLL config */ + adap->fe[1] = dvb_attach(zl10353_attach, + &anysee_zl10353_config, + &d->i2c_adap); + } + + break; + case ANYSEE_HW_508TC: /* 18 */ + case ANYSEE_HW_508PTC: /* 21 */ + /* E7 TC */ + /* E7 PTC */ + + /* disable DVB-T demod on IOD[6] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 6), 0x40); + if (ret) + goto error; + + /* enable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20); + if (ret) + goto error; + + /* attach demod */ + adap->fe[0] = dvb_attach(tda10023_attach, + &anysee_tda10023_tda18212_config, + &d->i2c_adap, 0x48); + + /* I2C gate for DNOD44CDH086A(TDA18212) tuner module */ + if (adap->fe[0]) + adap->fe[0]->ops.i2c_gate_ctrl = anysee_i2c_gate_ctrl; + + /* break out if first frontend attaching fails */ + if (!adap->fe[0]) + break; + + /* disable DVB-C demod on IOD[5] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20); + if (ret) + goto error; + + /* enable DVB-T demod on IOD[6] */ + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 6), 0x40); + if (ret) + goto error; + + /* attach demod */ + adap->fe[1] = dvb_attach(zl10353_attach, + &anysee_zl10353_tda18212_config, + &d->i2c_adap); + + /* I2C gate for DNOD44CDH086A(TDA18212) tuner module */ + if (adap->fe[1]) + adap->fe[1]->ops.i2c_gate_ctrl = anysee_i2c_gate_ctrl; + + state->has_ci = true; + + break; + case ANYSEE_HW_508S2: /* 19 */ + case ANYSEE_HW_508PS2: /* 22 */ + /* E7 S2 */ + /* E7 PS2 */ + + /* enable DVB-S/S2 demod on IOE[5] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 5), 0x20); + if (ret) + goto error; + + /* attach demod */ + adap->fe[0] = dvb_attach(stv0900_attach, + &anysee_stv0900_config, &d->i2c_adap, 0); + + state->has_ci = true; + + break; + case ANYSEE_HW_508T2C: /* 20 */ + /* E7 T2C */ + + /* enable DVB-T/T2/C demod on IOE[5] */ + ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 5), 0x20); + if (ret) + goto error; + + /* attach demod */ + adap->fe[0] = dvb_attach(cxd2820r_attach, + &anysee_cxd2820r_config, &d->i2c_adap, NULL); + + state->has_ci = true; + + break; + } + + if (!adap->fe[0]) { + /* we have no frontend :-( */ + ret = -ENODEV; + dev_err(&d->udev->dev, "%s: Unsupported Anysee version. " \ + "Please report the " \ + "<linux-media@vger.kernel.org>.\n", + KBUILD_MODNAME); + } +error: + return ret; +} + +static int anysee_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct anysee_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + struct dvb_frontend *fe; + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + switch (state->hw) { + case ANYSEE_HW_507T: /* 2 */ + /* E30 */ + + /* attach tuner */ + fe = dvb_attach(dvb_pll_attach, adap->fe[0], (0xc2 >> 1), NULL, + DVB_PLL_THOMSON_DTT7579); + + break; + case ANYSEE_HW_507CD: /* 6 */ + /* E30 Plus */ + + /* attach tuner */ + fe = dvb_attach(dvb_pll_attach, adap->fe[0], (0xc2 >> 1), + &d->i2c_adap, DVB_PLL_THOMSON_DTT7579); + + break; + case ANYSEE_HW_507DC: /* 10 */ + /* E30 C Plus */ + + /* attach tuner */ + fe = dvb_attach(dvb_pll_attach, adap->fe[0], (0xc0 >> 1), + &d->i2c_adap, DVB_PLL_SAMSUNG_DTOS403IH102A); + + break; + case ANYSEE_HW_507SI: /* 11 */ + /* E30 S2 Plus */ + + /* attach LNB controller */ + fe = dvb_attach(isl6423_attach, adap->fe[0], &d->i2c_adap, + &anysee_isl6423_config); + + break; + case ANYSEE_HW_507FA: /* 15 */ + /* E30 Combo Plus */ + /* E30 C Plus */ + + /* Try first attach TDA18212 silicon tuner on IOE[4], if that + * fails attach old simple PLL. */ + + /* attach tuner */ + fe = dvb_attach(tda18212_attach, adap->fe[0], &d->i2c_adap, + &anysee_tda18212_config); + + if (fe && adap->fe[1]) { + /* attach tuner for 2nd FE */ + fe = dvb_attach(tda18212_attach, adap->fe[1], + &d->i2c_adap, &anysee_tda18212_config); + break; + } else if (fe) { + break; + } + + /* attach tuner */ + fe = dvb_attach(dvb_pll_attach, adap->fe[0], (0xc0 >> 1), + &d->i2c_adap, DVB_PLL_SAMSUNG_DTOS403IH102A); + + if (fe && adap->fe[1]) { + /* attach tuner for 2nd FE */ + fe = dvb_attach(dvb_pll_attach, adap->fe[0], + (0xc0 >> 1), &d->i2c_adap, + DVB_PLL_SAMSUNG_DTOS403IH102A); + } + + break; + case ANYSEE_HW_508TC: /* 18 */ + case ANYSEE_HW_508PTC: /* 21 */ + /* E7 TC */ + /* E7 PTC */ + + /* attach tuner */ + fe = dvb_attach(tda18212_attach, adap->fe[0], &d->i2c_adap, + &anysee_tda18212_config); + + if (fe) { + /* attach tuner for 2nd FE */ + fe = dvb_attach(tda18212_attach, adap->fe[1], + &d->i2c_adap, &anysee_tda18212_config); + } + + break; + case ANYSEE_HW_508S2: /* 19 */ + case ANYSEE_HW_508PS2: /* 22 */ + /* E7 S2 */ + /* E7 PS2 */ + + /* attach tuner */ + fe = dvb_attach(stv6110_attach, adap->fe[0], + &anysee_stv6110_config, &d->i2c_adap); + + if (fe) { + /* attach LNB controller */ + fe = dvb_attach(isl6423_attach, adap->fe[0], + &d->i2c_adap, &anysee_isl6423_config); + } + + break; + + case ANYSEE_HW_508T2C: /* 20 */ + /* E7 T2C */ + + /* attach tuner */ + fe = dvb_attach(tda18212_attach, adap->fe[0], &d->i2c_adap, + &anysee_tda18212_config2); + + break; + default: + fe = NULL; + } + + if (fe) + ret = 0; + else + ret = -ENODEV; + + return ret; +} + +static int anysee_rc_query(struct dvb_usb_device *d) +{ + u8 buf[] = {CMD_GET_IR_CODE}; + u8 ircode[2]; + int ret; + + /* Remote controller is basic NEC using address byte 0x08. + Anysee device RC query returns only two bytes, status and code, + address byte is dropped. Also it does not return any value for + NEC RCs having address byte other than 0x08. Due to that, we + cannot use that device as standard NEC receiver. + It could be possible make hack which reads whole code directly + from device memory... */ + + ret = anysee_ctrl_msg(d, buf, sizeof(buf), ircode, sizeof(ircode)); + if (ret) + return ret; + + if (ircode[0]) { + dev_dbg(&d->udev->dev, "%s: key pressed %02x\n", __func__, + ircode[1]); + rc_keydown(d->rc_dev, 0x08 << 8 | ircode[1], 0); + } + + return 0; +} + +static int anysee_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) +{ + rc->allowed_protos = RC_TYPE_NEC; + rc->query = anysee_rc_query; + rc->interval = 250; /* windows driver uses 500ms */ + + return 0; +} + +static int anysee_ci_read_attribute_mem(struct dvb_ca_en50221 *ci, int slot, + int addr) +{ + struct dvb_usb_device *d = ci->data; + int ret; + u8 buf[] = {CMD_CI, 0x02, 0x40 | addr >> 8, addr & 0xff, 0x00, 1}; + u8 val; + + ret = anysee_ctrl_msg(d, buf, sizeof(buf), &val, 1); + if (ret) + return ret; + + return val; +} + +static int anysee_ci_write_attribute_mem(struct dvb_ca_en50221 *ci, int slot, + int addr, u8 val) +{ + struct dvb_usb_device *d = ci->data; + int ret; + u8 buf[] = {CMD_CI, 0x03, 0x40 | addr >> 8, addr & 0xff, 0x00, 1, val}; + + ret = anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); + if (ret) + return ret; + + return 0; +} + +static int anysee_ci_read_cam_control(struct dvb_ca_en50221 *ci, int slot, + u8 addr) +{ + struct dvb_usb_device *d = ci->data; + int ret; + u8 buf[] = {CMD_CI, 0x04, 0x40, addr, 0x00, 1}; + u8 val; + + ret = anysee_ctrl_msg(d, buf, sizeof(buf), &val, 1); + if (ret) + return ret; + + return val; +} + +static int anysee_ci_write_cam_control(struct dvb_ca_en50221 *ci, int slot, + u8 addr, u8 val) +{ + struct dvb_usb_device *d = ci->data; + int ret; + u8 buf[] = {CMD_CI, 0x05, 0x40, addr, 0x00, 1, val}; + + ret = anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); + if (ret) + return ret; + + return 0; +} + +static int anysee_ci_slot_reset(struct dvb_ca_en50221 *ci, int slot) +{ + struct dvb_usb_device *d = ci->data; + int ret; + struct anysee_state *state = d_to_priv(d); + + state->ci_cam_ready = jiffies + msecs_to_jiffies(1000); + + ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80); + if (ret) + return ret; + + msleep(300); + + ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80); + if (ret) + return ret; + + return 0; +} + +static int anysee_ci_slot_shutdown(struct dvb_ca_en50221 *ci, int slot) +{ + struct dvb_usb_device *d = ci->data; + int ret; + + ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80); + if (ret) + return ret; + + msleep(30); + + ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80); + if (ret) + return ret; + + return 0; +} + +static int anysee_ci_slot_ts_enable(struct dvb_ca_en50221 *ci, int slot) +{ + struct dvb_usb_device *d = ci->data; + int ret; + + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 1), 0x02); + if (ret) + return ret; + + return 0; +} + +static int anysee_ci_poll_slot_status(struct dvb_ca_en50221 *ci, int slot, + int open) +{ + struct dvb_usb_device *d = ci->data; + struct anysee_state *state = d_to_priv(d); + int ret; + u8 tmp; + + ret = anysee_rd_reg_mask(d, REG_IOC, &tmp, 0x40); + if (ret) + return ret; + + if (tmp == 0) { + ret = DVB_CA_EN50221_POLL_CAM_PRESENT; + if (time_after(jiffies, state->ci_cam_ready)) + ret |= DVB_CA_EN50221_POLL_CAM_READY; + } + + return ret; +} + +static int anysee_ci_init(struct dvb_usb_device *d) +{ + struct anysee_state *state = d_to_priv(d); + int ret; + + state->ci.owner = THIS_MODULE; + state->ci.read_attribute_mem = anysee_ci_read_attribute_mem; + state->ci.write_attribute_mem = anysee_ci_write_attribute_mem; + state->ci.read_cam_control = anysee_ci_read_cam_control; + state->ci.write_cam_control = anysee_ci_write_cam_control; + state->ci.slot_reset = anysee_ci_slot_reset; + state->ci.slot_shutdown = anysee_ci_slot_shutdown; + state->ci.slot_ts_enable = anysee_ci_slot_ts_enable; + state->ci.poll_slot_status = anysee_ci_poll_slot_status; + state->ci.data = d; + + ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80); + if (ret) + return ret; + + ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 2)|(0 << 1)|(0 << 0), 0x07); + if (ret) + return ret; + + ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 2)|(1 << 1)|(1 << 0), 0x07); + if (ret) + return ret; + + ret = dvb_ca_en50221_init(&d->adapter[0].dvb_adap, &state->ci, 0, 1); + if (ret) + return ret; + + state->ci_attached = true; + + return 0; +} + +static void anysee_ci_release(struct dvb_usb_device *d) +{ + struct anysee_state *state = d_to_priv(d); + + /* detach CI */ + if (state->ci_attached) + dvb_ca_en50221_release(&state->ci); + + return; +} + +static int anysee_init(struct dvb_usb_device *d) +{ + struct anysee_state *state = d_to_priv(d); + int ret; + + /* There is one interface with two alternate settings. + Alternate setting 0 is for bulk transfer. + Alternate setting 1 is for isochronous transfer. + We use bulk transfer (alternate setting 0). */ + ret = usb_set_interface(d->udev, 0, 0); + if (ret) + return ret; + + /* LED light */ + ret = anysee_led_ctrl(d, 0x01, 0x03); + if (ret) + return ret; + + /* enable IR */ + ret = anysee_ir_ctrl(d, 1); + if (ret) + return ret; + + /* attach CI */ + if (state->has_ci) { + ret = anysee_ci_init(d); + if (ret) + return ret; + } + + return 0; +} + +static void anysee_exit(struct dvb_usb_device *d) +{ + return anysee_ci_release(d); +} + +/* DVB USB Driver stuff */ +static struct dvb_usb_device_properties anysee_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct anysee_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .i2c_algo = &anysee_i2c_algo, + .read_config = anysee_read_config, + .frontend_attach = anysee_frontend_attach, + .tuner_attach = anysee_tuner_attach, + .init = anysee_init, + .get_rc_config = anysee_get_rc_config, + .frontend_ctrl = anysee_frontend_ctrl, + .streaming_ctrl = anysee_streaming_ctrl, + .exit = anysee_exit, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 16 * 512), + } + } +}; + +static const struct usb_device_id anysee_id_table[] = { + { DVB_USB_DEVICE(USB_VID_CYPRESS, USB_PID_ANYSEE, + &anysee_props, "Anysee", RC_MAP_ANYSEE) }, + { DVB_USB_DEVICE(USB_VID_AMT, USB_PID_ANYSEE, + &anysee_props, "Anysee", RC_MAP_ANYSEE) }, + { } +}; +MODULE_DEVICE_TABLE(usb, anysee_id_table); + +static struct usb_driver anysee_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = anysee_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(anysee_usb_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Driver Anysee E30 DVB-C & DVB-T USB2.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/dvb-usb-v2/anysee.h b/drivers/media/usb/dvb-usb-v2/anysee.h new file mode 100644 index 000000000000..c1a4273f14ff --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/anysee.h @@ -0,0 +1,326 @@ +/* + * DVB USB Linux driver for Anysee E30 DVB-C & DVB-T USB2.0 receiver + * + * Copyright (C) 2007 Antti Palosaari <crope@iki.fi> + * + * 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. + * + * TODO: + * - add smart card reader support for Conditional Access (CA) + * + * Card reader in Anysee is nothing more than ISO 7816 card reader. + * There is no hardware CAM in any Anysee device sold. + * In my understanding it should be implemented by making own module + * for ISO 7816 card reader, like dvb_ca_en50221 is implemented. This + * module registers serial interface that can be used to communicate + * with any ISO 7816 smart card. + * + * Any help according to implement serial smart card reader support + * is highly welcome! + */ + +#ifndef _DVB_USB_ANYSEE_H_ +#define _DVB_USB_ANYSEE_H_ + +#define DVB_USB_LOG_PREFIX "anysee" +#include "dvb_usb.h" +#include "dvb_ca_en50221.h" + +enum cmd { + CMD_I2C_READ = 0x33, + CMD_I2C_WRITE = 0x31, + CMD_REG_READ = 0xb0, + CMD_REG_WRITE = 0xb1, + CMD_STREAMING_CTRL = 0x12, + CMD_LED_AND_IR_CTRL = 0x16, + CMD_GET_IR_CODE = 0x41, + CMD_GET_HW_INFO = 0x19, + CMD_SMARTCARD = 0x34, + CMD_CI = 0x37, +}; + +struct anysee_state { + u8 hw; /* PCB ID */ + u8 seq; + u8 fe_id:1; /* frondend ID */ + u8 has_ci:1; + u8 ci_attached:1; + struct dvb_ca_en50221 ci; + unsigned long ci_cam_ready; /* jiffies */ +}; + +#define ANYSEE_HW_507T 2 /* E30 */ +#define ANYSEE_HW_507CD 6 /* E30 Plus */ +#define ANYSEE_HW_507DC 10 /* E30 C Plus */ +#define ANYSEE_HW_507SI 11 /* E30 S2 Plus */ +#define ANYSEE_HW_507FA 15 /* E30 Combo Plus / E30 C Plus */ +#define ANYSEE_HW_508TC 18 /* E7 TC */ +#define ANYSEE_HW_508S2 19 /* E7 S2 */ +#define ANYSEE_HW_508T2C 20 /* E7 T2C */ +#define ANYSEE_HW_508PTC 21 /* E7 PTC Plus */ +#define ANYSEE_HW_508PS2 22 /* E7 PS2 Plus */ + +#define REG_IOA 0x80 /* Port A (bit addressable) */ +#define REG_IOB 0x90 /* Port B (bit addressable) */ +#define REG_IOC 0xa0 /* Port C (bit addressable) */ +#define REG_IOD 0xb0 /* Port D (bit addressable) */ +#define REG_IOE 0xb1 /* Port E (NOT bit addressable) */ +#define REG_OEA 0xb2 /* Port A Output Enable */ +#define REG_OEB 0xb3 /* Port B Output Enable */ +#define REG_OEC 0xb4 /* Port C Output Enable */ +#define REG_OED 0xb5 /* Port D Output Enable */ +#define REG_OEE 0xb6 /* Port E Output Enable */ + +#endif + +/*************************************************************************** + * USB API description (reverse engineered) + *************************************************************************** + +Transaction flow: +================= +BULK[00001] >>> REQUEST PACKET 64 bytes +BULK[00081] <<< REPLY PACKET #1 64 bytes (PREVIOUS TRANSACTION REPLY) +BULK[00081] <<< REPLY PACKET #2 64 bytes (CURRENT TRANSACTION REPLY) + +General reply packet(s) are always used if not own reply defined. + +============================================================================ +| 00-63 | GENERAL REPLY PACKET #1 (PREVIOUS REPLY) +============================================================================ +| 00 | reply data (if any) from previous transaction +| | Just same reply packet as returned during previous transaction. +| | Needed only if reply is missed in previous transaction. +| | Just skip normally. +---------------------------------------------------------------------------- +| 01-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | GENERAL REPLY PACKET #2 (CURRENT REPLY) +============================================================================ +| 00 | reply data (if any) +---------------------------------------------------------------------------- +| 01-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | I2C WRITE REQUEST PACKET +============================================================================ +| 00 | 0x31 I2C write command +---------------------------------------------------------------------------- +| 01 | i2c address +---------------------------------------------------------------------------- +| 02 | data length +| | 0x02 (for typical I2C reg / val pair) +---------------------------------------------------------------------------- +| 03 | 0x01 +---------------------------------------------------------------------------- +| 04- | data +---------------------------------------------------------------------------- +| -59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | I2C READ REQUEST PACKET +============================================================================ +| 00 | 0x33 I2C read command +---------------------------------------------------------------------------- +| 01 | i2c address + 1 +---------------------------------------------------------------------------- +| 02 | register +---------------------------------------------------------------------------- +| 03 | 0x00 +---------------------------------------------------------------------------- +| 04 | 0x00 +---------------------------------------------------------------------------- +| 05 | data length +---------------------------------------------------------------------------- +| 06-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | USB CONTROLLER REGISTER WRITE REQUEST PACKET +============================================================================ +| 00 | 0xb1 register write command +---------------------------------------------------------------------------- +| 01-02 | register +---------------------------------------------------------------------------- +| 03 | 0x01 +---------------------------------------------------------------------------- +| 04 | value +---------------------------------------------------------------------------- +| 05-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | USB CONTROLLER REGISTER READ REQUEST PACKET +============================================================================ +| 00 | 0xb0 register read command +---------------------------------------------------------------------------- +| 01-02 | register +---------------------------------------------------------------------------- +| 03 | 0x01 +---------------------------------------------------------------------------- +| 04-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | LED CONTROL REQUEST PACKET +============================================================================ +| 00 | 0x16 LED and IR control command +---------------------------------------------------------------------------- +| 01 | 0x01 (LED) +---------------------------------------------------------------------------- +| 03 | 0x00 blink +| | 0x01 lights continuously +---------------------------------------------------------------------------- +| 04 | blink interval +| | 0x00 fastest (looks like LED lights continuously) +| | 0xff slowest +---------------------------------------------------------------------------- +| 05-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | IR CONTROL REQUEST PACKET +============================================================================ +| 00 | 0x16 LED and IR control command +---------------------------------------------------------------------------- +| 01 | 0x02 (IR) +---------------------------------------------------------------------------- +| 03 | 0x00 IR disabled +| | 0x01 IR enabled +---------------------------------------------------------------------------- +| 04-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | STREAMING CONTROL REQUEST PACKET +============================================================================ +| 00 | 0x12 streaming control command +---------------------------------------------------------------------------- +| 01 | 0x00 streaming disabled +| | 0x01 streaming enabled +---------------------------------------------------------------------------- +| 02 | 0x00 +---------------------------------------------------------------------------- +| 03-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | REMOTE CONTROL REQUEST PACKET +============================================================================ +| 00 | 0x41 remote control command +---------------------------------------------------------------------------- +| 01-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | REMOTE CONTROL REPLY PACKET +============================================================================ +| 00 | 0x00 code not received +| | 0x01 code received +---------------------------------------------------------------------------- +| 01 | remote control code +---------------------------------------------------------------------------- +| 02-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | GET HARDWARE INFO REQUEST PACKET +============================================================================ +| 00 | 0x19 get hardware info command +---------------------------------------------------------------------------- +| 01-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | GET HARDWARE INFO REPLY PACKET +============================================================================ +| 00 | hardware id +---------------------------------------------------------------------------- +| 01-02 | firmware version +---------------------------------------------------------------------------- +| 03-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +============================================================================ +| 00-63 | SMART CARD READER PACKET +============================================================================ +| 00 | 0x34 smart card reader command +---------------------------------------------------------------------------- +| xx | +---------------------------------------------------------------------------- +| xx-59 | don't care +---------------------------------------------------------------------------- +| 60 | packet sequence number +---------------------------------------------------------------------------- +| 61-63 | don't care +---------------------------------------------------------------------------- + +*/ diff --git a/drivers/media/usb/dvb-usb-v2/au6610.c b/drivers/media/usb/dvb-usb-v2/au6610.c new file mode 100644 index 000000000000..ae6a671b7fd5 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/au6610.c @@ -0,0 +1,213 @@ +/* + * DVB USB Linux driver for Alcor Micro AU6610 DVB-T USB2.0. + * + * Copyright (C) 2006 Antti Palosaari <crope@iki.fi> + * + * 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 "au6610.h" +#include "zl10353.h" +#include "qt1010.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int au6610_usb_msg(struct dvb_usb_device *d, u8 operation, u8 addr, + u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) +{ + int ret; + u16 index; + u8 *usb_buf; + + /* + * allocate enough for all known requests, + * read returns 5 and write 6 bytes + */ + usb_buf = kmalloc(6, GFP_KERNEL); + if (!usb_buf) + return -ENOMEM; + + switch (wlen) { + case 1: + index = wbuf[0] << 8; + break; + case 2: + index = wbuf[0] << 8; + index += wbuf[1]; + break; + default: + dev_err(&d->udev->dev, "%s: wlen=%d, aborting\n", + KBUILD_MODNAME, wlen); + ret = -EINVAL; + goto error; + } + + ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), operation, + USB_TYPE_VENDOR|USB_DIR_IN, addr << 1, index, + usb_buf, 6, AU6610_USB_TIMEOUT); + + dvb_usb_dbg_usb_control_msg(d->udev, operation, + (USB_TYPE_VENDOR|USB_DIR_IN), addr << 1, index, + usb_buf, 6); + + if (ret < 0) + goto error; + + switch (operation) { + case AU6610_REQ_I2C_READ: + case AU6610_REQ_USB_READ: + /* requested value is always 5th byte in buffer */ + rbuf[0] = usb_buf[4]; + } +error: + kfree(usb_buf); + return ret; +} + +static int au6610_i2c_msg(struct dvb_usb_device *d, u8 addr, + u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) +{ + u8 request; + u8 wo = (rbuf == NULL || rlen == 0); /* write-only */ + + if (wo) { + request = AU6610_REQ_I2C_WRITE; + } else { /* rw */ + request = AU6610_REQ_I2C_READ; + } + + return au6610_usb_msg(d, request, addr, wbuf, wlen, rbuf, rlen); +} + + +/* I2C */ +static int au6610_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int i; + + if (num > 2) + return -EINVAL; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + for (i = 0; i < num; i++) { + /* write/read request */ + if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) { + if (au6610_i2c_msg(d, msg[i].addr, msg[i].buf, + msg[i].len, msg[i+1].buf, + msg[i+1].len) < 0) + break; + i++; + } else if (au6610_i2c_msg(d, msg[i].addr, msg[i].buf, + msg[i].len, NULL, 0) < 0) + break; + } + + mutex_unlock(&d->i2c_mutex); + return i; +} + + +static u32 au6610_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm au6610_i2c_algo = { + .master_xfer = au6610_i2c_xfer, + .functionality = au6610_i2c_func, +}; + +/* Callbacks for DVB USB */ +static struct zl10353_config au6610_zl10353_config = { + .demod_address = 0x0f, + .no_tuner = 1, + .parallel_ts = 1, +}; + +static int au6610_zl10353_frontend_attach(struct dvb_usb_adapter *adap) +{ + adap->fe[0] = dvb_attach(zl10353_attach, &au6610_zl10353_config, + &adap_to_d(adap)->i2c_adap); + if (adap->fe[0] == NULL) + return -ENODEV; + + return 0; +} + +static struct qt1010_config au6610_qt1010_config = { + .i2c_address = 0x62 +}; + +static int au6610_qt1010_tuner_attach(struct dvb_usb_adapter *adap) +{ + return dvb_attach(qt1010_attach, adap->fe[0], + &adap_to_d(adap)->i2c_adap, + &au6610_qt1010_config) == NULL ? -ENODEV : 0; +} + +static int au6610_init(struct dvb_usb_device *d) +{ + /* TODO: this functionality belongs likely to the streaming control */ + /* bInterfaceNumber 0, bAlternateSetting 5 */ + return usb_set_interface(d->udev, 0, 5); +} + +static struct dvb_usb_device_properties au6610_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + + .i2c_algo = &au6610_i2c_algo, + .frontend_attach = au6610_zl10353_frontend_attach, + .tuner_attach = au6610_qt1010_tuner_attach, + .init = au6610_init, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_ISOC(0x82, 5, 40, 942, 1), + }, + }, +}; + +static const struct usb_device_id au6610_id_table[] = { + { DVB_USB_DEVICE(USB_VID_ALCOR_MICRO, USB_PID_SIGMATEK_DVB_110, + &au6610_props, "Sigmatek DVB-110", NULL) }, + { } +}; +MODULE_DEVICE_TABLE(usb, au6610_id_table); + +static struct usb_driver au6610_driver = { + .name = KBUILD_MODNAME, + .id_table = au6610_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(au6610_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Driver for Alcor Micro AU6610 DVB-T USB2.0"); +MODULE_VERSION("0.1"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/dvb-usb-v2/au6610.h b/drivers/media/usb/dvb-usb-v2/au6610.h new file mode 100644 index 000000000000..ea337bfc00b1 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/au6610.h @@ -0,0 +1,32 @@ +/* + * DVB USB Linux driver for Alcor Micro AU6610 DVB-T USB2.0. + * + * Copyright (C) 2006 Antti Palosaari <crope@iki.fi> + * + * 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. + */ + +#ifndef AU6610_H +#define AU6610_H +#include "dvb_usb.h" + +#define AU6610_REQ_I2C_WRITE 0x14 +#define AU6610_REQ_I2C_READ 0x13 +#define AU6610_REQ_USB_WRITE 0x16 +#define AU6610_REQ_USB_READ 0x15 + +#define AU6610_USB_TIMEOUT 1000 + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/az6007.c b/drivers/media/usb/dvb-usb-v2/az6007.c new file mode 100644 index 000000000000..54f1221d930d --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/az6007.c @@ -0,0 +1,919 @@ +/* + * Driver for AzureWave 6007 DVB-C/T USB2.0 and clones + * + * Copyright (c) Henry Wang <Henry.wang@AzureWave.com> + * + * This driver was made publicly available by Terratec, at: + * http://linux.terratec.de/files/TERRATEC_H7/20110323_TERRATEC_H7_Linux.tar.gz + * The original driver's license is GPL, as declared with MODULE_LICENSE() + * + * Copyright (c) 2010-2012 Mauro Carvalho Chehab <mchehab@redhat.com> + * Driver modified by in order to work with upstream drxk driver, and + * tons of bugs got fixed, and converted to use dvb-usb-v2. + * + * 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 under version 2 of the License. + * + * 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. + */ + +#include "drxk.h" +#include "mt2063.h" +#include "dvb_ca_en50221.h" +#include "dvb_usb.h" +#include "cypress_firmware.h" + +#define AZ6007_FIRMWARE "dvb-usb-terratec-h7-az6007.fw" + +static int az6007_xfer_debug; +module_param_named(xfer_debug, az6007_xfer_debug, int, 0644); +MODULE_PARM_DESC(xfer_debug, "Enable xfer debug"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* Known requests (Cypress FX2 firmware + az6007 "private" ones*/ + +#define FX2_OED 0xb5 +#define AZ6007_READ_DATA 0xb7 +#define AZ6007_I2C_RD 0xb9 +#define AZ6007_POWER 0xbc +#define AZ6007_I2C_WR 0xbd +#define FX2_SCON1 0xc0 +#define AZ6007_TS_THROUGH 0xc7 +#define AZ6007_READ_IR 0xb4 + +struct az6007_device_state { + struct mutex mutex; + struct mutex ca_mutex; + struct dvb_ca_en50221 ca; + unsigned warm:1; + int (*gate_ctrl) (struct dvb_frontend *, int); + unsigned char data[4096]; +}; + +static struct drxk_config terratec_h7_drxk = { + .adr = 0x29, + .parallel_ts = true, + .dynamic_clk = true, + .single_master = true, + .enable_merr_cfg = true, + .no_i2c_bridge = false, + .chunk_size = 64, + .mpeg_out_clk_strength = 0x02, + .qam_demod_parameter_count = 2, + .microcode_name = "dvb-usb-terratec-h7-drxk.fw", +}; + +static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct az6007_device_state *st = fe_to_priv(fe); + struct dvb_usb_adapter *adap = fe->sec_priv; + int status = 0; + + pr_debug("%s: %s\n", __func__, enable ? "enable" : "disable"); + + if (!adap || !st) + return -EINVAL; + + if (enable) + status = st->gate_ctrl(fe, 1); + else + status = st->gate_ctrl(fe, 0); + + return status; +} + +static struct mt2063_config az6007_mt2063_config = { + .tuner_address = 0x60, + .refclock = 36125000, +}; + +static int __az6007_read(struct usb_device *udev, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + int ret; + + ret = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + req, + USB_TYPE_VENDOR | USB_DIR_IN, + value, index, b, blen, 5000); + if (ret < 0) { + pr_warn("usb read operation failed. (%d)\n", ret); + return -EIO; + } + + if (az6007_xfer_debug) { + printk(KERN_DEBUG "az6007: IN req: %02x, value: %04x, index: %04x\n", + req, value, index); + print_hex_dump_bytes("az6007: payload: ", + DUMP_PREFIX_NONE, b, blen); + } + + return ret; +} + +static int az6007_read(struct dvb_usb_device *d, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + struct az6007_device_state *st = d->priv; + int ret; + + if (mutex_lock_interruptible(&st->mutex) < 0) + return -EAGAIN; + + ret = __az6007_read(d->udev, req, value, index, b, blen); + + mutex_unlock(&st->mutex); + + return ret; +} + +static int __az6007_write(struct usb_device *udev, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + int ret; + + if (az6007_xfer_debug) { + printk(KERN_DEBUG "az6007: OUT req: %02x, value: %04x, index: %04x\n", + req, value, index); + print_hex_dump_bytes("az6007: payload: ", + DUMP_PREFIX_NONE, b, blen); + } + + if (blen > 64) { + pr_err("az6007: tried to write %d bytes, but I2C max size is 64 bytes\n", + blen); + return -EOPNOTSUPP; + } + + ret = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + req, + USB_TYPE_VENDOR | USB_DIR_OUT, + value, index, b, blen, 5000); + if (ret != blen) { + pr_err("usb write operation failed. (%d)\n", ret); + return -EIO; + } + + return 0; +} + +static int az6007_write(struct dvb_usb_device *d, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + struct az6007_device_state *st = d->priv; + int ret; + + if (mutex_lock_interruptible(&st->mutex) < 0) + return -EAGAIN; + + ret = __az6007_write(d->udev, req, value, index, b, blen); + + mutex_unlock(&st->mutex); + + return ret; +} + +static int az6007_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct dvb_usb_device *d = fe_to_d(fe); + + pr_debug("%s: %s\n", __func__, onoff ? "enable" : "disable"); + + return az6007_write(d, 0xbc, onoff, 0, NULL, 0); +} + +/* remote control stuff (does not work with my box) */ +static int az6007_rc_query(struct dvb_usb_device *d) +{ + struct az6007_device_state *st = d_to_priv(d); + unsigned code = 0; + + az6007_read(d, AZ6007_READ_IR, 0, 0, st->data, 10); + + if (st->data[1] == 0x44) + return 0; + + if ((st->data[1] ^ st->data[2]) == 0xff) + code = st->data[1]; + else + code = st->data[1] << 8 | st->data[2]; + + if ((st->data[3] ^ st->data[4]) == 0xff) + code = code << 8 | st->data[3]; + else + code = code << 16 | st->data[3] << 8 | st->data[4]; + + rc_keydown(d->rc_dev, code, st->data[5]); + + return 0; +} + +static int az6007_ci_read_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, + int address) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = d_to_priv(d); + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + if (slot != 0) + return -EINVAL; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + + mutex_lock(&state->ca_mutex); + + req = 0xC1; + value = address; + index = 0; + blen = 1; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + pr_warn("usb in operation failed. (%d)\n", ret); + ret = -EINVAL; + } else { + ret = b[0]; + } + + mutex_unlock(&state->ca_mutex); + kfree(b); + return ret; +} + +static int az6007_ci_write_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, + int address, + u8 value) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = d_to_priv(d); + + int ret; + u8 req; + u16 value1; + u16 index; + int blen; + + pr_debug("%s(), slot %d\n", __func__, slot); + if (slot != 0) + return -EINVAL; + + mutex_lock(&state->ca_mutex); + req = 0xC2; + value1 = address; + index = value; + blen = 0; + + ret = az6007_write(d, req, value1, index, NULL, blen); + if (ret != 0) + pr_warn("usb out operation failed. (%d)\n", ret); + + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int az6007_ci_read_cam_control(struct dvb_ca_en50221 *ca, + int slot, + u8 address) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = d_to_priv(d); + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + if (slot != 0) + return -EINVAL; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + + mutex_lock(&state->ca_mutex); + + req = 0xC3; + value = address; + index = 0; + blen = 2; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + pr_warn("usb in operation failed. (%d)\n", ret); + ret = -EINVAL; + } else { + if (b[0] == 0) + pr_warn("Read CI IO error\n"); + + ret = b[1]; + pr_debug("read cam data = %x from 0x%x\n", b[1], value); + } + + mutex_unlock(&state->ca_mutex); + kfree(b); + return ret; +} + +static int az6007_ci_write_cam_control(struct dvb_ca_en50221 *ca, + int slot, + u8 address, + u8 value) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = d_to_priv(d); + + int ret; + u8 req; + u16 value1; + u16 index; + int blen; + + if (slot != 0) + return -EINVAL; + + mutex_lock(&state->ca_mutex); + req = 0xC4; + value1 = address; + index = value; + blen = 0; + + ret = az6007_write(d, req, value1, index, NULL, blen); + if (ret != 0) { + pr_warn("usb out operation failed. (%d)\n", ret); + goto failed; + } + +failed: + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int CI_CamReady(struct dvb_ca_en50221 *ca, int slot) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + + req = 0xC8; + value = 0; + index = 0; + blen = 1; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + pr_warn("usb in operation failed. (%d)\n", ret); + ret = -EIO; + } else{ + ret = b[0]; + } + kfree(b); + return ret; +} + +static int az6007_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = d_to_priv(d); + + int ret, i; + u8 req; + u16 value; + u16 index; + int blen; + + mutex_lock(&state->ca_mutex); + + req = 0xC6; + value = 1; + index = 0; + blen = 0; + + ret = az6007_write(d, req, value, index, NULL, blen); + if (ret != 0) { + pr_warn("usb out operation failed. (%d)\n", ret); + goto failed; + } + + msleep(500); + req = 0xC6; + value = 0; + index = 0; + blen = 0; + + ret = az6007_write(d, req, value, index, NULL, blen); + if (ret != 0) { + pr_warn("usb out operation failed. (%d)\n", ret); + goto failed; + } + + for (i = 0; i < 15; i++) { + msleep(100); + + if (CI_CamReady(ca, slot)) { + pr_debug("CAM Ready\n"); + break; + } + } + msleep(5000); + +failed: + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int az6007_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + return 0; +} + +static int az6007_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = d_to_priv(d); + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + + pr_debug("%s()\n", __func__); + mutex_lock(&state->ca_mutex); + req = 0xC7; + value = 1; + index = 0; + blen = 0; + + ret = az6007_write(d, req, value, index, NULL, blen); + if (ret != 0) { + pr_warn("usb out operation failed. (%d)\n", ret); + goto failed; + } + +failed: + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int az6007_ci_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = d_to_priv(d); + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + mutex_lock(&state->ca_mutex); + + req = 0xC5; + value = 0; + index = 0; + blen = 1; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + pr_warn("usb in operation failed. (%d)\n", ret); + ret = -EIO; + } else + ret = 0; + + if (!ret && b[0] == 1) { + ret = DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY; + } + + mutex_unlock(&state->ca_mutex); + kfree(b); + return ret; +} + + +static void az6007_ci_uninit(struct dvb_usb_device *d) +{ + struct az6007_device_state *state; + + pr_debug("%s()\n", __func__); + + if (NULL == d) + return; + + state = d_to_priv(d); + if (NULL == state) + return; + + if (NULL == state->ca.data) + return; + + dvb_ca_en50221_release(&state->ca); + + memset(&state->ca, 0, sizeof(state->ca)); +} + + +static int az6007_ci_init(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct az6007_device_state *state = adap_to_priv(adap); + int ret; + + pr_debug("%s()\n", __func__); + + mutex_init(&state->ca_mutex); + state->ca.owner = THIS_MODULE; + state->ca.read_attribute_mem = az6007_ci_read_attribute_mem; + state->ca.write_attribute_mem = az6007_ci_write_attribute_mem; + state->ca.read_cam_control = az6007_ci_read_cam_control; + state->ca.write_cam_control = az6007_ci_write_cam_control; + state->ca.slot_reset = az6007_ci_slot_reset; + state->ca.slot_shutdown = az6007_ci_slot_shutdown; + state->ca.slot_ts_enable = az6007_ci_slot_ts_enable; + state->ca.poll_slot_status = az6007_ci_poll_slot_status; + state->ca.data = d; + + ret = dvb_ca_en50221_init(&adap->dvb_adap, + &state->ca, + 0, /* flags */ + 1);/* n_slots */ + if (ret != 0) { + pr_err("Cannot initialize CI: Error %d.\n", ret); + memset(&state->ca, 0, sizeof(state->ca)); + return ret; + } + + pr_debug("CI initialized.\n"); + + return 0; +} + +static int az6007_read_mac_addr(struct dvb_usb_adapter *adap, u8 mac[6]) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct az6007_device_state *st = adap_to_priv(adap); + int ret; + + ret = az6007_read(d, AZ6007_READ_DATA, 6, 0, st->data, 6); + memcpy(mac, st->data, 6); + + if (ret > 0) + pr_debug("%s: mac is %pM\n", __func__, mac); + + return ret; +} + +static int az6007_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct az6007_device_state *st = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + + pr_debug("attaching demod drxk\n"); + + adap->fe[0] = dvb_attach(drxk_attach, &terratec_h7_drxk, + &d->i2c_adap); + if (!adap->fe[0]) + return -EINVAL; + + adap->fe[0]->sec_priv = adap; + st->gate_ctrl = adap->fe[0]->ops.i2c_gate_ctrl; + adap->fe[0]->ops.i2c_gate_ctrl = drxk_gate_ctrl; + + az6007_ci_init(adap); + + return 0; +} + +static int az6007_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + + pr_debug("attaching tuner mt2063\n"); + + /* Attach mt2063 to DVB-C frontend */ + if (adap->fe[0]->ops.i2c_gate_ctrl) + adap->fe[0]->ops.i2c_gate_ctrl(adap->fe[0], 1); + if (!dvb_attach(mt2063_attach, adap->fe[0], + &az6007_mt2063_config, + &d->i2c_adap)) + return -EINVAL; + + if (adap->fe[0]->ops.i2c_gate_ctrl) + adap->fe[0]->ops.i2c_gate_ctrl(adap->fe[0], 0); + + return 0; +} + +static int az6007_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + struct az6007_device_state *state = d_to_priv(d); + int ret; + + pr_debug("%s()\n", __func__); + + if (!state->warm) { + mutex_init(&state->mutex); + + ret = az6007_write(d, AZ6007_POWER, 0, 2, NULL, 0); + if (ret < 0) + return ret; + msleep(60); + ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0); + if (ret < 0) + return ret; + msleep(100); + ret = az6007_write(d, AZ6007_POWER, 1, 3, NULL, 0); + if (ret < 0) + return ret; + msleep(20); + ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0); + if (ret < 0) + return ret; + + msleep(400); + ret = az6007_write(d, FX2_SCON1, 0, 3, NULL, 0); + if (ret < 0) + return ret; + msleep(150); + ret = az6007_write(d, FX2_SCON1, 1, 3, NULL, 0); + if (ret < 0) + return ret; + msleep(430); + ret = az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0); + if (ret < 0) + return ret; + + state->warm = true; + + return 0; + } + + if (!onoff) + return 0; + + az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0); + az6007_write(d, AZ6007_TS_THROUGH, 0, 0, NULL, 0); + + return 0; +} + +/* I2C */ +static int az6007_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct az6007_device_state *st = d_to_priv(d); + int i, j, len; + int ret = 0; + u16 index; + u16 value; + int length; + u8 req, addr; + + if (mutex_lock_interruptible(&st->mutex) < 0) + return -EAGAIN; + + for (i = 0; i < num; i++) { + addr = msgs[i].addr << 1; + if (((i + 1) < num) + && (msgs[i].len == 1) + && ((msgs[i].flags & I2C_M_RD) != I2C_M_RD) + && (msgs[i + 1].flags & I2C_M_RD) + && (msgs[i].addr == msgs[i + 1].addr)) { + /* + * A write + read xfer for the same address, where + * the first xfer has just 1 byte length. + * Need to join both into one operation + */ + if (az6007_xfer_debug) + printk(KERN_DEBUG "az6007: I2C W/R addr=0x%x len=%d/%d\n", + addr, msgs[i].len, msgs[i + 1].len); + req = AZ6007_I2C_RD; + index = msgs[i].buf[0]; + value = addr | (1 << 8); + length = 6 + msgs[i + 1].len; + len = msgs[i + 1].len; + ret = __az6007_read(d->udev, req, value, index, + st->data, length); + if (ret >= len) { + for (j = 0; j < len; j++) + msgs[i + 1].buf[j] = st->data[j + 5]; + } else + ret = -EIO; + i++; + } else if (!(msgs[i].flags & I2C_M_RD)) { + /* write bytes */ + if (az6007_xfer_debug) + printk(KERN_DEBUG "az6007: I2C W addr=0x%x len=%d\n", + addr, msgs[i].len); + req = AZ6007_I2C_WR; + index = msgs[i].buf[0]; + value = addr | (1 << 8); + length = msgs[i].len - 1; + len = msgs[i].len - 1; + for (j = 0; j < len; j++) + st->data[j] = msgs[i].buf[j + 1]; + ret = __az6007_write(d->udev, req, value, index, + st->data, length); + } else { + /* read bytes */ + if (az6007_xfer_debug) + printk(KERN_DEBUG "az6007: I2C R addr=0x%x len=%d\n", + addr, msgs[i].len); + req = AZ6007_I2C_RD; + index = msgs[i].buf[0]; + value = addr; + length = msgs[i].len + 6; + len = msgs[i].len; + ret = __az6007_read(d->udev, req, value, index, + st->data, length); + for (j = 0; j < len; j++) + msgs[i].buf[j] = st->data[j + 5]; + } + if (ret < 0) + goto err; + } +err: + mutex_unlock(&st->mutex); + + if (ret < 0) { + pr_info("%s ERROR: %i\n", __func__, ret); + return ret; + } + return num; +} + +static u32 az6007_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm az6007_i2c_algo = { + .master_xfer = az6007_i2c_xfer, + .functionality = az6007_i2c_func, +}; + +static int az6007_identify_state(struct dvb_usb_device *d, const char **name) +{ + int ret; + u8 *mac; + + pr_debug("Identifying az6007 state\n"); + + mac = kmalloc(6, GFP_ATOMIC); + if (!mac) + return -ENOMEM; + + /* Try to read the mac address */ + ret = __az6007_read(d->udev, AZ6007_READ_DATA, 6, 0, mac, 6); + if (ret == 6) + ret = WARM; + else + ret = COLD; + + kfree(mac); + + if (ret == COLD) { + __az6007_write(d->udev, 0x09, 1, 0, NULL, 0); + __az6007_write(d->udev, 0x00, 0, 0, NULL, 0); + __az6007_write(d->udev, 0x00, 0, 0, NULL, 0); + } + + pr_debug("Device is on %s state\n", + ret == WARM ? "warm" : "cold"); + return ret; +} + +static void az6007_usb_disconnect(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + az6007_ci_uninit(d); + dvb_usbv2_disconnect(intf); +} + +static int az6007_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) +{ + pr_debug("Getting az6007 Remote Control properties\n"); + + rc->allowed_protos = RC_TYPE_NEC; + rc->query = az6007_rc_query; + rc->interval = 400; + + return 0; +} + +static int az6007_download_firmware(struct dvb_usb_device *d, + const struct firmware *fw) +{ + pr_debug("Loading az6007 firmware\n"); + + return usbv2_cypress_load_firmware(d->udev, fw, CYPRESS_FX2); +} + +/* DVB USB Driver stuff */ +static struct dvb_usb_device_properties az6007_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .firmware = AZ6007_FIRMWARE, + + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct az6007_device_state), + .i2c_algo = &az6007_i2c_algo, + .tuner_attach = az6007_tuner_attach, + .frontend_attach = az6007_frontend_attach, + .streaming_ctrl = az6007_streaming_ctrl, + .get_rc_config = az6007_get_rc_config, + .read_mac_address = az6007_read_mac_addr, + .download_firmware = az6007_download_firmware, + .identify_state = az6007_identify_state, + .power_ctrl = az6007_power_ctrl, + .num_adapters = 1, + .adapter = { + { .stream = DVB_USB_STREAM_BULK(0x02, 10, 4096), } + } +}; + +static struct usb_device_id az6007_usb_table[] = { + {DVB_USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_AZUREWAVE_6007, + &az6007_props, "Azurewave 6007", RC_MAP_EMPTY)}, + {DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7, + &az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)}, + {DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7_2, + &az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)}, + {0}, +}; + +MODULE_DEVICE_TABLE(usb, az6007_usb_table); + +static int az6007_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + + az6007_ci_uninit(d); + return dvb_usbv2_suspend(intf, msg); +} + +static int az6007_resume(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + struct dvb_usb_adapter *adap = &d->adapter[0]; + + az6007_ci_init(adap); + return dvb_usbv2_resume(intf); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver az6007_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = az6007_usb_table, + .probe = dvb_usbv2_probe, + .disconnect = az6007_usb_disconnect, + .no_dynamic_id = 1, + .soft_unbind = 1, + /* + * FIXME: need to implement reset_resume, likely with + * dvb-usb-v2 core support + */ + .suspend = az6007_suspend, + .resume = az6007_resume, +}; + +module_usb_driver(az6007_usb_driver); + +MODULE_AUTHOR("Henry Wang <Henry.wang@AzureWave.com>"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); +MODULE_DESCRIPTION("Driver for AzureWave 6007 DVB-C/T USB2.0 and clones"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(AZ6007_FIRMWARE); diff --git a/drivers/media/usb/dvb-usb-v2/ce6230.c b/drivers/media/usb/dvb-usb-v2/ce6230.c new file mode 100644 index 000000000000..f67b14bc32e3 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/ce6230.c @@ -0,0 +1,292 @@ +/* + * Intel CE6230 DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * + * 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 "ce6230.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int ce6230_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) +{ + int ret; + unsigned int pipe; + u8 request; + u8 requesttype; + u16 value; + u16 index; + u8 *buf; + + request = req->cmd; + value = req->value; + index = req->index; + + switch (req->cmd) { + case I2C_READ: + case DEMOD_READ: + case REG_READ: + requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); + break; + case I2C_WRITE: + case DEMOD_WRITE: + case REG_WRITE: + requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); + break; + default: + dev_err(&d->udev->dev, "%s: unknown command=%02x\n", + KBUILD_MODNAME, req->cmd); + ret = -EINVAL; + goto error; + } + + buf = kmalloc(req->data_len, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto error; + } + + if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) { + /* write */ + memcpy(buf, req->data, req->data_len); + pipe = usb_sndctrlpipe(d->udev, 0); + } else { + /* read */ + pipe = usb_rcvctrlpipe(d->udev, 0); + } + + msleep(1); /* avoid I2C errors */ + + ret = usb_control_msg(d->udev, pipe, request, requesttype, value, index, + buf, req->data_len, CE6230_USB_TIMEOUT); + + dvb_usb_dbg_usb_control_msg(d->udev, request, requesttype, value, index, + buf, req->data_len); + + if (ret < 0) + dev_err(&d->udev->dev, "%s: usb_control_msg() failed=%d\n", + KBUILD_MODNAME, ret); + else + ret = 0; + + /* read request, copy returned data to return buf */ + if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) + memcpy(req->data, buf, req->data_len); + + kfree(buf); +error: + return ret; +} + +/* I2C */ +static struct zl10353_config ce6230_zl10353_config; + +static int ce6230_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg msg[], int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret = 0, i = 0; + struct usb_req req; + + if (num > 2) + return -EOPNOTSUPP; + + memset(&req, 0, sizeof(req)); + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + while (i < num) { + if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { + if (msg[i].addr == + ce6230_zl10353_config.demod_address) { + req.cmd = DEMOD_READ; + req.value = msg[i].addr >> 1; + req.index = msg[i].buf[0]; + req.data_len = msg[i+1].len; + req.data = &msg[i+1].buf[0]; + ret = ce6230_ctrl_msg(d, &req); + } else { + dev_err(&d->udev->dev, "%s: I2C read not " \ + "implemented\n", + KBUILD_MODNAME); + ret = -EOPNOTSUPP; + } + i += 2; + } else { + if (msg[i].addr == + ce6230_zl10353_config.demod_address) { + req.cmd = DEMOD_WRITE; + req.value = msg[i].addr >> 1; + req.index = msg[i].buf[0]; + req.data_len = msg[i].len-1; + req.data = &msg[i].buf[1]; + ret = ce6230_ctrl_msg(d, &req); + } else { + req.cmd = I2C_WRITE; + req.value = 0x2000 + (msg[i].addr >> 1); + req.index = 0x0000; + req.data_len = msg[i].len; + req.data = &msg[i].buf[0]; + ret = ce6230_ctrl_msg(d, &req); + } + i += 1; + } + if (ret) + break; + } + + mutex_unlock(&d->i2c_mutex); + return ret ? ret : i; +} + +static u32 ce6230_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm ce6230_i2c_algorithm = { + .master_xfer = ce6230_i2c_master_xfer, + .functionality = ce6230_i2c_functionality, +}; + +/* Callbacks for DVB USB */ +static struct zl10353_config ce6230_zl10353_config = { + .demod_address = 0x1e, + .adc_clock = 450000, + .if2 = 45700, + .no_tuner = 1, + .parallel_ts = 1, + .clock_ctl_1 = 0x34, + .pll_0 = 0x0e, +}; + +static int ce6230_zl10353_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + adap->fe[0] = dvb_attach(zl10353_attach, &ce6230_zl10353_config, + &d->i2c_adap); + if (adap->fe[0] == NULL) + return -ENODEV; + + return 0; +} + +static struct mxl5005s_config ce6230_mxl5003s_config = { + .i2c_address = 0xc6, + .if_freq = IF_FREQ_4570000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_DEFAULT, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .AgcMasterByte = 0x00, +}; + +static int ce6230_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + int ret; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + ret = dvb_attach(mxl5005s_attach, adap->fe[0], &d->i2c_adap, + &ce6230_mxl5003s_config) == NULL ? -ENODEV : 0; + return ret; +} + +static int ce6230_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + int ret; + + dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff); + + /* InterfaceNumber 1 / AlternateSetting 0 idle + InterfaceNumber 1 / AlternateSetting 1 streaming */ + ret = usb_set_interface(d->udev, 1, onoff); + if (ret) + dev_err(&d->udev->dev, "%s: usb_set_interface() failed=%d\n", + KBUILD_MODNAME, ret); + + return ret; +} + +/* DVB USB Driver stuff */ +static struct dvb_usb_device_properties ce6230_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .bInterfaceNumber = 1, + + .i2c_algo = &ce6230_i2c_algorithm, + .power_ctrl = ce6230_power_ctrl, + .frontend_attach = ce6230_zl10353_frontend_attach, + .tuner_attach = ce6230_mxl5003s_tuner_attach, + + .num_adapters = 1, + .adapter = { + { + .stream = { + .type = USB_BULK, + .count = 6, + .endpoint = 0x82, + .u = { + .bulk = { + .buffersize = (16 * 512), + } + } + }, + } + }, +}; + +static const struct usb_device_id ce6230_id_table[] = { + { DVB_USB_DEVICE(USB_VID_INTEL, USB_PID_INTEL_CE9500, + &ce6230_props, "Intel CE9500 reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A310, + &ce6230_props, "AVerMedia A310 USB 2.0 DVB-T tuner", NULL) }, + { } +}; +MODULE_DEVICE_TABLE(usb, ce6230_id_table); + +static struct usb_driver ce6230_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = ce6230_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(ce6230_usb_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Intel CE6230 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/dvb-usb-v2/ce6230.h b/drivers/media/usb/dvb-usb-v2/ce6230.h new file mode 100644 index 000000000000..299e57e3390b --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/ce6230.h @@ -0,0 +1,50 @@ +/* + * Intel CE6230 DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * + * 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. + * + */ + +#ifndef CE6230_H +#define CE6230_H + +#include "dvb_usb.h" +#include "zl10353.h" +#include "mxl5005s.h" + +#define CE6230_USB_TIMEOUT 1000 + +struct usb_req { + u8 cmd; /* [1] */ + u16 value; /* [2|3] */ + u16 index; /* [4|5] */ + u16 data_len; /* [6|7] */ + u8 *data; +}; + +enum ce6230_cmd { + CONFIG_READ = 0xd0, /* rd 0 (unclear) */ + UNKNOWN_WRITE = 0xc7, /* wr 7 (unclear) */ + I2C_READ = 0xd9, /* rd 9 (unclear) */ + I2C_WRITE = 0xca, /* wr a */ + DEMOD_READ = 0xdb, /* rd b */ + DEMOD_WRITE = 0xcc, /* wr c */ + REG_READ = 0xde, /* rd e */ + REG_WRITE = 0xcf, /* wr f */ +}; + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/cypress_firmware.c b/drivers/media/usb/dvb-usb-v2/cypress_firmware.c new file mode 100644 index 000000000000..211df549f26a --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/cypress_firmware.c @@ -0,0 +1,134 @@ +/* cypress_firmware.c is part of the DVB USB library. + * + * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@desy.de) + * see dvb-usb-init.c for copyright information. + * + * This file contains functions for downloading the firmware to Cypress FX 1 + * and 2 based devices. + * + */ + +#include "dvb_usb.h" +#include "cypress_firmware.h" + +struct usb_cypress_controller { + u8 id; + const char *name; /* name of the usb controller */ + u16 cs_reg; /* needs to be restarted, + * when the firmware has been downloaded */ +}; + +static const struct usb_cypress_controller cypress[] = { + { .id = CYPRESS_AN2135, .name = "Cypress AN2135", .cs_reg = 0x7f92 }, + { .id = CYPRESS_AN2235, .name = "Cypress AN2235", .cs_reg = 0x7f92 }, + { .id = CYPRESS_FX2, .name = "Cypress FX2", .cs_reg = 0xe600 }, +}; + +/* + * load a firmware packet to the device + */ +static int usb_cypress_writemem(struct usb_device *udev, u16 addr, u8 *data, + u8 len) +{ + dvb_usb_dbg_usb_control_msg(udev, + 0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len); + + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000); +} + +int usbv2_cypress_load_firmware(struct usb_device *udev, + const struct firmware *fw, int type) +{ + struct hexline *hx; + int ret, pos = 0; + + hx = kmalloc(sizeof(struct hexline), GFP_KERNEL); + if (!hx) { + dev_err(&udev->dev, "%s: kmalloc() failed\n", KBUILD_MODNAME); + return -ENOMEM; + } + + /* stop the CPU */ + hx->data[0] = 1; + ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1); + if (ret != 1) { + dev_err(&udev->dev, "%s: CPU stop failed=%d\n", + KBUILD_MODNAME, ret); + ret = -EIO; + goto err_kfree; + } + + /* write firmware to memory */ + for (;;) { + ret = dvb_usbv2_get_hexline(fw, hx, &pos); + if (ret < 0) + goto err_kfree; + else if (ret == 0) + break; + + ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len); + if (ret < 0) { + goto err_kfree; + } else if (ret != hx->len) { + dev_err(&udev->dev, "%s: error while transferring " \ + "firmware (transferred size=%d, " \ + "block size=%d)\n", + KBUILD_MODNAME, ret, hx->len); + ret = -EIO; + goto err_kfree; + } + } + + /* start the CPU */ + hx->data[0] = 0; + ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1); + if (ret != 1) { + dev_err(&udev->dev, "%s: CPU start failed=%d\n", + KBUILD_MODNAME, ret); + ret = -EIO; + goto err_kfree; + } + + ret = 0; +err_kfree: + kfree(hx); + return ret; +} +EXPORT_SYMBOL(usbv2_cypress_load_firmware); + +int dvb_usbv2_get_hexline(const struct firmware *fw, struct hexline *hx, + int *pos) +{ + u8 *b = (u8 *) &fw->data[*pos]; + int data_offs = 4; + + if (*pos >= fw->size) + return 0; + + memset(hx, 0, sizeof(struct hexline)); + hx->len = b[0]; + + if ((*pos + hx->len + 4) >= fw->size) + return -EINVAL; + + hx->addr = b[1] | (b[2] << 8); + hx->type = b[3]; + + if (hx->type == 0x04) { + /* b[4] and b[5] are the Extended linear address record data + * field */ + hx->addr |= (b[4] << 24) | (b[5] << 16); + } + + memcpy(hx->data, &b[data_offs], hx->len); + hx->chk = b[hx->len + data_offs]; + *pos += hx->len + 5; + + return *pos; +} +EXPORT_SYMBOL(dvb_usbv2_get_hexline); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Cypress firmware download"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/dvb-usb-v2/cypress_firmware.h b/drivers/media/usb/dvb-usb-v2/cypress_firmware.h new file mode 100644 index 000000000000..80085fd4132c --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/cypress_firmware.h @@ -0,0 +1,31 @@ +/* cypress_firmware.h is part of the DVB USB library. + * + * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@desy.de) + * see dvb-usb-init.c for copyright information. + * + * This file contains functions for downloading the firmware to Cypress FX 1 + * and 2 based devices. + * + */ + +#ifndef CYPRESS_FIRMWARE_H +#define CYPRESS_FIRMWARE_H + +#define CYPRESS_AN2135 0 +#define CYPRESS_AN2235 1 +#define CYPRESS_FX2 2 + +/* commonly used firmware download types and function */ +struct hexline { + u8 len; + u32 addr; + u8 type; + u8 data[255]; + u8 chk; +}; +extern int usbv2_cypress_load_firmware(struct usb_device *, + const struct firmware *, int); +extern int dvb_usbv2_get_hexline(const struct firmware *, + struct hexline *, int *); + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb.h b/drivers/media/usb/dvb-usb-v2/dvb_usb.h new file mode 100644 index 000000000000..bae16a1189d6 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb.h @@ -0,0 +1,403 @@ +/* + * DVB USB framework + * + * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@desy.de> + * Copyright (C) 2012 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef DVB_USB_H +#define DVB_USB_H + +#include <linux/usb/input.h> +#include <linux/firmware.h> +#include <media/rc-core.h> + +#include "dvb_frontend.h" +#include "dvb_demux.h" +#include "dvb_net.h" +#include "dmxdev.h" +#include "dvb-usb-ids.h" + +/* + * device file: /dev/dvb/adapter[0-1]/frontend[0-2] + * + * |-- device + * | |-- adapter0 + * | | |-- frontend0 + * | | |-- frontend1 + * | | `-- frontend2 + * | `-- adapter1 + * | |-- frontend0 + * | |-- frontend1 + * | `-- frontend2 + * + * + * Commonly used variable names: + * d = pointer to device (struct dvb_usb_device *) + * adap = pointer to adapter (struct dvb_usb_adapter *) + * fe = pointer to frontend (struct dvb_frontend *) + * + * Use macros defined in that file to resolve needed pointers. + */ + +/* helper macros for every DVB USB driver use */ +#define adap_to_d(adap) (container_of(adap, struct dvb_usb_device, \ + adapter[adap->id])) +#define adap_to_priv(adap) (adap_to_d(adap)->priv) +#define fe_to_adap(fe) ((struct dvb_usb_adapter *) ((fe)->dvb->priv)) +#define fe_to_d(fe) (adap_to_d(fe_to_adap(fe))) +#define fe_to_priv(fe) (fe_to_d(fe)->priv) +#define d_to_priv(d) (d->priv) + +#define dvb_usb_dbg_usb_control_msg(udev, r, t, v, i, b, l) { \ + char *direction; \ + if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \ + direction = ">>>"; \ + else \ + direction = "<<<"; \ + dev_dbg(&udev->dev, "%s: %02x %02x %02x %02x %02x %02x %02x %02x " \ + "%s %*ph\n", __func__, t, r, v & 0xff, v >> 8, \ + i & 0xff, i >> 8, l & 0xff, l >> 8, direction, l, b); \ +} + +#define DVB_USB_STREAM_BULK(endpoint_, count_, size_) { \ + .type = USB_BULK, \ + .count = count_, \ + .endpoint = endpoint_, \ + .u = { \ + .bulk = { \ + .buffersize = size_, \ + } \ + } \ +} + +#define DVB_USB_STREAM_ISOC(endpoint_, count_, frames_, size_, interval_) { \ + .type = USB_ISOC, \ + .count = count_, \ + .endpoint = endpoint_, \ + .u = { \ + .isoc = { \ + .framesperurb = frames_, \ + .framesize = size_,\ + .interval = interval_, \ + } \ + } \ +} + +#define DVB_USB_DEVICE(vend, prod, props_, name_, rc) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, \ + .idVendor = (vend), \ + .idProduct = (prod), \ + .driver_info = (kernel_ulong_t) &((const struct dvb_usb_driver_info) { \ + .props = (props_), \ + .name = (name_), \ + .rc_map = (rc), \ + }) + +struct dvb_usb_device; +struct dvb_usb_adapter; + +/** + * structure for carrying all needed data from the device driver to the general + * dvb usb routines + * @name: device name + * @rc_map: name of rc codes table + * @props: structure containing all device properties + */ +struct dvb_usb_driver_info { + const char *name; + const char *rc_map; + const struct dvb_usb_device_properties *props; +}; + +/** + * structure for remote controller configuration + * @map_name: name of rc codes table + * @allowed_protos: protocol(s) supported by the driver + * @change_protocol: callback to change protocol + * @query: called to query an event from the device + * @interval: time in ms between two queries + * @driver_type: used to point if a device supports raw mode + * @bulk_mode: device supports bulk mode for rc (disable polling mode) + */ +struct dvb_usb_rc { + const char *map_name; + u64 allowed_protos; + int (*change_protocol)(struct rc_dev *dev, u64 rc_type); + int (*query) (struct dvb_usb_device *d); + unsigned int interval; + const enum rc_driver_type driver_type; + bool bulk_mode; +}; + +/** + * usb streaming configration for adapter + * @type: urb type + * @count: count of used urbs + * @endpoint: stream usb endpoint number + */ +struct usb_data_stream_properties { +#define USB_BULK 1 +#define USB_ISOC 2 + u8 type; + u8 count; + u8 endpoint; + + union { + struct { + unsigned int buffersize; /* per URB */ + } bulk; + struct { + int framesperurb; + int framesize; + int interval; + } isoc; + } u; +}; + +/** + * properties of dvb usb device adapter + * @caps: adapter capabilities + * @pid_filter_count: pid count of adapter pid-filter + * @pid_filter_ctrl: called to enable/disable pid-filter + * @pid_filter: called to set/unset pid for filtering + * @stream: adapter usb stream configuration + */ +#define MAX_NO_OF_FE_PER_ADAP 3 +struct dvb_usb_adapter_properties { +#define DVB_USB_ADAP_HAS_PID_FILTER 0x01 +#define DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF 0x02 +#define DVB_USB_ADAP_NEED_PID_FILTERING 0x04 + u8 caps; + + u8 pid_filter_count; + int (*pid_filter_ctrl) (struct dvb_usb_adapter *, int); + int (*pid_filter) (struct dvb_usb_adapter *, int, u16, int); + + struct usb_data_stream_properties stream; +}; + +/** + * struct dvb_usb_device_properties - properties of a dvb-usb-device + * @driver_name: name of the owning driver module + * @owner: owner of the dvb_adapter + * @adapter_nr: values from the DVB_DEFINE_MOD_OPT_ADAPTER_NR() macro + * @bInterfaceNumber: usb interface number driver binds + * @size_of_priv: bytes allocated for the driver private data + * @generic_bulk_ctrl_endpoint: bulk control endpoint number for sent + * @generic_bulk_ctrl_endpoint_response: bulk control endpoint number for + * receive + * @generic_bulk_ctrl_delay: delay between bulk control sent and receive message + * @identify_state: called to determine the firmware state (cold or warm) and + * return possible firmware file name to be loaded + * @firmware: name of the firmware file to be loaded + * @download_firmware: called to download the firmware + * @i2c_algo: i2c_algorithm if the device has i2c-adapter + * @num_adapters: dvb usb device adapter count + * @get_adapter_count: called to resolve adapter count + * @adapter: array of all adapter properties of device + * @power_ctrl: called to enable/disable power of the device + * @read_config: called to resolve device configuration + * @read_mac_address: called to resolve adapter mac-address + * @frontend_attach: called to attach the possible frontends + * @tuner_attach: called to attach the possible tuners + * @frontend_ctrl: called to power on/off active frontend + * @streaming_ctrl: called to start/stop the usb streaming of adapter + * @init: called after adapters are created in order to finalize device + * configuration + * @exit: called when driver is unloaded + * @get_rc_config: called to resolve used remote controller configuration + * @get_stream_config: called to resolve input and output stream configuration + * of the adapter just before streaming is started. input stream is transport + * stream from the demodulator and output stream is usb stream to host. + */ +#define MAX_NO_OF_ADAPTER_PER_DEVICE 2 +struct dvb_usb_device_properties { + const char *driver_name; + struct module *owner; + short *adapter_nr; + + u8 bInterfaceNumber; + unsigned int size_of_priv; + u8 generic_bulk_ctrl_endpoint; + u8 generic_bulk_ctrl_endpoint_response; + unsigned int generic_bulk_ctrl_delay; + +#define WARM 0 +#define COLD 1 + int (*identify_state) (struct dvb_usb_device *, const char **); + const char *firmware; +#define RECONNECTS_USB 1 + int (*download_firmware) (struct dvb_usb_device *, + const struct firmware *); + + struct i2c_algorithm *i2c_algo; + + unsigned int num_adapters; + int (*get_adapter_count) (struct dvb_usb_device *); + struct dvb_usb_adapter_properties adapter[MAX_NO_OF_ADAPTER_PER_DEVICE]; + int (*power_ctrl) (struct dvb_usb_device *, int); + int (*read_config) (struct dvb_usb_device *d); + int (*read_mac_address) (struct dvb_usb_adapter *, u8 []); + int (*frontend_attach) (struct dvb_usb_adapter *); + int (*tuner_attach) (struct dvb_usb_adapter *); + int (*frontend_ctrl) (struct dvb_frontend *, int); + int (*streaming_ctrl) (struct dvb_frontend *, int); + int (*init) (struct dvb_usb_device *); + void (*exit) (struct dvb_usb_device *); + int (*get_rc_config) (struct dvb_usb_device *, struct dvb_usb_rc *); +#define DVB_USB_FE_TS_TYPE_188 0 +#define DVB_USB_FE_TS_TYPE_204 1 +#define DVB_USB_FE_TS_TYPE_RAW 2 + int (*get_stream_config) (struct dvb_frontend *, u8 *, + struct usb_data_stream_properties *); +}; + +/** + * generic object of an usb stream + * @buf_num: number of buffer allocated + * @buf_size: size of each buffer in buf_list + * @buf_list: array containing all allocate buffers for streaming + * @dma_addr: list of dma_addr_t for each buffer in buf_list + * + * @urbs_initialized: number of URBs initialized + * @urbs_submitted: number of URBs submitted + */ +#define MAX_NO_URBS_FOR_DATA_STREAM 10 +struct usb_data_stream { + struct usb_device *udev; + struct usb_data_stream_properties props; + +#define USB_STATE_INIT 0x00 +#define USB_STATE_URB_BUF 0x01 + u8 state; + + void (*complete) (struct usb_data_stream *, u8 *, size_t); + + struct urb *urb_list[MAX_NO_URBS_FOR_DATA_STREAM]; + int buf_num; + unsigned long buf_size; + u8 *buf_list[MAX_NO_URBS_FOR_DATA_STREAM]; + dma_addr_t dma_addr[MAX_NO_URBS_FOR_DATA_STREAM]; + + int urbs_initialized; + int urbs_submitted; + + void *user_priv; +}; + +/** + * dvb adapter object on dvb usb device + * @props: pointer to adapter properties + * @stream: adapter the usb data stream + * @id: index of this adapter (starting with 0) + * @ts_type: transport stream, input stream, type + * @suspend_resume_active: set when there is ongoing suspend / resume + * @pid_filtering: is hardware pid_filtering used or not + * @feed_count: current feed count + * @max_feed_count: maimum feed count device can handle + * @dvb_adap: adapter dvb_adapter + * @dmxdev: adapter dmxdev + * @demux: adapter software demuxer + * @dvb_net: adapter dvb_net interfaces + * @sync_mutex: mutex used to sync control and streaming of the adapter + * @fe: adapter frontends + * @fe_init: rerouted frontend-init function + * @fe_sleep: rerouted frontend-sleep function + */ +struct dvb_usb_adapter { + const struct dvb_usb_adapter_properties *props; + struct usb_data_stream stream; + u8 id; + u8 ts_type; + bool suspend_resume_active; + bool pid_filtering; + u8 feed_count; + u8 max_feed_count; + s8 active_fe; + + /* dvb */ + struct dvb_adapter dvb_adap; + struct dmxdev dmxdev; + struct dvb_demux demux; + struct dvb_net dvb_net; + struct mutex sync_mutex; + + struct dvb_frontend *fe[MAX_NO_OF_FE_PER_ADAP]; + int (*fe_init[MAX_NO_OF_FE_PER_ADAP]) (struct dvb_frontend *); + int (*fe_sleep[MAX_NO_OF_FE_PER_ADAP]) (struct dvb_frontend *); +}; + +/** + * dvb usb device object + * @props: device properties + * @name: device name + * @rc_map: name of rc codes table + * @udev: pointer to the device's struct usb_device + * @intf: pointer to the device's usb interface + * @rc: remote controller configuration + * @probe_work: work to defer .probe() + * @powered: indicated whether the device is power or not + * @usb_mutex: mutex for usb control messages + * @i2c_mutex: mutex for i2c-transfers + * @i2c_adap: device's i2c-adapter + * @rc_dev: rc device for the remote control + * @rc_query_work: work for polling remote + * @priv: private data of the actual driver (allocate by dvb usb, size defined + * in size_of_priv of dvb_usb_properties). + */ +struct dvb_usb_device { + const struct dvb_usb_device_properties *props; + const char *name; + const char *rc_map; + + struct usb_device *udev; + struct usb_interface *intf; + struct dvb_usb_rc rc; + struct work_struct probe_work; + pid_t work_pid; + int powered; + + /* locking */ + struct mutex usb_mutex; + + /* i2c */ + struct mutex i2c_mutex; + struct i2c_adapter i2c_adap; + + struct dvb_usb_adapter adapter[MAX_NO_OF_ADAPTER_PER_DEVICE]; + + /* remote control */ + struct rc_dev *rc_dev; + char rc_phys[64]; + struct delayed_work rc_query_work; + + void *priv; +}; + +extern int dvb_usbv2_probe(struct usb_interface *, + const struct usb_device_id *); +extern void dvb_usbv2_disconnect(struct usb_interface *); +extern int dvb_usbv2_suspend(struct usb_interface *, pm_message_t); +extern int dvb_usbv2_resume(struct usb_interface *); +extern int dvb_usbv2_reset_resume(struct usb_interface *); + +/* the generic read/write method for device control */ +extern int dvb_usbv2_generic_rw(struct dvb_usb_device *, u8 *, u16, u8 *, u16); +extern int dvb_usbv2_generic_write(struct dvb_usb_device *, u8 *, u16); + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_common.h b/drivers/media/usb/dvb-usb-v2/dvb_usb_common.h new file mode 100644 index 000000000000..45f07090d431 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_common.h @@ -0,0 +1,35 @@ +/* + * DVB USB framework + * + * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@desy.de> + * Copyright (C) 2012 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef DVB_USB_COMMON_H +#define DVB_USB_COMMON_H + +#include "dvb_usb.h" + +/* commonly used methods */ +extern int usb_urb_initv2(struct usb_data_stream *stream, + const struct usb_data_stream_properties *props); +extern int usb_urb_exitv2(struct usb_data_stream *stream); +extern int usb_urb_submitv2(struct usb_data_stream *stream, + struct usb_data_stream_properties *props); +extern int usb_urb_killv2(struct usb_data_stream *stream); + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c new file mode 100644 index 000000000000..9859d2a2449b --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c @@ -0,0 +1,1049 @@ +/* + * DVB USB framework + * + * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@desy.de> + * Copyright (C) 2012 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "dvb_usb_common.h" + +int dvb_usbv2_disable_rc_polling; +module_param_named(disable_rc_polling, dvb_usbv2_disable_rc_polling, int, 0644); +MODULE_PARM_DESC(disable_rc_polling, + "disable remote control polling (default: 0)"); +static int dvb_usb_force_pid_filter_usage; +module_param_named(force_pid_filter_usage, dvb_usb_force_pid_filter_usage, + int, 0444); +MODULE_PARM_DESC(force_pid_filter_usage, "force all DVB USB devices to use a " \ + "PID filter, if any (default: 0)"); + +static int dvb_usbv2_download_firmware(struct dvb_usb_device *d, const char *name) +{ + int ret; + const struct firmware *fw; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (!d->props->download_firmware) { + ret = -EINVAL; + goto err; + } + + ret = request_firmware(&fw, name, &d->udev->dev); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: Did not find the firmware file "\ + "'%s'. Please see linux/Documentation/dvb/ " \ + "for more details on firmware-problems. " \ + "Status %d\n", KBUILD_MODNAME, name, ret); + goto err; + } + + dev_info(&d->udev->dev, "%s: downloading firmware from file '%s'\n", + KBUILD_MODNAME, name); + + ret = d->props->download_firmware(d, fw); + release_firmware(fw); + if (ret < 0) + goto err; + + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_i2c_init(struct dvb_usb_device *d) +{ + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (!d->props->i2c_algo) + return 0; + + strlcpy(d->i2c_adap.name, d->name, sizeof(d->i2c_adap.name)); + d->i2c_adap.algo = d->props->i2c_algo; + d->i2c_adap.dev.parent = &d->udev->dev; + i2c_set_adapdata(&d->i2c_adap, d); + + ret = i2c_add_adapter(&d->i2c_adap); + if (ret < 0) { + d->i2c_adap.algo = NULL; + dev_err(&d->udev->dev, "%s: i2c_add_adapter() failed=%d\n", + KBUILD_MODNAME, ret); + goto err; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_i2c_exit(struct dvb_usb_device *d) +{ + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (d->i2c_adap.algo) + i2c_del_adapter(&d->i2c_adap); + + return 0; +} + +static void dvb_usb_read_remote_control(struct work_struct *work) +{ + struct dvb_usb_device *d = container_of(work, + struct dvb_usb_device, rc_query_work.work); + int ret; + + /* + * When the parameter has been set to 1 via sysfs while the + * driver was running, or when bulk mode is enabled after IR init. + */ + if (dvb_usbv2_disable_rc_polling || d->rc.bulk_mode) + return; + + ret = d->rc.query(d); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: rc.query() failed=%d\n", + KBUILD_MODNAME, ret); + return; /* stop polling */ + } + + schedule_delayed_work(&d->rc_query_work, + msecs_to_jiffies(d->rc.interval)); +} + +static int dvb_usbv2_remote_init(struct dvb_usb_device *d) +{ + int ret; + struct rc_dev *dev; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (dvb_usbv2_disable_rc_polling || !d->props->get_rc_config) + return 0; + + d->rc.map_name = d->rc_map; + ret = d->props->get_rc_config(d, &d->rc); + if (ret < 0) + goto err; + + /* disable rc when there is no keymap defined */ + if (!d->rc.map_name) + return 0; + + dev = rc_allocate_device(); + if (!dev) { + ret = -ENOMEM; + goto err; + } + + dev->dev.parent = &d->udev->dev; + dev->input_name = d->name; + usb_make_path(d->udev, d->rc_phys, sizeof(d->rc_phys)); + strlcat(d->rc_phys, "/ir0", sizeof(d->rc_phys)); + dev->input_phys = d->rc_phys; + usb_to_input_id(d->udev, &dev->input_id); + /* TODO: likely RC-core should took const char * */ + dev->driver_name = (char *) d->props->driver_name; + dev->map_name = d->rc.map_name; + dev->driver_type = d->rc.driver_type; + dev->allowed_protos = d->rc.allowed_protos; + dev->change_protocol = d->rc.change_protocol; + dev->priv = d; + + ret = rc_register_device(dev); + if (ret < 0) { + rc_free_device(dev); + goto err; + } + + d->rc_dev = dev; + + /* start polling if needed */ + if (d->rc.query && !d->rc.bulk_mode) { + /* initialize a work queue for handling polling */ + INIT_DELAYED_WORK(&d->rc_query_work, + dvb_usb_read_remote_control); + dev_info(&d->udev->dev, "%s: schedule remote query interval " \ + "to %d msecs\n", KBUILD_MODNAME, + d->rc.interval); + schedule_delayed_work(&d->rc_query_work, + msecs_to_jiffies(d->rc.interval)); + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_remote_exit(struct dvb_usb_device *d) +{ + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + if (d->rc_dev) { + cancel_delayed_work_sync(&d->rc_query_work); + rc_unregister_device(d->rc_dev); + d->rc_dev = NULL; + } + + return 0; +} + +static void dvb_usb_data_complete(struct usb_data_stream *stream, u8 *buf, + size_t len) +{ + struct dvb_usb_adapter *adap = stream->user_priv; + dvb_dmx_swfilter(&adap->demux, buf, len); +} + +static void dvb_usb_data_complete_204(struct usb_data_stream *stream, u8 *buf, + size_t len) +{ + struct dvb_usb_adapter *adap = stream->user_priv; + dvb_dmx_swfilter_204(&adap->demux, buf, len); +} + +static void dvb_usb_data_complete_raw(struct usb_data_stream *stream, u8 *buf, + size_t len) +{ + struct dvb_usb_adapter *adap = stream->user_priv; + dvb_dmx_swfilter_raw(&adap->demux, buf, len); +} + +int dvb_usbv2_adapter_stream_init(struct dvb_usb_adapter *adap) +{ + dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, + adap->id); + + adap->stream.udev = adap_to_d(adap)->udev; + adap->stream.user_priv = adap; + adap->stream.complete = dvb_usb_data_complete; + + return usb_urb_initv2(&adap->stream, &adap->props->stream); +} + +int dvb_usbv2_adapter_stream_exit(struct dvb_usb_adapter *adap) +{ + dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, + adap->id); + + return usb_urb_exitv2(&adap->stream); +} + +static inline int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, + int count) +{ + struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv; + struct dvb_usb_device *d = adap_to_d(adap); + int ret; + dev_dbg(&d->udev->dev, "%s: adap=%d active_fe=%d feed_type=%d " \ + "setting pid [%s]: %04x (%04d) at index %d '%s'\n", + __func__, adap->id, adap->active_fe, dvbdmxfeed->type, + adap->pid_filtering ? "yes" : "no", dvbdmxfeed->pid, + dvbdmxfeed->pid, dvbdmxfeed->index, + (count == 1) ? "on" : "off"); + + if (adap->active_fe == -1) + return -EINVAL; + + adap->feed_count += count; + + /* stop feeding if it is last pid */ + if (adap->feed_count == 0) { + dev_dbg(&d->udev->dev, "%s: stop feeding\n", __func__); + + if (d->props->streaming_ctrl) { + ret = d->props->streaming_ctrl( + adap->fe[adap->active_fe], 0); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: streaming_ctrl() " \ + "failed=%d\n", KBUILD_MODNAME, + ret); + usb_urb_killv2(&adap->stream); + goto err_mutex_unlock; + } + } + usb_urb_killv2(&adap->stream); + mutex_unlock(&adap->sync_mutex); + } + + /* activate the pid on the device pid filter */ + if (adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER && + adap->pid_filtering && + adap->props->pid_filter) + ret = adap->props->pid_filter(adap, dvbdmxfeed->index, + dvbdmxfeed->pid, (count == 1) ? 1 : 0); + if (ret < 0) + dev_err(&d->udev->dev, "%s: pid_filter() " \ + "failed=%d\n", KBUILD_MODNAME, + ret); + + /* start feeding if it is first pid */ + if (adap->feed_count == 1 && count == 1) { + struct usb_data_stream_properties stream_props; + mutex_lock(&adap->sync_mutex); + dev_dbg(&d->udev->dev, "%s: start feeding\n", __func__); + + /* resolve input and output streaming paramters */ + if (d->props->get_stream_config) { + memcpy(&stream_props, &adap->props->stream, + sizeof(struct usb_data_stream_properties)); + ret = d->props->get_stream_config( + adap->fe[adap->active_fe], + &adap->ts_type, &stream_props); + if (ret < 0) + goto err_mutex_unlock; + } else { + stream_props = adap->props->stream; + } + + switch (adap->ts_type) { + case DVB_USB_FE_TS_TYPE_204: + adap->stream.complete = dvb_usb_data_complete_204; + break; + case DVB_USB_FE_TS_TYPE_RAW: + adap->stream.complete = dvb_usb_data_complete_raw; + break; + case DVB_USB_FE_TS_TYPE_188: + default: + adap->stream.complete = dvb_usb_data_complete; + break; + } + + usb_urb_submitv2(&adap->stream, &stream_props); + + if (adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER && + adap->props->caps & + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF && + adap->props->pid_filter_ctrl) { + ret = adap->props->pid_filter_ctrl(adap, + adap->pid_filtering); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: " \ + "pid_filter_ctrl() failed=%d\n", + KBUILD_MODNAME, ret); + goto err_mutex_unlock; + } + } + + if (d->props->streaming_ctrl) { + ret = d->props->streaming_ctrl( + adap->fe[adap->active_fe], 1); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: streaming_ctrl() " \ + "failed=%d\n", KBUILD_MODNAME, + ret); + goto err_mutex_unlock; + } + } + } + + return 0; +err_mutex_unlock: + mutex_unlock(&adap->sync_mutex); + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + return dvb_usb_ctrl_feed(dvbdmxfeed, 1); +} + +static int dvb_usb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + return dvb_usb_ctrl_feed(dvbdmxfeed, -1); +} + +int dvb_usbv2_adapter_dvb_init(struct dvb_usb_adapter *adap) +{ + int ret; + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id); + + ret = dvb_register_adapter(&adap->dvb_adap, d->name, d->props->owner, + &d->udev->dev, d->props->adapter_nr); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: dvb_register_adapter() failed=%d\n", + __func__, ret); + goto err_dvb_register_adapter; + } + + adap->dvb_adap.priv = adap; + + if (d->props->read_mac_address) { + ret = d->props->read_mac_address(adap, + adap->dvb_adap.proposed_mac); + if (ret < 0) + goto err_dvb_dmx_init; + + dev_info(&d->udev->dev, "%s: MAC address: %pM\n", + KBUILD_MODNAME, adap->dvb_adap.proposed_mac); + } + + adap->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; + adap->demux.priv = adap; + adap->demux.filternum = 0; + adap->demux.filternum = adap->max_feed_count; + adap->demux.feednum = adap->demux.filternum; + adap->demux.start_feed = dvb_usb_start_feed; + adap->demux.stop_feed = dvb_usb_stop_feed; + adap->demux.write_to_decoder = NULL; + ret = dvb_dmx_init(&adap->demux); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: dvb_dmx_init() failed=%d\n", + KBUILD_MODNAME, ret); + goto err_dvb_dmx_init; + } + + adap->dmxdev.filternum = adap->demux.filternum; + adap->dmxdev.demux = &adap->demux.dmx; + adap->dmxdev.capabilities = 0; + ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: dvb_dmxdev_init() failed=%d\n", + KBUILD_MODNAME, ret); + goto err_dvb_dmxdev_init; + } + + ret = dvb_net_init(&adap->dvb_adap, &adap->dvb_net, &adap->demux.dmx); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: dvb_net_init() failed=%d\n", + KBUILD_MODNAME, ret); + goto err_dvb_net_init; + } + + mutex_init(&adap->sync_mutex); + + return 0; +err_dvb_net_init: + dvb_dmxdev_release(&adap->dmxdev); +err_dvb_dmxdev_init: + dvb_dmx_release(&adap->demux); +err_dvb_dmx_init: + dvb_unregister_adapter(&adap->dvb_adap); +err_dvb_register_adapter: + adap->dvb_adap.priv = NULL; + return ret; +} + +int dvb_usbv2_adapter_dvb_exit(struct dvb_usb_adapter *adap) +{ + dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, + adap->id); + + if (adap->dvb_adap.priv) { + dvb_net_release(&adap->dvb_net); + adap->demux.dmx.close(&adap->demux.dmx); + dvb_dmxdev_release(&adap->dmxdev); + dvb_dmx_release(&adap->demux); + dvb_unregister_adapter(&adap->dvb_adap); + } + + return 0; +} + +int dvb_usbv2_device_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + int ret; + + if (onoff) + d->powered++; + else + d->powered--; + + if (d->powered == 0 || (onoff && d->powered == 1)) { + /* when switching from 1 to 0 or from 0 to 1 */ + dev_dbg(&d->udev->dev, "%s: power=%d\n", __func__, onoff); + if (d->props->power_ctrl) { + ret = d->props->power_ctrl(d, onoff); + if (ret < 0) + goto err; + } + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usb_fe_init(struct dvb_frontend *fe) +{ + int ret; + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s: adap=%d fe=%d\n", __func__, adap->id, + fe->id); + + if (!adap->suspend_resume_active) { + adap->active_fe = fe->id; + mutex_lock(&adap->sync_mutex); + } + + ret = dvb_usbv2_device_power_ctrl(d, 1); + if (ret < 0) + goto err; + + if (d->props->frontend_ctrl) { + ret = d->props->frontend_ctrl(fe, 1); + if (ret < 0) + goto err; + } + + if (adap->fe_init[fe->id]) { + ret = adap->fe_init[fe->id](fe); + if (ret < 0) + goto err; + } +err: + if (!adap->suspend_resume_active) + mutex_unlock(&adap->sync_mutex); + + dev_dbg(&d->udev->dev, "%s: ret=%d\n", __func__, ret); + return ret; +} + +static int dvb_usb_fe_sleep(struct dvb_frontend *fe) +{ + int ret; + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s: adap=%d fe=%d\n", __func__, adap->id, + fe->id); + + if (!adap->suspend_resume_active) + mutex_lock(&adap->sync_mutex); + + if (adap->fe_sleep[fe->id]) { + ret = adap->fe_sleep[fe->id](fe); + if (ret < 0) + goto err; + } + + if (d->props->frontend_ctrl) { + ret = d->props->frontend_ctrl(fe, 0); + if (ret < 0) + goto err; + } + + ret = dvb_usbv2_device_power_ctrl(d, 0); + if (ret < 0) + goto err; +err: + if (!adap->suspend_resume_active) { + adap->active_fe = -1; + mutex_unlock(&adap->sync_mutex); + } + + dev_dbg(&d->udev->dev, "%s: ret=%d\n", __func__, ret); + return ret; +} + +int dvb_usbv2_adapter_frontend_init(struct dvb_usb_adapter *adap) +{ + int ret, i, count_registered = 0; + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id); + + memset(adap->fe, 0, sizeof(adap->fe)); + adap->active_fe = -1; + + if (d->props->frontend_attach) { + ret = d->props->frontend_attach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: frontend_attach() " \ + "failed=%d\n", __func__, ret); + goto err_dvb_frontend_detach; + } + } else { + dev_dbg(&d->udev->dev, "%s: frontend_attach() do not exists\n", + __func__); + ret = 0; + goto err; + } + + for (i = 0; i < MAX_NO_OF_FE_PER_ADAP && adap->fe[i]; i++) { + adap->fe[i]->id = i; + /* re-assign sleep and wakeup functions */ + adap->fe_init[i] = adap->fe[i]->ops.init; + adap->fe[i]->ops.init = dvb_usb_fe_init; + adap->fe_sleep[i] = adap->fe[i]->ops.sleep; + adap->fe[i]->ops.sleep = dvb_usb_fe_sleep; + + ret = dvb_register_frontend(&adap->dvb_adap, adap->fe[i]); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: frontend%d registration " \ + "failed\n", KBUILD_MODNAME, i); + goto err_dvb_unregister_frontend; + } + + count_registered++; + } + + if (d->props->tuner_attach) { + ret = d->props->tuner_attach(adap); + if (ret < 0) { + dev_dbg(&d->udev->dev, "%s: tuner_attach() failed=%d\n", + __func__, ret); + goto err_dvb_unregister_frontend; + } + } + + return 0; + +err_dvb_unregister_frontend: + for (i = count_registered - 1; i >= 0; i--) + dvb_unregister_frontend(adap->fe[i]); + +err_dvb_frontend_detach: + for (i = MAX_NO_OF_FE_PER_ADAP - 1; i >= 0; i--) { + if (adap->fe[i]) { + dvb_frontend_detach(adap->fe[i]); + adap->fe[i] = NULL; + } + } + +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int dvb_usbv2_adapter_frontend_exit(struct dvb_usb_adapter *adap) +{ + int i; + dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__, + adap->id); + + for (i = MAX_NO_OF_FE_PER_ADAP - 1; i >= 0; i--) { + if (adap->fe[i]) { + dvb_unregister_frontend(adap->fe[i]); + dvb_frontend_detach(adap->fe[i]); + } + } + + return 0; +} + +static int dvb_usbv2_adapter_init(struct dvb_usb_device *d) +{ + struct dvb_usb_adapter *adap; + int ret, i, adapter_count; + + /* resolve adapter count */ + adapter_count = d->props->num_adapters; + if (d->props->get_adapter_count) { + ret = d->props->get_adapter_count(d); + if (ret < 0) + goto err; + + adapter_count = ret; + } + + for (i = 0; i < adapter_count; i++) { + adap = &d->adapter[i]; + adap->id = i; + adap->props = &d->props->adapter[i]; + + /* speed - when running at FULL speed we need a HW PID filter */ + if (d->udev->speed == USB_SPEED_FULL && + !(adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER)) { + dev_err(&d->udev->dev, "%s: this USB2.0 device " \ + "cannot be run on a USB1.1 port (it " \ + "lacks a hardware PID filter)\n", + KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } else if ((d->udev->speed == USB_SPEED_FULL && + adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER) || + (adap->props->caps & DVB_USB_ADAP_NEED_PID_FILTERING)) { + dev_info(&d->udev->dev, "%s: will use the device's " \ + "hardware PID filter " \ + "(table count: %d)\n", KBUILD_MODNAME, + adap->props->pid_filter_count); + adap->pid_filtering = 1; + adap->max_feed_count = adap->props->pid_filter_count; + } else { + dev_info(&d->udev->dev, "%s: will pass the complete " \ + "MPEG2 transport stream to the " \ + "software demuxer\n", KBUILD_MODNAME); + adap->pid_filtering = 0; + adap->max_feed_count = 255; + } + + if (!adap->pid_filtering && dvb_usb_force_pid_filter_usage && + adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER) { + dev_info(&d->udev->dev, "%s: PID filter enabled by " \ + "module option\n", KBUILD_MODNAME); + adap->pid_filtering = 1; + adap->max_feed_count = adap->props->pid_filter_count; + } + + ret = dvb_usbv2_adapter_stream_init(adap); + if (ret) + goto err; + + ret = dvb_usbv2_adapter_dvb_init(adap); + if (ret) + goto err; + + ret = dvb_usbv2_adapter_frontend_init(adap); + if (ret) + goto err; + + /* use exclusive FE lock if there is multiple shared FEs */ + if (adap->fe[1]) + adap->dvb_adap.mfe_shared = 1; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int dvb_usbv2_adapter_exit(struct dvb_usb_device *d) +{ + int i; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + for (i = MAX_NO_OF_ADAPTER_PER_DEVICE - 1; i >= 0; i--) { + if (d->adapter[i].props) { + dvb_usbv2_adapter_frontend_exit(&d->adapter[i]); + dvb_usbv2_adapter_dvb_exit(&d->adapter[i]); + dvb_usbv2_adapter_stream_exit(&d->adapter[i]); + } + } + + return 0; +} + +/* general initialization functions */ +static int dvb_usbv2_exit(struct dvb_usb_device *d) +{ + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + dvb_usbv2_remote_exit(d); + dvb_usbv2_adapter_exit(d); + dvb_usbv2_i2c_exit(d); + kfree(d->priv); + kfree(d); + + return 0; +} + +static int dvb_usbv2_init(struct dvb_usb_device *d) +{ + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + dvb_usbv2_device_power_ctrl(d, 1); + + if (d->props->read_config) { + ret = d->props->read_config(d); + if (ret < 0) + goto err; + } + + ret = dvb_usbv2_i2c_init(d); + if (ret < 0) + goto err; + + ret = dvb_usbv2_adapter_init(d); + if (ret < 0) + goto err; + + if (d->props->init) { + ret = d->props->init(d); + if (ret < 0) + goto err; + } + + ret = dvb_usbv2_remote_init(d); + if (ret < 0) + goto err; + + dvb_usbv2_device_power_ctrl(d, 0); + + return 0; +err: + dvb_usbv2_device_power_ctrl(d, 0); + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +/* + * udev, which is used for the firmware downloading, requires we cannot + * block during module_init(). module_init() calls USB probe() which + * is this routine. Due to that we delay actual operation using workqueue + * and return always success here. + */ +static void dvb_usbv2_init_work(struct work_struct *work) +{ + int ret; + struct dvb_usb_device *d = + container_of(work, struct dvb_usb_device, probe_work); + + d->work_pid = current->pid; + dev_dbg(&d->udev->dev, "%s: work_pid=%d\n", __func__, d->work_pid); + + if (d->props->size_of_priv) { + d->priv = kzalloc(d->props->size_of_priv, GFP_KERNEL); + if (!d->priv) { + dev_err(&d->udev->dev, "%s: kzalloc() failed\n", + KBUILD_MODNAME); + ret = -ENOMEM; + goto err_usb_driver_release_interface; + } + } + + if (d->props->identify_state) { + const char *name = NULL; + ret = d->props->identify_state(d, &name); + if (ret == 0) { + ; + } else if (ret == COLD) { + dev_info(&d->udev->dev, "%s: found a '%s' in cold " \ + "state\n", KBUILD_MODNAME, d->name); + + if (!name) + name = d->props->firmware; + + ret = dvb_usbv2_download_firmware(d, name); + if (ret == 0) { + /* device is warm, continue initialization */ + ; + } else if (ret == RECONNECTS_USB) { + /* + * USB core will call disconnect() and then + * probe() as device reconnects itself from the + * USB bus. disconnect() will release all driver + * resources and probe() is called for 'new' + * device. As 'new' device is warm we should + * never go here again. + */ + return; + } else { + /* + * Unexpected error. We must unregister driver + * manually from the device, because device is + * already register by returning from probe() + * with success. usb_driver_release_interface() + * finally calls disconnect() in order to free + * resources. + */ + goto err_usb_driver_release_interface; + } + } else { + goto err_usb_driver_release_interface; + } + } + + dev_info(&d->udev->dev, "%s: found a '%s' in warm state\n", + KBUILD_MODNAME, d->name); + + ret = dvb_usbv2_init(d); + if (ret < 0) + goto err_usb_driver_release_interface; + + dev_info(&d->udev->dev, "%s: '%s' successfully initialized and " \ + "connected\n", KBUILD_MODNAME, d->name); + + return; +err_usb_driver_release_interface: + dev_info(&d->udev->dev, "%s: '%s' error while loading driver (%d)\n", + KBUILD_MODNAME, d->name, ret); + usb_driver_release_interface(to_usb_driver(d->intf->dev.driver), + d->intf); + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return; +} + +int dvb_usbv2_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret; + struct dvb_usb_device *d; + struct usb_device *udev = interface_to_usbdev(intf); + struct dvb_usb_driver_info *driver_info = + (struct dvb_usb_driver_info *) id->driver_info; + + dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + + if (!id->driver_info) { + dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME); + ret = -ENODEV; + goto err; + } + + d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL); + if (!d) { + dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + ret = -ENOMEM; + goto err; + } + + d->name = driver_info->name; + d->rc_map = driver_info->rc_map; + d->udev = udev; + d->intf = intf; + d->props = driver_info->props; + + if (d->intf->cur_altsetting->desc.bInterfaceNumber != + d->props->bInterfaceNumber) { + ret = -ENODEV; + goto err_kfree; + } + + mutex_init(&d->usb_mutex); + mutex_init(&d->i2c_mutex); + INIT_WORK(&d->probe_work, dvb_usbv2_init_work); + usb_set_intfdata(intf, d); + ret = schedule_work(&d->probe_work); + if (ret < 0) { + dev_err(&d->udev->dev, "%s: schedule_work() failed\n", + KBUILD_MODNAME); + goto err_kfree; + } + + return 0; +err_kfree: + kfree(d); +err: + dev_dbg(&udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL(dvb_usbv2_probe); + +void dvb_usbv2_disconnect(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + const char *name = d->name; + struct device dev = d->udev->dev; + dev_dbg(&d->udev->dev, "%s: pid=%d work_pid=%d\n", __func__, + current->pid, d->work_pid); + + /* ensure initialization work is finished until release resources */ + if (d->work_pid != current->pid) + cancel_work_sync(&d->probe_work); + + if (d->props->exit) + d->props->exit(d); + + dvb_usbv2_exit(d); + + dev_info(&dev, "%s: '%s' successfully deinitialized and disconnected\n", + KBUILD_MODNAME, name); +} +EXPORT_SYMBOL(dvb_usbv2_disconnect); + +int dvb_usbv2_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + int ret = 0, i, active_fe; + struct dvb_frontend *fe; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* stop remote controller poll */ + if (d->rc.query && !d->rc.bulk_mode) + cancel_delayed_work_sync(&d->rc_query_work); + + for (i = MAX_NO_OF_ADAPTER_PER_DEVICE - 1; i >= 0; i--) { + active_fe = d->adapter[i].active_fe; + if (d->adapter[i].dvb_adap.priv && active_fe != -1) { + fe = d->adapter[i].fe[active_fe]; + d->adapter[i].suspend_resume_active = true; + + if (d->props->streaming_ctrl) + d->props->streaming_ctrl(fe, 0); + + /* stop usb streaming */ + usb_urb_killv2(&d->adapter[i].stream); + + ret = dvb_frontend_suspend(fe); + } + } + + return ret; +} +EXPORT_SYMBOL(dvb_usbv2_suspend); + +static int dvb_usbv2_resume_common(struct dvb_usb_device *d) +{ + int ret = 0, i, active_fe; + struct dvb_frontend *fe; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + for (i = 0; i < MAX_NO_OF_ADAPTER_PER_DEVICE; i++) { + active_fe = d->adapter[i].active_fe; + if (d->adapter[i].dvb_adap.priv && active_fe != -1) { + fe = d->adapter[i].fe[active_fe]; + + ret = dvb_frontend_resume(fe); + + /* resume usb streaming */ + usb_urb_submitv2(&d->adapter[i].stream, NULL); + + if (d->props->streaming_ctrl) + d->props->streaming_ctrl(fe, 1); + + d->adapter[i].suspend_resume_active = false; + } + } + + /* start remote controller poll */ + if (d->rc.query && !d->rc.bulk_mode) + schedule_delayed_work(&d->rc_query_work, + msecs_to_jiffies(d->rc.interval)); + + return ret; +} + +int dvb_usbv2_resume(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + return dvb_usbv2_resume_common(d); +} +EXPORT_SYMBOL(dvb_usbv2_resume); + +int dvb_usbv2_reset_resume(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + int ret; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + dvb_usbv2_device_power_ctrl(d, 1); + + if (d->props->init) + d->props->init(d); + + ret = dvb_usbv2_resume_common(d); + + dvb_usbv2_device_power_ctrl(d, 0); + + return ret; +} +EXPORT_SYMBOL(dvb_usbv2_reset_resume); + +MODULE_VERSION("2.0"); +MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@desy.de>"); +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("DVB USB common"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_urb.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_urb.c new file mode 100644 index 000000000000..0431beed0ef4 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_urb.c @@ -0,0 +1,77 @@ +/* + * DVB USB framework + * + * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@desy.de> + * Copyright (C) 2012 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "dvb_usb_common.h" + +int dvb_usbv2_generic_rw(struct dvb_usb_device *d, u8 *wbuf, u16 wlen, u8 *rbuf, + u16 rlen) +{ + int ret, actual_length; + + if (!d || !wbuf || !wlen || !d->props->generic_bulk_ctrl_endpoint || + !d->props->generic_bulk_ctrl_endpoint_response) { + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, -EINVAL); + return -EINVAL; + } + + ret = mutex_lock_interruptible(&d->usb_mutex); + if (ret < 0) + return ret; + + dev_dbg(&d->udev->dev, "%s: >>> %*ph\n", __func__, wlen, wbuf); + + ret = usb_bulk_msg(d->udev, usb_sndbulkpipe(d->udev, + d->props->generic_bulk_ctrl_endpoint), wbuf, wlen, + &actual_length, 2000); + if (ret < 0) + dev_err(&d->udev->dev, "%s: usb_bulk_msg() failed=%d\n", + KBUILD_MODNAME, ret); + else + ret = actual_length != wlen ? -EIO : 0; + + /* an answer is expected, and no error before */ + if (!ret && rbuf && rlen) { + if (d->props->generic_bulk_ctrl_delay) + usleep_range(d->props->generic_bulk_ctrl_delay, + d->props->generic_bulk_ctrl_delay + + 20000); + + ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev, + d->props->generic_bulk_ctrl_endpoint_response), + rbuf, rlen, &actual_length, 2000); + if (ret) + dev_err(&d->udev->dev, "%s: 2nd usb_bulk_msg() " \ + "failed=%d\n", KBUILD_MODNAME, ret); + + dev_dbg(&d->udev->dev, "%s: <<< %*ph\n", __func__, + actual_length, rbuf); + } + + mutex_unlock(&d->usb_mutex); + return ret; +} +EXPORT_SYMBOL(dvb_usbv2_generic_rw); + +int dvb_usbv2_generic_write(struct dvb_usb_device *d, u8 *buf, u16 len) +{ + return dvb_usbv2_generic_rw(d, buf, len, NULL, 0); +} +EXPORT_SYMBOL(dvb_usbv2_generic_write); diff --git a/drivers/media/usb/dvb-usb-v2/ec168.c b/drivers/media/usb/dvb-usb-v2/ec168.c new file mode 100644 index 000000000000..5c68f3918bc8 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/ec168.c @@ -0,0 +1,385 @@ +/* + * E3C EC168 DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * + * 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 "ec168.h" +#include "ec100.h" +#include "mxl5005s.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int ec168_ctrl_msg(struct dvb_usb_device *d, struct ec168_req *req) +{ + int ret; + unsigned int pipe; + u8 request, requesttype; + u8 *buf; + + switch (req->cmd) { + case DOWNLOAD_FIRMWARE: + case GPIO: + case WRITE_I2C: + case STREAMING_CTRL: + requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); + request = req->cmd; + break; + case READ_I2C: + requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); + request = req->cmd; + break; + case GET_CONFIG: + requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); + request = CONFIG; + break; + case SET_CONFIG: + requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); + request = CONFIG; + break; + case WRITE_DEMOD: + requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); + request = DEMOD_RW; + break; + case READ_DEMOD: + requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); + request = DEMOD_RW; + break; + default: + dev_err(&d->udev->dev, "%s: unknown command=%02x\n", + KBUILD_MODNAME, req->cmd); + ret = -EINVAL; + goto error; + } + + buf = kmalloc(req->size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto error; + } + + if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) { + /* write */ + memcpy(buf, req->data, req->size); + pipe = usb_sndctrlpipe(d->udev, 0); + } else { + /* read */ + pipe = usb_rcvctrlpipe(d->udev, 0); + } + + msleep(1); /* avoid I2C errors */ + + ret = usb_control_msg(d->udev, pipe, request, requesttype, req->value, + req->index, buf, req->size, EC168_USB_TIMEOUT); + + dvb_usb_dbg_usb_control_msg(d->udev, request, requesttype, req->value, + req->index, buf, req->size); + + if (ret < 0) + goto err_dealloc; + else + ret = 0; + + /* read request, copy returned data to return buf */ + if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) + memcpy(req->data, buf, req->size); + + kfree(buf); + return ret; + +err_dealloc: + kfree(buf); +error: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +/* I2C */ +static struct ec100_config ec168_ec100_config; + +static int ec168_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct ec168_req req; + int i = 0; + int ret; + + if (num > 2) + return -EINVAL; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + while (i < num) { + if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { + if (msg[i].addr == ec168_ec100_config.demod_address) { + req.cmd = READ_DEMOD; + req.value = 0; + req.index = 0xff00 + msg[i].buf[0]; /* reg */ + req.size = msg[i+1].len; /* bytes to read */ + req.data = &msg[i+1].buf[0]; + ret = ec168_ctrl_msg(d, &req); + i += 2; + } else { + dev_err(&d->udev->dev, "%s: I2C read not " \ + "implemented\n", + KBUILD_MODNAME); + ret = -EOPNOTSUPP; + i += 2; + } + } else { + if (msg[i].addr == ec168_ec100_config.demod_address) { + req.cmd = WRITE_DEMOD; + req.value = msg[i].buf[1]; /* val */ + req.index = 0xff00 + msg[i].buf[0]; /* reg */ + req.size = 0; + req.data = NULL; + ret = ec168_ctrl_msg(d, &req); + i += 1; + } else { + req.cmd = WRITE_I2C; + req.value = msg[i].buf[0]; /* val */ + req.index = 0x0100 + msg[i].addr; /* I2C addr */ + req.size = msg[i].len-1; + req.data = &msg[i].buf[1]; + ret = ec168_ctrl_msg(d, &req); + i += 1; + } + } + if (ret) + goto error; + + } + ret = i; + +error: + mutex_unlock(&d->i2c_mutex); + return i; +} + +static u32 ec168_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm ec168_i2c_algo = { + .master_xfer = ec168_i2c_xfer, + .functionality = ec168_i2c_func, +}; + +/* Callbacks for DVB USB */ +static int ec168_identify_state(struct dvb_usb_device *d, const char **name) +{ + int ret; + u8 reply; + struct ec168_req req = {GET_CONFIG, 0, 1, sizeof(reply), &reply}; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + ret = ec168_ctrl_msg(d, &req); + if (ret) + goto error; + + dev_dbg(&d->udev->dev, "%s: reply=%02x\n", __func__, reply); + + if (reply == 0x01) + ret = WARM; + else + ret = COLD; + + return ret; +error: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int ec168_download_firmware(struct dvb_usb_device *d, + const struct firmware *fw) +{ + int ret, len, remaining; + struct ec168_req req = {DOWNLOAD_FIRMWARE, 0, 0, 0, NULL}; + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + #define LEN_MAX 2048 /* max packet size */ + for (remaining = fw->size; remaining > 0; remaining -= LEN_MAX) { + len = remaining; + if (len > LEN_MAX) + len = LEN_MAX; + + req.size = len; + req.data = (u8 *) &fw->data[fw->size - remaining]; + req.index = fw->size - remaining; + + ret = ec168_ctrl_msg(d, &req); + if (ret) { + dev_err(&d->udev->dev, + "%s: firmware download failed=%d\n", + KBUILD_MODNAME, ret); + goto error; + } + } + + req.size = 0; + + /* set "warm"? */ + req.cmd = SET_CONFIG; + req.value = 0; + req.index = 0x0001; + ret = ec168_ctrl_msg(d, &req); + if (ret) + goto error; + + /* really needed - no idea what does */ + req.cmd = GPIO; + req.value = 0; + req.index = 0x0206; + ret = ec168_ctrl_msg(d, &req); + if (ret) + goto error; + + /* activate tuner I2C? */ + req.cmd = WRITE_I2C; + req.value = 0; + req.index = 0x00c6; + ret = ec168_ctrl_msg(d, &req); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct ec100_config ec168_ec100_config = { + .demod_address = 0xff, /* not real address, demod is integrated */ +}; + +static int ec168_ec100_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + adap->fe[0] = dvb_attach(ec100_attach, &ec168_ec100_config, + &d->i2c_adap); + if (adap->fe[0] == NULL) + return -ENODEV; + + return 0; +} + +static struct mxl5005s_config ec168_mxl5003s_config = { + .i2c_address = 0xc6, + .if_freq = IF_FREQ_4570000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_OFF, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .AgcMasterByte = 0x00, +}; + +static int ec168_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + return dvb_attach(mxl5005s_attach, adap->fe[0], &d->i2c_adap, + &ec168_mxl5003s_config) == NULL ? -ENODEV : 0; +} + +static int ec168_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct ec168_req req = {STREAMING_CTRL, 0x7f01, 0x0202, 0, NULL}; + dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff); + + if (onoff) + req.index = 0x0102; + return ec168_ctrl_msg(d, &req); +} + +/* DVB USB Driver stuff */ +/* bInterfaceNumber 0 is HID + * bInterfaceNumber 1 is DVB-T */ +static struct dvb_usb_device_properties ec168_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .bInterfaceNumber = 1, + + .identify_state = ec168_identify_state, + .firmware = EC168_FIRMWARE, + .download_firmware = ec168_download_firmware, + + .i2c_algo = &ec168_i2c_algo, + .frontend_attach = ec168_ec100_frontend_attach, + .tuner_attach = ec168_mxl5003s_tuner_attach, + .streaming_ctrl = ec168_streaming_ctrl, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 6, 32 * 512), + } + }, +}; + +static const struct dvb_usb_driver_info ec168_driver_info = { + .name = "E3C EC168 reference design", + .props = &ec168_props, +}; + +static const struct usb_device_id ec168_id[] = { + { USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168), + .driver_info = (kernel_ulong_t) &ec168_driver_info }, + { USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_2), + .driver_info = (kernel_ulong_t) &ec168_driver_info }, + { USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_3), + .driver_info = (kernel_ulong_t) &ec168_driver_info }, + { USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_4), + .driver_info = (kernel_ulong_t) &ec168_driver_info }, + { USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_5), + .driver_info = (kernel_ulong_t) &ec168_driver_info }, + {} +}; +MODULE_DEVICE_TABLE(usb, ec168_id); + +static struct usb_driver ec168_driver = { + .name = KBUILD_MODNAME, + .id_table = ec168_id, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(ec168_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("E3C EC168 driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(EC168_FIRMWARE); diff --git a/drivers/media/usb/dvb-usb-v2/ec168.h b/drivers/media/usb/dvb-usb-v2/ec168.h new file mode 100644 index 000000000000..615a6569421f --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/ec168.h @@ -0,0 +1,53 @@ +/* + * E3C EC168 DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * + * 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. + * + */ + +#ifndef EC168_H +#define EC168_H + +#include "dvb_usb.h" + +#define EC168_USB_TIMEOUT 1000 +#define EC168_FIRMWARE "dvb-usb-ec168.fw" + +struct ec168_req { + u8 cmd; /* [1] */ + u16 value; /* [2|3] */ + u16 index; /* [4|5] */ + u16 size; /* [6|7] */ + u8 *data; +}; + +enum ec168_cmd { + DOWNLOAD_FIRMWARE = 0x00, + CONFIG = 0x01, + DEMOD_RW = 0x03, + GPIO = 0x04, + STREAMING_CTRL = 0x10, + READ_I2C = 0x20, + WRITE_I2C = 0x21, + HID_DOWNLOAD = 0x30, + GET_CONFIG, + SET_CONFIG, + READ_DEMOD, + WRITE_DEMOD, +}; + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/gl861.c b/drivers/media/usb/dvb-usb-v2/gl861.c new file mode 100644 index 000000000000..b1b09c547861 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/gl861.c @@ -0,0 +1,177 @@ +/* DVB USB compliant linux driver for GL861 USB2.0 devices. + * + * 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. + * + * see Documentation/dvb/README.dvb-usb for more information + */ +#include "gl861.h" + +#include "zl10353.h" +#include "qt1010.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int gl861_i2c_msg(struct dvb_usb_device *d, u8 addr, + u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) +{ + u16 index; + u16 value = addr << (8 + 1); + int wo = (rbuf == NULL || rlen == 0); /* write-only */ + u8 req, type; + + if (wo) { + req = GL861_REQ_I2C_WRITE; + type = GL861_WRITE; + } else { /* rw */ + req = GL861_REQ_I2C_READ; + type = GL861_READ; + } + + switch (wlen) { + case 1: + index = wbuf[0]; + break; + case 2: + index = wbuf[0]; + value = value + wbuf[1]; + break; + default: + dev_err(&d->udev->dev, "%s: wlen=%d, aborting\n", + KBUILD_MODNAME, wlen); + return -EINVAL; + } + + msleep(1); /* avoid I2C errors */ + + return usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), req, type, + value, index, rbuf, rlen, 2000); +} + +/* I2C */ +static int gl861_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int i; + + if (num > 2) + return -EINVAL; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + for (i = 0; i < num; i++) { + /* write/read request */ + if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) { + if (gl861_i2c_msg(d, msg[i].addr, msg[i].buf, + msg[i].len, msg[i+1].buf, msg[i+1].len) < 0) + break; + i++; + } else + if (gl861_i2c_msg(d, msg[i].addr, msg[i].buf, + msg[i].len, NULL, 0) < 0) + break; + } + + mutex_unlock(&d->i2c_mutex); + return i; +} + +static u32 gl861_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm gl861_i2c_algo = { + .master_xfer = gl861_i2c_xfer, + .functionality = gl861_i2c_func, +}; + +/* Callbacks for DVB USB */ +static struct zl10353_config gl861_zl10353_config = { + .demod_address = 0x0f, + .no_tuner = 1, + .parallel_ts = 1, +}; + +static int gl861_frontend_attach(struct dvb_usb_adapter *adap) +{ + + adap->fe[0] = dvb_attach(zl10353_attach, &gl861_zl10353_config, + &adap_to_d(adap)->i2c_adap); + if (adap->fe[0] == NULL) + return -EIO; + + return 0; +} + +static struct qt1010_config gl861_qt1010_config = { + .i2c_address = 0x62 +}; + +static int gl861_tuner_attach(struct dvb_usb_adapter *adap) +{ + return dvb_attach(qt1010_attach, + adap->fe[0], &adap_to_d(adap)->i2c_adap, + &gl861_qt1010_config) == NULL ? -ENODEV : 0; +} + +static int gl861_init(struct dvb_usb_device *d) +{ + /* + * There is 2 interfaces. Interface 0 is for TV and interface 1 is + * for HID remote controller. Interface 0 has 2 alternate settings. + * For some reason we need to set interface explicitly, defaulted + * as alternate setting 1? + */ + return usb_set_interface(d->udev, 0, 0); +} + +/* DVB USB Driver stuff */ +static struct dvb_usb_device_properties gl861_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + + .i2c_algo = &gl861_i2c_algo, + .frontend_attach = gl861_frontend_attach, + .tuner_attach = gl861_tuner_attach, + .init = gl861_init, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x81, 7, 512), + } + } +}; + +static const struct usb_device_id gl861_id_table[] = { + { DVB_USB_DEVICE(USB_VID_MSI, USB_PID_MSI_MEGASKY580_55801, + &gl861_props, "MSI Mega Sky 55801 DVB-T USB2.0", NULL) }, + { DVB_USB_DEVICE(USB_VID_ALINK, USB_VID_ALINK_DTU, + &gl861_props, "A-LINK DTU DVB-T USB2.0", NULL) }, + { } +}; +MODULE_DEVICE_TABLE(usb, gl861_id_table); + +static struct usb_driver gl861_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = gl861_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(gl861_usb_driver); + +MODULE_AUTHOR("Carl Lundqvist <comabug@gmail.com>"); +MODULE_DESCRIPTION("Driver MSI Mega Sky 580 DVB-T USB2.0 / GL861"); +MODULE_VERSION("0.1"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/dvb-usb-v2/gl861.h b/drivers/media/usb/dvb-usb-v2/gl861.h new file mode 100644 index 000000000000..b0b80d87bb7e --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/gl861.h @@ -0,0 +1,12 @@ +#ifndef _DVB_USB_GL861_H_ +#define _DVB_USB_GL861_H_ + +#include "dvb_usb.h" + +#define GL861_WRITE 0x40 +#define GL861_READ 0xc0 + +#define GL861_REQ_I2C_WRITE 0x01 +#define GL861_REQ_I2C_READ 0x02 + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/it913x.c b/drivers/media/usb/dvb-usb-v2/it913x.c new file mode 100644 index 000000000000..695f9106bc54 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/it913x.c @@ -0,0 +1,799 @@ +/* + * DVB USB compliant linux driver for ITE IT9135 and IT9137 + * + * Copyright (C) 2011 Malcolm Priestley (tvboxspy@gmail.com) + * IT9135 (C) ITE Tech Inc. + * IT9137 (C) ITE Tech Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2, as + * published by the Free Software Foundation. + * + * 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. + * + * + * see Documentation/dvb/README.dvb-usb for more information + * see Documentation/dvb/it9137.txt for firmware information + * + */ +#define DVB_USB_LOG_PREFIX "it913x" + +#include <linux/usb.h> +#include <linux/usb/input.h> +#include <media/rc-core.h> + +#include "dvb_usb.h" +#include "it913x-fe.h" + +/* debug */ +static int dvb_usb_it913x_debug; +#define it_debug(var, level, args...) \ + do { if ((var & level)) pr_debug(DVB_USB_LOG_PREFIX": " args); \ +} while (0) +#define deb_info(level, args...) it_debug(dvb_usb_it913x_debug, level, args) +#define info(args...) pr_info(DVB_USB_LOG_PREFIX": " args) + +module_param_named(debug, dvb_usb_it913x_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); + +static int dvb_usb_it913x_firmware; +module_param_named(firmware, dvb_usb_it913x_firmware, int, 0644); +MODULE_PARM_DESC(firmware, "set firmware 0=auto"\ + "1=IT9137 2=IT9135 V1 3=IT9135 V2"); +#define FW_IT9137 "dvb-usb-it9137-01.fw" +#define FW_IT9135_V1 "dvb-usb-it9135-01.fw" +#define FW_IT9135_V2 "dvb-usb-it9135-02.fw" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct it913x_state { + struct ite_config it913x_config; + u8 pid_filter_onoff; + bool proprietary_ir; + int cmd_counter; +}; + +static u16 check_sum(u8 *p, u8 len) +{ + u16 sum = 0; + u8 i = 1; + while (i < len) + sum += (i++ & 1) ? (*p++) << 8 : *p++; + return ~sum; +} + +static int it913x_io(struct dvb_usb_device *d, u8 mode, u8 pro, + u8 cmd, u32 reg, u8 addr, u8 *data, u8 len) +{ + struct it913x_state *st = d->priv; + int ret = 0, i, buf_size = 1; + u8 *buff; + u8 rlen; + u16 chk_sum; + + buff = kzalloc(256, GFP_KERNEL); + if (!buff) { + info("USB Buffer Failed"); + return -ENOMEM; + } + + buff[buf_size++] = pro; + buff[buf_size++] = cmd; + buff[buf_size++] = st->cmd_counter; + + switch (mode) { + case READ_LONG: + case WRITE_LONG: + buff[buf_size++] = len; + buff[buf_size++] = 2; + buff[buf_size++] = (reg >> 24); + buff[buf_size++] = (reg >> 16) & 0xff; + buff[buf_size++] = (reg >> 8) & 0xff; + buff[buf_size++] = reg & 0xff; + break; + case READ_SHORT: + buff[buf_size++] = addr; + break; + case WRITE_SHORT: + buff[buf_size++] = len; + buff[buf_size++] = addr; + buff[buf_size++] = (reg >> 8) & 0xff; + buff[buf_size++] = reg & 0xff; + break; + case READ_DATA: + case WRITE_DATA: + break; + case WRITE_CMD: + mode = 7; + break; + default: + kfree(buff); + return -EINVAL; + } + + if (mode & 1) { + for (i = 0; i < len ; i++) + buff[buf_size++] = data[i]; + } + chk_sum = check_sum(&buff[1], buf_size); + + buff[buf_size++] = chk_sum >> 8; + buff[0] = buf_size; + buff[buf_size++] = (chk_sum & 0xff); + + ret = dvb_usbv2_generic_rw(d, buff, buf_size, buff, (mode & 1) ? + 5 : len + 5); + if (ret < 0) + goto error; + + rlen = (mode & 0x1) ? 0x1 : len; + + if (mode & 1) + ret = buff[2]; + else + memcpy(data, &buff[3], rlen); + + st->cmd_counter++; + +error: kfree(buff); + + return ret; +} + +static int it913x_wr_reg(struct dvb_usb_device *d, u8 pro, u32 reg , u8 data) +{ + int ret; + u8 b[1]; + b[0] = data; + ret = it913x_io(d, WRITE_LONG, pro, + CMD_DEMOD_WRITE, reg, 0, b, sizeof(b)); + + return ret; +} + +static int it913x_read_reg(struct dvb_usb_device *d, u32 reg) +{ + int ret; + u8 data[1]; + + ret = it913x_io(d, READ_LONG, DEV_0, + CMD_DEMOD_READ, reg, 0, &data[0], sizeof(data)); + + return (ret < 0) ? ret : data[0]; +} + +static int it913x_query(struct dvb_usb_device *d, u8 pro) +{ + struct it913x_state *st = d->priv; + int ret, i; + u8 data[4]; + u8 ver; + + for (i = 0; i < 5; i++) { + ret = it913x_io(d, READ_LONG, pro, CMD_DEMOD_READ, + 0x1222, 0, &data[0], 3); + ver = data[0]; + if (ver > 0 && ver < 3) + break; + msleep(100); + } + + if (ver < 1 || ver > 2) { + info("Failed to identify chip version applying 1"); + st->it913x_config.chip_ver = 0x1; + st->it913x_config.chip_type = 0x9135; + return 0; + } + + st->it913x_config.chip_ver = ver; + st->it913x_config.chip_type = (u16)(data[2] << 8) + data[1]; + + info("Chip Version=%02x Chip Type=%04x", st->it913x_config.chip_ver, + st->it913x_config.chip_type); + + ret = it913x_io(d, READ_SHORT, pro, + CMD_QUERYINFO, 0, 0x1, &data[0], 4); + + st->it913x_config.firmware = (data[0] << 24) | (data[1] << 16) | + (data[2] << 8) | data[3]; + + return ret; +} + +static int it913x_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct it913x_state *st = adap_to_priv(adap); + int ret; + u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; + + mutex_lock(&d->i2c_mutex); + + deb_info(1, "PID_C (%02x)", onoff); + + ret = it913x_wr_reg(d, pro, PID_EN, st->pid_filter_onoff); + + mutex_unlock(&d->i2c_mutex); + return ret; +} + +static int it913x_pid_filter(struct dvb_usb_adapter *adap, + int index, u16 pid, int onoff) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct it913x_state *st = adap_to_priv(adap); + int ret; + u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; + + mutex_lock(&d->i2c_mutex); + + deb_info(1, "PID_F (%02x)", onoff); + + ret = it913x_wr_reg(d, pro, PID_LSB, (u8)(pid & 0xff)); + + ret |= it913x_wr_reg(d, pro, PID_MSB, (u8)(pid >> 8)); + + ret |= it913x_wr_reg(d, pro, PID_INX_EN, (u8)onoff); + + ret |= it913x_wr_reg(d, pro, PID_INX, (u8)(index & 0x1f)); + + if (d->udev->speed == USB_SPEED_HIGH && pid == 0x2000) { + ret |= it913x_wr_reg(d , pro, PID_EN, !onoff); + st->pid_filter_onoff = !onoff; + } else + st->pid_filter_onoff = + adap->pid_filtering; + + mutex_unlock(&d->i2c_mutex); + return 0; +} + + +static int it913x_return_status(struct dvb_usb_device *d) +{ + struct it913x_state *st = d->priv; + int ret = it913x_query(d, DEV_0); + if (st->it913x_config.firmware > 0) + info("Firmware Version %d", st->it913x_config.firmware); + + return ret; +} + +static int it913x_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + static u8 data[256]; + int ret; + u32 reg; + u8 pro; + + mutex_lock(&d->i2c_mutex); + + deb_info(2, "num of messages %d address %02x", num, msg[0].addr); + + pro = (msg[0].addr & 0x2) ? DEV_0_DMOD : 0x0; + pro |= (msg[0].addr & 0x20) ? DEV_1 : DEV_0; + memcpy(data, msg[0].buf, msg[0].len); + reg = (data[0] << 24) + (data[1] << 16) + + (data[2] << 8) + data[3]; + if (num == 2) { + ret = it913x_io(d, READ_LONG, pro, + CMD_DEMOD_READ, reg, 0, data, msg[1].len); + memcpy(msg[1].buf, data, msg[1].len); + } else + ret = it913x_io(d, WRITE_LONG, pro, CMD_DEMOD_WRITE, + reg, 0, &data[4], msg[0].len - 4); + + mutex_unlock(&d->i2c_mutex); + + return ret; +} + +static u32 it913x_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm it913x_i2c_algo = { + .master_xfer = it913x_i2c_xfer, + .functionality = it913x_i2c_func, +}; + +/* Callbacks for DVB USB */ +#define IT913X_POLL 250 +static int it913x_rc_query(struct dvb_usb_device *d) +{ + u8 ibuf[4]; + int ret; + u32 key; + /* Avoid conflict with frontends*/ + mutex_lock(&d->i2c_mutex); + + ret = it913x_io(d, READ_LONG, PRO_LINK, CMD_IR_GET, + 0, 0, &ibuf[0], sizeof(ibuf)); + + if ((ibuf[2] + ibuf[3]) == 0xff) { + key = ibuf[2]; + key += ibuf[0] << 16; + key += ibuf[1] << 8; + deb_info(1, "NEC Extended Key =%08x", key); + if (d->rc_dev != NULL) + rc_keydown(d->rc_dev, key, 0); + } + + mutex_unlock(&d->i2c_mutex); + + return ret; +} + +/* Firmware sets raw */ +static const char fw_it9135_v1[] = FW_IT9135_V1; +static const char fw_it9135_v2[] = FW_IT9135_V2; +static const char fw_it9137[] = FW_IT9137; + +static void ite_get_firmware_name(struct dvb_usb_device *d, + const char **name) +{ + struct it913x_state *st = d->priv; + int sw; + /* auto switch */ + if (le16_to_cpu(d->udev->descriptor.idVendor) == USB_VID_KWORLD_2) + sw = IT9137_FW; + else if (st->it913x_config.chip_ver == 1) + sw = IT9135_V1_FW; + else + sw = IT9135_V2_FW; + + /* force switch */ + if (dvb_usb_it913x_firmware != IT9135_AUTO) + sw = dvb_usb_it913x_firmware; + + switch (sw) { + case IT9135_V1_FW: + st->it913x_config.firmware_ver = 1; + st->it913x_config.adc_x2 = 1; + st->it913x_config.read_slevel = false; + *name = fw_it9135_v1; + break; + case IT9135_V2_FW: + st->it913x_config.firmware_ver = 1; + st->it913x_config.adc_x2 = 1; + st->it913x_config.read_slevel = false; + *name = fw_it9135_v2; + switch (st->it913x_config.tuner_id_0) { + case IT9135_61: + case IT9135_62: + break; + default: + info("Unknown tuner ID applying default 0x60"); + case IT9135_60: + st->it913x_config.tuner_id_0 = IT9135_60; + } + break; + case IT9137_FW: + default: + st->it913x_config.firmware_ver = 0; + st->it913x_config.adc_x2 = 0; + st->it913x_config.read_slevel = true; + *name = fw_it9137; + } + + return; +} + +#define TS_MPEG_PKT_SIZE 188 +#define EP_LOW 21 +#define TS_BUFFER_SIZE_PID (EP_LOW*TS_MPEG_PKT_SIZE) +#define EP_HIGH 348 +#define TS_BUFFER_SIZE_MAX (EP_HIGH*TS_MPEG_PKT_SIZE) + +static int it913x_get_stream_config(struct dvb_frontend *fe, u8 *ts_type, + struct usb_data_stream_properties *stream) +{ + struct dvb_usb_adapter *adap = fe_to_adap(fe); + if (adap->pid_filtering) + stream->u.bulk.buffersize = TS_BUFFER_SIZE_PID; + else + stream->u.bulk.buffersize = TS_BUFFER_SIZE_MAX; + + return 0; +} + +static int it913x_select_config(struct dvb_usb_device *d) +{ + struct it913x_state *st = d->priv; + int ret, reg; + + ret = it913x_return_status(d); + if (ret < 0) + return ret; + + if (st->it913x_config.chip_ver == 0x02 + && st->it913x_config.chip_type == 0x9135) + reg = it913x_read_reg(d, 0x461d); + else + reg = it913x_read_reg(d, 0x461b); + + if (reg < 0) + return reg; + + if (reg == 0) { + st->it913x_config.dual_mode = 0; + st->it913x_config.tuner_id_0 = IT9135_38; + st->proprietary_ir = true; + } else { + /* TS mode */ + reg = it913x_read_reg(d, 0x49c5); + if (reg < 0) + return reg; + st->it913x_config.dual_mode = reg; + + /* IR mode type */ + reg = it913x_read_reg(d, 0x49ac); + if (reg < 0) + return reg; + if (reg == 5) { + info("Remote propriety (raw) mode"); + st->proprietary_ir = true; + } else if (reg == 1) { + info("Remote HID mode NOT SUPPORTED"); + st->proprietary_ir = false; + } + + /* Tuner_id */ + reg = it913x_read_reg(d, 0x49d0); + if (reg < 0) + return reg; + st->it913x_config.tuner_id_0 = reg; + } + + info("Dual mode=%x Tuner Type=%x", st->it913x_config.dual_mode, + st->it913x_config.tuner_id_0); + + return ret; +} + +static int it913x_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct dvb_usb_adapter *adap = fe_to_adap(fe); + struct dvb_usb_device *d = adap_to_d(adap); + struct it913x_state *st = fe_to_priv(fe); + int ret = 0; + u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; + + deb_info(1, "STM (%02x)", onoff); + + if (!onoff) { + mutex_lock(&d->i2c_mutex); + + ret = it913x_wr_reg(d, pro, PID_RST, 0x1); + + mutex_unlock(&d->i2c_mutex); + st->pid_filter_onoff = + adap->pid_filtering; + + } + + return ret; +} + +static int it913x_identify_state(struct dvb_usb_device *d, const char **name) +{ + struct it913x_state *st = d->priv; + int ret; + u8 reg; + + /* Read and select config */ + ret = it913x_select_config(d); + if (ret < 0) + return ret; + + ite_get_firmware_name(d, name); + + if (st->it913x_config.firmware > 0) + return WARM; + + if (st->it913x_config.dual_mode) { + st->it913x_config.tuner_id_1 = it913x_read_reg(d, 0x49e0); + ret = it913x_wr_reg(d, DEV_0, GPIOH1_EN, 0x1); + ret |= it913x_wr_reg(d, DEV_0, GPIOH1_ON, 0x1); + ret |= it913x_wr_reg(d, DEV_0, GPIOH1_O, 0x1); + msleep(50); + ret |= it913x_wr_reg(d, DEV_0, GPIOH1_O, 0x0); + msleep(50); + reg = it913x_read_reg(d, GPIOH1_O); + if (reg == 0) { + ret |= it913x_wr_reg(d, DEV_0, GPIOH1_O, 0x1); + ret |= it913x_return_status(d); + if (ret != 0) + ret = it913x_wr_reg(d, DEV_0, + GPIOH1_O, 0x0); + } + } + + reg = it913x_read_reg(d, IO_MUX_POWER_CLK); + + if (st->it913x_config.dual_mode) { + ret |= it913x_wr_reg(d, DEV_0, 0x4bfb, CHIP2_I2C_ADDR); + if (st->it913x_config.firmware_ver == 1) + ret |= it913x_wr_reg(d, DEV_0, 0xcfff, 0x1); + else + ret |= it913x_wr_reg(d, DEV_0, CLK_O_EN, 0x1); + } else { + ret |= it913x_wr_reg(d, DEV_0, 0x4bfb, 0x0); + if (st->it913x_config.firmware_ver == 1) + ret |= it913x_wr_reg(d, DEV_0, 0xcfff, 0x0); + else + ret |= it913x_wr_reg(d, DEV_0, CLK_O_EN, 0x0); + } + + ret |= it913x_wr_reg(d, DEV_0, I2C_CLK, I2C_CLK_100); + + return (ret < 0) ? ret : COLD; +} + +static int it913x_download_firmware(struct dvb_usb_device *d, + const struct firmware *fw) +{ + struct it913x_state *st = d->priv; + int ret = 0, i = 0, pos = 0; + u8 packet_size, min_pkt; + u8 *fw_data; + + ret = it913x_wr_reg(d, DEV_0, I2C_CLK, I2C_CLK_100); + + info("FRM Starting Firmware Download"); + + /* Multi firmware loader */ + /* This uses scatter write firmware headers */ + /* The firmware must start with 03 XX 00 */ + /* and be the extact firmware length */ + + if (st->it913x_config.chip_ver == 2) + min_pkt = 0x11; + else + min_pkt = 0x19; + + while (i <= fw->size) { + if (((fw->data[i] == 0x3) && (fw->data[i + 2] == 0x0)) + || (i == fw->size)) { + packet_size = i - pos; + if ((packet_size > min_pkt) || (i == fw->size)) { + fw_data = (u8 *)(fw->data + pos); + pos += packet_size; + if (packet_size > 0) { + ret = it913x_io(d, WRITE_DATA, + DEV_0, CMD_SCATTER_WRITE, 0, + 0, fw_data, packet_size); + if (ret < 0) + break; + } + udelay(1000); + } + } + i++; + } + + if (ret < 0) + info("FRM Firmware Download Failed (%d)" , ret); + else + info("FRM Firmware Download Completed - Resetting Device"); + + msleep(30); + + ret = it913x_io(d, WRITE_CMD, DEV_0, CMD_BOOT, 0, 0, NULL, 0); + if (ret < 0) + info("FRM Device not responding to reboot"); + + ret = it913x_return_status(d); + if (st->it913x_config.firmware == 0) { + info("FRM Failed to reboot device"); + return -ENODEV; + } + + msleep(30); + + ret = it913x_wr_reg(d, DEV_0, I2C_CLK, I2C_CLK_400); + + msleep(30); + + /* Tuner function */ + if (st->it913x_config.dual_mode) + ret |= it913x_wr_reg(d, DEV_0_DMOD , 0xec4c, 0xa0); + else + ret |= it913x_wr_reg(d, DEV_0_DMOD , 0xec4c, 0x68); + + if ((st->it913x_config.chip_ver == 1) && + (st->it913x_config.chip_type == 0x9135)) { + ret |= it913x_wr_reg(d, DEV_0, PADODPU, 0x0); + ret |= it913x_wr_reg(d, DEV_0, AGC_O_D, 0x0); + if (st->it913x_config.dual_mode) { + ret |= it913x_wr_reg(d, DEV_1, PADODPU, 0x0); + ret |= it913x_wr_reg(d, DEV_1, AGC_O_D, 0x0); + } + } + + return (ret < 0) ? -ENODEV : 0; +} + +static int it913x_name(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + const char *desc = d->name; + char *fe_name[] = {"_1", "_2", "_3", "_4"}; + char *name = adap->fe[0]->ops.info.name; + + strlcpy(name, desc, 128); + strlcat(name, fe_name[adap->id], 128); + + return 0; +} + +static int it913x_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct it913x_state *st = d->priv; + int ret = 0; + u8 adap_addr = I2C_BASE_ADDR + (adap->id << 5); + u16 ep_size = adap->stream.buf_size / 4; + u8 pkt_size = 0x80; + + if (d->udev->speed != USB_SPEED_HIGH) + pkt_size = 0x10; + + st->it913x_config.adf = it913x_read_reg(d, IO_MUX_POWER_CLK); + + adap->fe[0] = dvb_attach(it913x_fe_attach, + &d->i2c_adap, adap_addr, &st->it913x_config); + + if (adap->id == 0 && adap->fe[0]) { + it913x_wr_reg(d, DEV_0_DMOD, MP2_SW_RST, 0x1); + it913x_wr_reg(d, DEV_0_DMOD, MP2IF2_SW_RST, 0x1); + it913x_wr_reg(d, DEV_0, EP0_TX_EN, 0x0f); + it913x_wr_reg(d, DEV_0, EP0_TX_NAK, 0x1b); + it913x_wr_reg(d, DEV_0, EP0_TX_EN, 0x2f); + it913x_wr_reg(d, DEV_0, EP4_TX_LEN_LSB, + ep_size & 0xff); + it913x_wr_reg(d, DEV_0, EP4_TX_LEN_MSB, ep_size >> 8); + ret = it913x_wr_reg(d, DEV_0, EP4_MAX_PKT, pkt_size); + } else if (adap->id == 1 && adap->fe[0]) { + it913x_wr_reg(d, DEV_0, EP0_TX_EN, 0x6f); + it913x_wr_reg(d, DEV_0, EP5_TX_LEN_LSB, + ep_size & 0xff); + it913x_wr_reg(d, DEV_0, EP5_TX_LEN_MSB, ep_size >> 8); + it913x_wr_reg(d, DEV_0, EP5_MAX_PKT, pkt_size); + it913x_wr_reg(d, DEV_0_DMOD, MP2IF2_EN, 0x1); + it913x_wr_reg(d, DEV_1_DMOD, MP2IF_SERIAL, 0x1); + it913x_wr_reg(d, DEV_1, TOP_HOSTB_SER_MODE, 0x1); + it913x_wr_reg(d, DEV_0_DMOD, TSIS_ENABLE, 0x1); + it913x_wr_reg(d, DEV_0_DMOD, MP2_SW_RST, 0x0); + it913x_wr_reg(d, DEV_0_DMOD, MP2IF2_SW_RST, 0x0); + it913x_wr_reg(d, DEV_0_DMOD, MP2IF2_HALF_PSB, 0x0); + it913x_wr_reg(d, DEV_0_DMOD, MP2IF_STOP_EN, 0x1); + it913x_wr_reg(d, DEV_1_DMOD, MPEG_FULL_SPEED, 0x0); + ret = it913x_wr_reg(d, DEV_1_DMOD, MP2IF_STOP_EN, 0x0); + } else + return -ENODEV; + + ret |= it913x_name(adap); + + return ret; +} + +/* DVB USB Driver */ +static int it913x_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) +{ + struct it913x_state *st = d->priv; + + if (st->proprietary_ir == false) { + rc->map_name = NULL; + return 0; + } + + rc->allowed_protos = RC_TYPE_NEC; + rc->query = it913x_rc_query; + rc->interval = 250; + + return 0; +} + +static int it913x_get_adapter_count(struct dvb_usb_device *d) +{ + struct it913x_state *st = d->priv; + if (st->it913x_config.dual_mode) + return 2; + return 1; +} + +static struct dvb_usb_device_properties it913x_properties = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .bInterfaceNumber = 0, + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct it913x_state), + + .identify_state = it913x_identify_state, + .i2c_algo = &it913x_i2c_algo, + + .download_firmware = it913x_download_firmware, + + .frontend_attach = it913x_frontend_attach, + .get_rc_config = it913x_get_rc_config, + .get_stream_config = it913x_get_stream_config, + .get_adapter_count = it913x_get_adapter_count, + .streaming_ctrl = it913x_streaming_ctrl, + + + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER| + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = it913x_pid_filter, + .pid_filter_ctrl = it913x_pid_filter_ctrl, + .stream = + DVB_USB_STREAM_BULK(0x84, 10, TS_BUFFER_SIZE_MAX), + }, + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER| + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = it913x_pid_filter, + .pid_filter_ctrl = it913x_pid_filter_ctrl, + .stream = + DVB_USB_STREAM_BULK(0x85, 10, TS_BUFFER_SIZE_MAX), + } + } +}; + +static const struct usb_device_id it913x_id_table[] = { + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB499_2T_T09, + &it913x_properties, "Kworld UB499-2T T09(IT9137)", + RC_MAP_IT913X_V1) }, + { DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135, + &it913x_properties, "ITE 9135 Generic", + RC_MAP_IT913X_V1) }, + { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22_IT9137, + &it913x_properties, "Sveon STV22 Dual DVB-T HDTV(IT9137)", + RC_MAP_IT913X_V1) }, + { DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9005, + &it913x_properties, "ITE 9135(9005) Generic", + RC_MAP_IT913X_V2) }, + { DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9006, + &it913x_properties, "ITE 9135(9006) Generic", + RC_MAP_IT913X_V1) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, it913x_id_table); + +static struct usb_driver it913x_driver = { + .name = KBUILD_MODNAME, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .id_table = it913x_id_table, +}; + +module_usb_driver(it913x_driver); + +MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>"); +MODULE_DESCRIPTION("it913x USB 2 Driver"); +MODULE_VERSION("1.32"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FW_IT9135_V1); +MODULE_FIRMWARE(FW_IT9135_V2); +MODULE_FIRMWARE(FW_IT9137); + diff --git a/drivers/media/usb/dvb-usb-v2/lmedm04.c b/drivers/media/usb/dvb-usb-v2/lmedm04.c new file mode 100644 index 000000000000..c41d9d9ec7b5 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/lmedm04.c @@ -0,0 +1,1369 @@ +/* DVB USB compliant linux driver for + * + * DM04/QQBOX DVB-S USB BOX LME2510C + SHARP:BS2F7HZ7395 + * LME2510C + LG TDQY-P001F + * LME2510C + BS2F7HZ0194 + * LME2510 + LG TDQY-P001F + * LME2510 + BS2F7HZ0194 + * + * MVB7395 (LME2510C+SHARP:BS2F7HZ7395) + * SHARP:BS2F7HZ7395 = (STV0288+Sharp IX2505V) + * + * MV001F (LME2510+LGTDQY-P001F) + * LG TDQY - P001F =(TDA8263 + TDA10086H) + * + * MVB0001F (LME2510C+LGTDQT-P001F) + * + * MV0194 (LME2510+SHARP:BS2F7HZ0194) + * SHARP:BS2F7HZ0194 = (STV0299+IX2410) + * + * MVB0194 (LME2510C+SHARP0194) + * + * LME2510C + M88RS2000 + * + * For firmware see Documentation/dvb/lmedm04.txt + * + * I2C addresses: + * 0xd0 - STV0288 - Demodulator + * 0xc0 - Sharp IX2505V - Tuner + * -- + * 0x1c - TDA10086 - Demodulator + * 0xc0 - TDA8263 - Tuner + * -- + * 0xd0 - STV0299 - Demodulator + * 0xc0 - IX2410 - Tuner + * + * + * VID = 3344 PID LME2510=1122 LME2510C=1120 + * + * Copyright (C) 2010 Malcolm Priestley (tvboxspy@gmail.com) + * LME2510(C)(C) Leaguerme (Shenzhen) MicroElectronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2, as + * published by the Free Software Foundation. + * + * 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. + * + * + * see Documentation/dvb/README.dvb-usb for more information + * + * Known Issues : + * LME2510: Non Intel USB chipsets fail to maintain High Speed on + * Boot or Hot Plug. + * + * QQbox suffers from noise on LNB voltage. + * + * LME2510: SHARP:BS2F7HZ0194(MV0194) cannot cold reset and share system + * with other tuners. After a cold reset streaming will not start. + * + * M88RS2000 suffers from loss of lock. + */ +#define DVB_USB_LOG_PREFIX "LME2510(C)" +#include <linux/usb.h> +#include <linux/usb/input.h> +#include <media/rc-core.h> + +#include "dvb_usb.h" +#include "lmedm04.h" +#include "tda826x.h" +#include "tda10086.h" +#include "stv0288.h" +#include "ix2505v.h" +#include "stv0299.h" +#include "dvb-pll.h" +#include "z0194a.h" +#include "m88rs2000.h" + + +#define LME2510_C_S7395 "dvb-usb-lme2510c-s7395.fw"; +#define LME2510_C_LG "dvb-usb-lme2510c-lg.fw"; +#define LME2510_C_S0194 "dvb-usb-lme2510c-s0194.fw"; +#define LME2510_C_RS2000 "dvb-usb-lme2510c-rs2000.fw"; +#define LME2510_LG "dvb-usb-lme2510-lg.fw"; +#define LME2510_S0194 "dvb-usb-lme2510-s0194.fw"; + +/* debug */ +static int dvb_usb_lme2510_debug; +#define lme_debug(var, level, args...) do { \ + if ((var >= level)) \ + pr_debug(DVB_USB_LOG_PREFIX": " args); \ +} while (0) +#define deb_info(level, args...) lme_debug(dvb_usb_lme2510_debug, level, args) +#define debug_data_snipet(level, name, p) \ + deb_info(level, name" (%02x%02x%02x%02x%02x%02x%02x%02x)", \ + *p, *(p+1), *(p+2), *(p+3), *(p+4), \ + *(p+5), *(p+6), *(p+7)); +#define info(args...) pr_info(DVB_USB_LOG_PREFIX": "args) + +module_param_named(debug, dvb_usb_lme2510_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); + +static int dvb_usb_lme2510_firmware; +module_param_named(firmware, dvb_usb_lme2510_firmware, int, 0644); +MODULE_PARM_DESC(firmware, "set default firmware 0=Sharp7395 1=LG"); + +static int pid_filter; +module_param_named(pid, pid_filter, int, 0644); +MODULE_PARM_DESC(pid, "set default 0=default 1=off 2=on"); + + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define TUNER_DEFAULT 0x0 +#define TUNER_LG 0x1 +#define TUNER_S7395 0x2 +#define TUNER_S0194 0x3 +#define TUNER_RS2000 0x4 + +struct lme2510_state { + u8 id; + u8 tuner_config; + u8 signal_lock; + u8 signal_level; + u8 signal_sn; + u8 time_key; + u8 last_key; + u8 key_timeout; + u8 i2c_talk_onoff; + u8 i2c_gate; + u8 i2c_tuner_gate_w; + u8 i2c_tuner_gate_r; + u8 i2c_tuner_addr; + u8 stream_on; + u8 pid_size; + u8 pid_off; + void *buffer; + struct urb *lme_urb; + void *usb_buffer; + int (*fe_set_voltage)(struct dvb_frontend *, fe_sec_voltage_t); + u8 dvb_usb_lme2510_firmware; +}; + +static int lme2510_bulk_write(struct usb_device *dev, + u8 *snd, int len, u8 pipe) +{ + int ret, actual_l; + + ret = usb_bulk_msg(dev, usb_sndbulkpipe(dev, pipe), + snd, len , &actual_l, 100); + return ret; +} + +static int lme2510_bulk_read(struct usb_device *dev, + u8 *rev, int len, u8 pipe) +{ + int ret, actual_l; + + ret = usb_bulk_msg(dev, usb_rcvbulkpipe(dev, pipe), + rev, len , &actual_l, 200); + return ret; +} + +static int lme2510_usb_talk(struct dvb_usb_device *d, + u8 *wbuf, int wlen, u8 *rbuf, int rlen) +{ + struct lme2510_state *st = d->priv; + u8 *buff; + int ret = 0; + + if (st->usb_buffer == NULL) { + st->usb_buffer = kmalloc(64, GFP_KERNEL); + if (st->usb_buffer == NULL) { + info("MEM Error no memory"); + return -ENOMEM; + } + } + buff = st->usb_buffer; + + ret = mutex_lock_interruptible(&d->usb_mutex); + + if (ret < 0) + return -EAGAIN; + + /* the read/write capped at 64 */ + memcpy(buff, wbuf, (wlen < 64) ? wlen : 64); + + ret |= lme2510_bulk_write(d->udev, buff, wlen , 0x01); + + ret |= lme2510_bulk_read(d->udev, buff, (rlen < 64) ? + rlen : 64 , 0x01); + + if (rlen > 0) + memcpy(rbuf, buff, rlen); + + mutex_unlock(&d->usb_mutex); + + return (ret < 0) ? -ENODEV : 0; +} + +static int lme2510_stream_restart(struct dvb_usb_device *d) +{ + struct lme2510_state *st = d->priv; + u8 all_pids[] = LME_ALL_PIDS; + u8 stream_on[] = LME_ST_ON_W; + int ret; + u8 rbuff[1]; + if (st->pid_off) + ret = lme2510_usb_talk(d, all_pids, sizeof(all_pids), + rbuff, sizeof(rbuff)); + /*Restart Stream Command*/ + ret = lme2510_usb_talk(d, stream_on, sizeof(stream_on), + rbuff, sizeof(rbuff)); + return ret; +} + +static int lme2510_enable_pid(struct dvb_usb_device *d, u8 index, u16 pid_out) +{ + struct lme2510_state *st = d->priv; + static u8 pid_buff[] = LME_ZERO_PID; + static u8 rbuf[1]; + u8 pid_no = index * 2; + u8 pid_len = pid_no + 2; + int ret = 0; + deb_info(1, "PID Setting Pid %04x", pid_out); + + if (st->pid_size == 0) + ret |= lme2510_stream_restart(d); + + pid_buff[2] = pid_no; + pid_buff[3] = (u8)pid_out & 0xff; + pid_buff[4] = pid_no + 1; + pid_buff[5] = (u8)(pid_out >> 8); + + if (pid_len > st->pid_size) + st->pid_size = pid_len; + pid_buff[7] = 0x80 + st->pid_size; + + ret |= lme2510_usb_talk(d, pid_buff , + sizeof(pid_buff) , rbuf, sizeof(rbuf)); + + if (st->stream_on) + ret |= lme2510_stream_restart(d); + + return ret; +} + +static void lme2510_int_response(struct urb *lme_urb) +{ + struct dvb_usb_adapter *adap = lme_urb->context; + struct lme2510_state *st = adap_to_priv(adap); + static u8 *ibuf, *rbuf; + int i = 0, offset; + u32 key; + + switch (lme_urb->status) { + case 0: + case -ETIMEDOUT: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + info("Error %x", lme_urb->status); + break; + } + + rbuf = (u8 *) lme_urb->transfer_buffer; + + offset = ((lme_urb->actual_length/8) > 4) + ? 4 : (lme_urb->actual_length/8) ; + + for (i = 0; i < offset; ++i) { + ibuf = (u8 *)&rbuf[i*8]; + deb_info(5, "INT O/S C =%02x C/O=%02x Type =%02x%02x", + offset, i, ibuf[0], ibuf[1]); + + switch (ibuf[0]) { + case 0xaa: + debug_data_snipet(1, "INT Remote data snipet", ibuf); + if ((ibuf[4] + ibuf[5]) == 0xff) { + key = ibuf[5]; + key += (ibuf[3] > 0) + ? (ibuf[3] ^ 0xff) << 8 : 0; + key += (ibuf[2] ^ 0xff) << 16; + deb_info(1, "INT Key =%08x", key); + if (adap_to_d(adap)->rc_dev != NULL) + rc_keydown(adap_to_d(adap)->rc_dev, + key, 0); + } + break; + case 0xbb: + switch (st->tuner_config) { + case TUNER_LG: + if (ibuf[2] > 0) + st->signal_lock = ibuf[2]; + st->signal_level = ibuf[4]; + st->signal_sn = ibuf[3]; + st->time_key = ibuf[7]; + break; + case TUNER_S7395: + case TUNER_S0194: + /* Tweak for earlier firmware*/ + if (ibuf[1] == 0x03) { + if (ibuf[2] > 1) + st->signal_lock = ibuf[2]; + st->signal_level = ibuf[3]; + st->signal_sn = ibuf[4]; + } else { + st->signal_level = ibuf[4]; + st->signal_sn = ibuf[5]; + st->signal_lock = + (st->signal_lock & 0xf7) + + ((ibuf[2] & 0x01) << 0x03); + } + break; + case TUNER_RS2000: + if (ibuf[1] == 0x3 && ibuf[6] == 0xff) + st->signal_lock = 0xff; + else + st->signal_lock = 0x00; + st->signal_level = ibuf[5]; + st->signal_sn = ibuf[4]; + st->time_key = ibuf[7]; + default: + break; + } + debug_data_snipet(5, "INT Remote data snipet in", ibuf); + break; + case 0xcc: + debug_data_snipet(1, "INT Control data snipet", ibuf); + break; + default: + debug_data_snipet(1, "INT Unknown data snipet", ibuf); + break; + } + } + usb_submit_urb(lme_urb, GFP_ATOMIC); +} + +static int lme2510_int_read(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct lme2510_state *lme_int = adap_to_priv(adap); + + lme_int->lme_urb = usb_alloc_urb(0, GFP_ATOMIC); + + if (lme_int->lme_urb == NULL) + return -ENOMEM; + + lme_int->buffer = usb_alloc_coherent(d->udev, 128, GFP_ATOMIC, + &lme_int->lme_urb->transfer_dma); + + if (lme_int->buffer == NULL) + return -ENOMEM; + + usb_fill_int_urb(lme_int->lme_urb, + d->udev, + usb_rcvintpipe(d->udev, 0xa), + lme_int->buffer, + 128, + lme2510_int_response, + adap, + 8); + + lme_int->lme_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + usb_submit_urb(lme_int->lme_urb, GFP_ATOMIC); + info("INT Interrupt Service Started"); + + return 0; +} + +static int lme2510_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct lme2510_state *st = adap_to_priv(adap); + static u8 clear_pid_reg[] = LME_ALL_PIDS; + static u8 rbuf[1]; + int ret = 0; + + deb_info(1, "PID Clearing Filter"); + + mutex_lock(&d->i2c_mutex); + + if (!onoff) { + ret |= lme2510_usb_talk(d, clear_pid_reg, + sizeof(clear_pid_reg), rbuf, sizeof(rbuf)); + st->pid_off = true; + } else + st->pid_off = false; + + st->pid_size = 0; + + mutex_unlock(&d->i2c_mutex); + + return 0; +} + +static int lme2510_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, + int onoff) +{ + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + + deb_info(3, "%s PID=%04x Index=%04x onoff=%02x", __func__, + pid, index, onoff); + + if (onoff) { + mutex_lock(&d->i2c_mutex); + ret |= lme2510_enable_pid(d, index, pid); + mutex_unlock(&d->i2c_mutex); + } + + + return ret; +} + + +static int lme2510_return_status(struct dvb_usb_device *d) +{ + int ret = 0; + u8 *data; + + data = kzalloc(10, GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret |= usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), + 0x06, 0x80, 0x0302, 0x00, data, 0x0006, 200); + info("Firmware Status: %x (%x)", ret , data[2]); + + ret = (ret < 0) ? -ENODEV : data[2]; + kfree(data); + return ret; +} + +static int lme2510_msg(struct dvb_usb_device *d, + u8 *wbuf, int wlen, u8 *rbuf, int rlen) +{ + int ret = 0; + struct lme2510_state *st = d->priv; + + if (st->i2c_talk_onoff == 1) { + + ret = lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); + + switch (st->tuner_config) { + case TUNER_LG: + if (wbuf[2] == 0x1c) { + if (wbuf[3] == 0x0e) { + st->signal_lock = rbuf[1]; + if ((st->stream_on & 1) && + (st->signal_lock & 0x10)) { + lme2510_stream_restart(d); + st->i2c_talk_onoff = 0; + } + msleep(80); + } + } + break; + case TUNER_S7395: + if (wbuf[2] == 0xd0) { + if (wbuf[3] == 0x24) { + st->signal_lock = rbuf[1]; + if ((st->stream_on & 1) && + (st->signal_lock & 0x8)) { + lme2510_stream_restart(d); + st->i2c_talk_onoff = 0; + } + } + } + break; + case TUNER_S0194: + if (wbuf[2] == 0xd0) { + if (wbuf[3] == 0x1b) { + st->signal_lock = rbuf[1]; + if ((st->stream_on & 1) && + (st->signal_lock & 0x8)) { + lme2510_stream_restart(d); + st->i2c_talk_onoff = 0; + } + } + } + break; + case TUNER_RS2000: + default: + break; + } + } else { + /* TODO rewrite this section */ + switch (st->tuner_config) { + case TUNER_LG: + switch (wbuf[3]) { + case 0x0e: + rbuf[0] = 0x55; + rbuf[1] = st->signal_lock; + break; + case 0x43: + rbuf[0] = 0x55; + rbuf[1] = st->signal_level; + break; + case 0x1c: + rbuf[0] = 0x55; + rbuf[1] = st->signal_sn; + break; + case 0x15: + case 0x16: + case 0x17: + case 0x18: + rbuf[0] = 0x55; + rbuf[1] = 0x00; + break; + default: + lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); + st->i2c_talk_onoff = 1; + break; + } + break; + case TUNER_S7395: + switch (wbuf[3]) { + case 0x10: + rbuf[0] = 0x55; + rbuf[1] = (st->signal_level & 0x80) + ? 0 : (st->signal_level * 2); + break; + case 0x2d: + rbuf[0] = 0x55; + rbuf[1] = st->signal_sn; + break; + case 0x24: + rbuf[0] = 0x55; + rbuf[1] = st->signal_lock; + break; + case 0x2e: + case 0x26: + case 0x27: + rbuf[0] = 0x55; + rbuf[1] = 0x00; + break; + default: + lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); + st->i2c_talk_onoff = 1; + break; + } + break; + case TUNER_S0194: + switch (wbuf[3]) { + case 0x18: + rbuf[0] = 0x55; + rbuf[1] = (st->signal_level & 0x80) + ? 0 : (st->signal_level * 2); + break; + case 0x24: + rbuf[0] = 0x55; + rbuf[1] = st->signal_sn; + break; + case 0x1b: + rbuf[0] = 0x55; + rbuf[1] = st->signal_lock; + break; + case 0x19: + case 0x25: + case 0x1e: + case 0x1d: + rbuf[0] = 0x55; + rbuf[1] = 0x00; + break; + default: + lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); + st->i2c_talk_onoff = 1; + break; + } + break; + case TUNER_RS2000: + switch (wbuf[3]) { + case 0x8c: + rbuf[0] = 0x55; + rbuf[1] = 0xff; + if (st->last_key == st->time_key) { + st->key_timeout++; + if (st->key_timeout > 5) + rbuf[1] = 0; + } else + st->key_timeout = 0; + st->last_key = st->time_key; + break; + default: + lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); + st->i2c_talk_onoff = 1; + break; + } + default: + break; + } + + deb_info(4, "I2C From Interrupt Message out(%02x) in(%02x)", + wbuf[3], rbuf[1]); + + } + + return ret; +} + + +static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct lme2510_state *st = d->priv; + static u8 obuf[64], ibuf[64]; + int i, read, read_o; + u16 len; + u8 gate = st->i2c_gate; + + mutex_lock(&d->i2c_mutex); + + if (gate == 0) + gate = 5; + + for (i = 0; i < num; i++) { + read_o = 1 & (msg[i].flags & I2C_M_RD); + read = i+1 < num && (msg[i+1].flags & I2C_M_RD); + read |= read_o; + gate = (msg[i].addr == st->i2c_tuner_addr) + ? (read) ? st->i2c_tuner_gate_r + : st->i2c_tuner_gate_w + : st->i2c_gate; + obuf[0] = gate | (read << 7); + + if (gate == 5) + obuf[1] = (read) ? 2 : msg[i].len + 1; + else + obuf[1] = msg[i].len + read + 1; + + obuf[2] = msg[i].addr; + if (read) { + if (read_o) + len = 3; + else { + memcpy(&obuf[3], msg[i].buf, msg[i].len); + obuf[msg[i].len+3] = msg[i+1].len; + len = msg[i].len+4; + } + } else { + memcpy(&obuf[3], msg[i].buf, msg[i].len); + len = msg[i].len+3; + } + + if (lme2510_msg(d, obuf, len, ibuf, 64) < 0) { + deb_info(1, "i2c transfer failed."); + mutex_unlock(&d->i2c_mutex); + return -EAGAIN; + } + + if (read) { + if (read_o) + memcpy(msg[i].buf, &ibuf[1], msg[i].len); + else { + memcpy(msg[i+1].buf, &ibuf[1], msg[i+1].len); + i++; + } + } + } + + mutex_unlock(&d->i2c_mutex); + return i; +} + +static u32 lme2510_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm lme2510_i2c_algo = { + .master_xfer = lme2510_i2c_xfer, + .functionality = lme2510_i2c_func, +}; + +static int lme2510_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct dvb_usb_adapter *adap = fe_to_adap(fe); + struct dvb_usb_device *d = adap_to_d(adap); + struct lme2510_state *st = adap_to_priv(adap); + static u8 clear_reg_3[] = LME_ALL_PIDS; + static u8 rbuf[1]; + int ret = 0, rlen = sizeof(rbuf); + + deb_info(1, "STM (%02x)", onoff); + + /* Streaming is started by FE_HAS_LOCK */ + if (onoff == 1) + st->stream_on = 1; + else { + deb_info(1, "STM Steam Off"); + /* mutex is here only to avoid collision with I2C */ + mutex_lock(&d->i2c_mutex); + + ret = lme2510_usb_talk(d, clear_reg_3, + sizeof(clear_reg_3), rbuf, rlen); + st->stream_on = 0; + st->i2c_talk_onoff = 1; + + mutex_unlock(&d->i2c_mutex); + } + + return (ret < 0) ? -ENODEV : 0; +} + +static u8 check_sum(u8 *p, u8 len) +{ + u8 sum = 0; + while (len--) + sum += *p++; + return sum; +} + +static int lme2510_download_firmware(struct dvb_usb_device *d, + const struct firmware *fw) +{ + int ret = 0; + u8 *data; + u16 j, wlen, len_in, start, end; + u8 packet_size, dlen, i; + u8 *fw_data; + + packet_size = 0x31; + len_in = 1; + + data = kzalloc(128, GFP_KERNEL); + if (!data) { + info("FRM Could not start Firmware Download"\ + "(Buffer allocation failed)"); + return -ENOMEM; + } + + info("FRM Starting Firmware Download"); + + for (i = 1; i < 3; i++) { + start = (i == 1) ? 0 : 512; + end = (i == 1) ? 512 : fw->size; + for (j = start; j < end; j += (packet_size+1)) { + fw_data = (u8 *)(fw->data + j); + if ((end - j) > packet_size) { + data[0] = i; + dlen = packet_size; + } else { + data[0] = i | 0x80; + dlen = (u8)(end - j)-1; + } + data[1] = dlen; + memcpy(&data[2], fw_data, dlen+1); + wlen = (u8) dlen + 4; + data[wlen-1] = check_sum(fw_data, dlen+1); + deb_info(1, "Data S=%02x:E=%02x CS= %02x", data[3], + data[dlen+2], data[dlen+3]); + lme2510_usb_talk(d, data, wlen, data, len_in); + ret |= (data[0] == 0x88) ? 0 : -1; + } + } + + data[0] = 0x8a; + len_in = 1; + msleep(2000); + lme2510_usb_talk(d, data, len_in, data, len_in); + msleep(400); + + if (ret < 0) + info("FRM Firmware Download Failed (%04x)" , ret); + else + info("FRM Firmware Download Completed - Resetting Device"); + + kfree(data); + return RECONNECTS_USB; +} + +static void lme_coldreset(struct dvb_usb_device *d) +{ + u8 data[1] = {0}; + data[0] = 0x0a; + info("FRM Firmware Cold Reset"); + + lme2510_usb_talk(d, data, sizeof(data), data, sizeof(data)); + + return; +} + +static const char fw_c_s7395[] = LME2510_C_S7395; +static const char fw_c_lg[] = LME2510_C_LG; +static const char fw_c_s0194[] = LME2510_C_S0194; +static const char fw_c_rs2000[] = LME2510_C_RS2000; +static const char fw_lg[] = LME2510_LG; +static const char fw_s0194[] = LME2510_S0194; + +const char *lme_firmware_switch(struct dvb_usb_device *d, int cold) +{ + struct lme2510_state *st = d->priv; + struct usb_device *udev = d->udev; + const struct firmware *fw = NULL; + const char *fw_lme; + int ret = 0; + + cold = (cold > 0) ? (cold & 1) : 0; + + switch (le16_to_cpu(udev->descriptor.idProduct)) { + case 0x1122: + switch (st->dvb_usb_lme2510_firmware) { + default: + st->dvb_usb_lme2510_firmware = TUNER_S0194; + case TUNER_S0194: + fw_lme = fw_s0194; + ret = request_firmware(&fw, fw_lme, &udev->dev); + if (ret == 0) { + cold = 0; + break; + } + st->dvb_usb_lme2510_firmware = TUNER_LG; + case TUNER_LG: + fw_lme = fw_lg; + ret = request_firmware(&fw, fw_lme, &udev->dev); + if (ret == 0) + break; + st->dvb_usb_lme2510_firmware = TUNER_DEFAULT; + break; + } + break; + case 0x1120: + switch (st->dvb_usb_lme2510_firmware) { + default: + st->dvb_usb_lme2510_firmware = TUNER_S7395; + case TUNER_S7395: + fw_lme = fw_c_s7395; + ret = request_firmware(&fw, fw_lme, &udev->dev); + if (ret == 0) { + cold = 0; + break; + } + st->dvb_usb_lme2510_firmware = TUNER_LG; + case TUNER_LG: + fw_lme = fw_c_lg; + ret = request_firmware(&fw, fw_lme, &udev->dev); + if (ret == 0) + break; + st->dvb_usb_lme2510_firmware = TUNER_S0194; + case TUNER_S0194: + fw_lme = fw_c_s0194; + ret = request_firmware(&fw, fw_lme, &udev->dev); + if (ret == 0) + break; + st->dvb_usb_lme2510_firmware = TUNER_DEFAULT; + cold = 0; + break; + } + break; + case 0x22f0: + fw_lme = fw_c_rs2000; + st->dvb_usb_lme2510_firmware = TUNER_RS2000; + break; + default: + fw_lme = fw_c_s7395; + } + + release_firmware(fw); + + if (cold) { + dvb_usb_lme2510_firmware = st->dvb_usb_lme2510_firmware; + info("FRM Changing to %s firmware", fw_lme); + lme_coldreset(d); + return NULL; + } + + return fw_lme; +} + +static int lme2510_kill_urb(struct usb_data_stream *stream) +{ + int i; + + for (i = 0; i < stream->urbs_submitted; i++) { + deb_info(3, "killing URB no. %d.", i); + /* stop the URB */ + usb_kill_urb(stream->urb_list[i]); + } + stream->urbs_submitted = 0; + + return 0; +} + +static struct tda10086_config tda10086_config = { + .demod_address = 0x1c, + .invert = 0, + .diseqc_tone = 1, + .xtal_freq = TDA10086_XTAL_16M, +}; + +static struct stv0288_config lme_config = { + .demod_address = 0xd0, + .min_delay_ms = 15, + .inittab = s7395_inittab, +}; + +static struct ix2505v_config lme_tuner = { + .tuner_address = 0xc0, + .min_delay_ms = 100, + .tuner_gain = 0x0, + .tuner_chargepump = 0x3, +}; + +static struct stv0299_config sharp_z0194_config = { + .demod_address = 0xd0, + .inittab = sharp_z0194a_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = sharp_z0194a_set_symbol_rate, +}; + +static int dm04_rs2000_set_ts_param(struct dvb_frontend *fe, + int caller) +{ + struct dvb_usb_adapter *adap = fe_to_adap(fe); + struct dvb_usb_device *d = adap_to_d(adap); + struct lme2510_state *st = d->priv; + + mutex_lock(&d->i2c_mutex); + if ((st->i2c_talk_onoff == 1) && (st->stream_on & 1)) { + st->i2c_talk_onoff = 0; + lme2510_stream_restart(d); + } + mutex_unlock(&d->i2c_mutex); + + return 0; +} + +static struct m88rs2000_config m88rs2000_config = { + .demod_addr = 0xd0, + .tuner_addr = 0xc0, + .set_ts_params = dm04_rs2000_set_ts_param, +}; + +static int dm04_lme2510_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct lme2510_state *st = fe_to_priv(fe); + static u8 voltage_low[] = LME_VOLTAGE_L; + static u8 voltage_high[] = LME_VOLTAGE_H; + static u8 rbuf[1]; + int ret = 0, len = 3, rlen = 1; + + mutex_lock(&d->i2c_mutex); + + switch (voltage) { + case SEC_VOLTAGE_18: + ret |= lme2510_usb_talk(d, + voltage_high, len, rbuf, rlen); + break; + + case SEC_VOLTAGE_OFF: + case SEC_VOLTAGE_13: + default: + ret |= lme2510_usb_talk(d, + voltage_low, len, rbuf, rlen); + break; + } + + mutex_unlock(&d->i2c_mutex); + + if (st->tuner_config == TUNER_RS2000) + if (st->fe_set_voltage) + st->fe_set_voltage(fe, voltage); + + + return (ret < 0) ? -ENODEV : 0; +} + +static int dm04_rs2000_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct lme2510_state *st = fe_to_priv(fe); + + *strength = (u16)((u32)st->signal_level * 0xffff / 0xff); + + return 0; +} + +static int dm04_rs2000_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct lme2510_state *st = fe_to_priv(fe); + + *snr = (u16)((u32)st->signal_sn * 0xffff / 0x7f); + + return 0; +} + +static int dm04_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + *ber = 0; + + return 0; +} + +static int dm04_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + + return 0; +} + +static int lme_name(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct lme2510_state *st = adap_to_priv(adap); + const char *desc = d->name; + char *fe_name[] = {"", " LG TDQY-P001F", " SHARP:BS2F7HZ7395", + " SHARP:BS2F7HZ0194", " RS2000"}; + char *name = adap->fe[0]->ops.info.name; + + strlcpy(name, desc, 128); + strlcat(name, fe_name[st->tuner_config], 128); + + return 0; +} + +static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct lme2510_state *st = d->priv; + int ret = 0; + + st->i2c_talk_onoff = 1; + switch (le16_to_cpu(d->udev->descriptor.idProduct)) { + case 0x1122: + case 0x1120: + st->i2c_gate = 4; + adap->fe[0] = dvb_attach(tda10086_attach, + &tda10086_config, &d->i2c_adap); + if (adap->fe[0]) { + info("TUN Found Frontend TDA10086"); + st->i2c_tuner_gate_w = 4; + st->i2c_tuner_gate_r = 4; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_LG; + if (st->dvb_usb_lme2510_firmware != TUNER_LG) { + st->dvb_usb_lme2510_firmware = TUNER_LG; + ret = lme_firmware_switch(d, 1) ? 0 : -ENODEV; + } + break; + } + + st->i2c_gate = 4; + adap->fe[0] = dvb_attach(stv0299_attach, + &sharp_z0194_config, &d->i2c_adap); + if (adap->fe[0]) { + info("FE Found Stv0299"); + st->i2c_tuner_gate_w = 4; + st->i2c_tuner_gate_r = 5; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_S0194; + if (st->dvb_usb_lme2510_firmware != TUNER_S0194) { + st->dvb_usb_lme2510_firmware = TUNER_S0194; + ret = lme_firmware_switch(d, 1) ? 0 : -ENODEV; + } + break; + } + + st->i2c_gate = 5; + adap->fe[0] = dvb_attach(stv0288_attach, &lme_config, + &d->i2c_adap); + + if (adap->fe[0]) { + info("FE Found Stv0288"); + st->i2c_tuner_gate_w = 4; + st->i2c_tuner_gate_r = 5; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_S7395; + if (st->dvb_usb_lme2510_firmware != TUNER_S7395) { + st->dvb_usb_lme2510_firmware = TUNER_S7395; + ret = lme_firmware_switch(d, 1) ? 0 : -ENODEV; + } + break; + } + case 0x22f0: + st->i2c_gate = 5; + adap->fe[0] = dvb_attach(m88rs2000_attach, + &m88rs2000_config, &d->i2c_adap); + + if (adap->fe[0]) { + info("FE Found M88RS2000"); + st->i2c_tuner_gate_w = 5; + st->i2c_tuner_gate_r = 5; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_RS2000; + st->fe_set_voltage = + adap->fe[0]->ops.set_voltage; + + adap->fe[0]->ops.read_signal_strength = + dm04_rs2000_read_signal_strength; + adap->fe[0]->ops.read_snr = + dm04_rs2000_read_snr; + adap->fe[0]->ops.read_ber = + dm04_read_ber; + adap->fe[0]->ops.read_ucblocks = + dm04_read_ucblocks; + } + break; + } + + if (adap->fe[0] == NULL) { + info("DM04/QQBOX Not Powered up or not Supported"); + return -ENODEV; + } + + if (ret) { + if (adap->fe[0]) { + dvb_frontend_detach(adap->fe[0]); + adap->fe[0] = NULL; + } + d->rc_map = NULL; + return -ENODEV; + } + + adap->fe[0]->ops.set_voltage = dm04_lme2510_set_voltage; + ret = lme_name(adap); + return ret; +} + +static int dm04_lme2510_tuner(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct lme2510_state *st = adap_to_priv(adap); + char *tun_msg[] = {"", "TDA8263", "IX2505V", "DVB_PLL_OPERA", "RS2000"}; + int ret = 0; + + switch (st->tuner_config) { + case TUNER_LG: + if (dvb_attach(tda826x_attach, adap->fe[0], 0xc0, + &d->i2c_adap, 1)) + ret = st->tuner_config; + break; + case TUNER_S7395: + if (dvb_attach(ix2505v_attach , adap->fe[0], &lme_tuner, + &d->i2c_adap)) + ret = st->tuner_config; + break; + case TUNER_S0194: + if (dvb_attach(dvb_pll_attach , adap->fe[0], 0xc0, + &d->i2c_adap, DVB_PLL_OPERA1)) + ret = st->tuner_config; + break; + case TUNER_RS2000: + ret = st->tuner_config; + break; + default: + break; + } + + if (ret) + info("TUN Found %s tuner", tun_msg[ret]); + else { + info("TUN No tuner found --- resetting device"); + lme_coldreset(d); + return -ENODEV; + } + + /* Start the Interrupt*/ + ret = lme2510_int_read(adap); + if (ret < 0) { + info("INT Unable to start Interrupt Service"); + return -ENODEV; + } + + return ret; +} + +static int lme2510_powerup(struct dvb_usb_device *d, int onoff) +{ + struct lme2510_state *st = d->priv; + static u8 lnb_on[] = LNB_ON; + static u8 lnb_off[] = LNB_OFF; + static u8 rbuf[1]; + int ret = 0, len = 3, rlen = 1; + + mutex_lock(&d->i2c_mutex); + + if (onoff) + ret = lme2510_usb_talk(d, lnb_on, len, rbuf, rlen); + else + ret = lme2510_usb_talk(d, lnb_off, len, rbuf, rlen); + + st->i2c_talk_onoff = 1; + + mutex_unlock(&d->i2c_mutex); + + return ret; +} + +static int lme2510_get_adapter_count(struct dvb_usb_device *d) +{ + return 1; +} + +static int lme2510_identify_state(struct dvb_usb_device *d, const char **name) +{ + struct lme2510_state *st = d->priv; + + usb_reset_configuration(d->udev); + + usb_set_interface(d->udev, + d->intf->cur_altsetting->desc.bInterfaceNumber, 1); + + st->dvb_usb_lme2510_firmware = dvb_usb_lme2510_firmware; + + if (lme2510_return_status(d) == 0x44) { + *name = lme_firmware_switch(d, 0); + return COLD; + } + + return 0; +} + +static int lme2510_get_stream_config(struct dvb_frontend *fe, u8 *ts_type, + struct usb_data_stream_properties *stream) +{ + struct dvb_usb_adapter *adap = fe_to_adap(fe); + struct dvb_usb_device *d = adap_to_d(adap); + + if (adap == NULL) + return 0; + /* Turn PID filter on the fly by module option */ + if (pid_filter == 2) { + adap->pid_filtering = 1; + adap->max_feed_count = 15; + } + + if (!(le16_to_cpu(d->udev->descriptor.idProduct) + == 0x1122)) + stream->endpoint = 0x8; + + return 0; +} + +static int lme2510_get_rc_config(struct dvb_usb_device *d, + struct dvb_usb_rc *rc) +{ + rc->allowed_protos = RC_TYPE_NEC; + return 0; +} + +static void *lme2510_exit_int(struct dvb_usb_device *d) +{ + struct lme2510_state *st = d->priv; + struct dvb_usb_adapter *adap = &d->adapter[0]; + void *buffer = NULL; + + if (adap != NULL) { + lme2510_kill_urb(&adap->stream); + } + + if (st->usb_buffer != NULL) { + st->i2c_talk_onoff = 1; + st->signal_lock = 0; + st->signal_level = 0; + st->signal_sn = 0; + buffer = st->usb_buffer; + } + + if (st->lme_urb != NULL) { + usb_kill_urb(st->lme_urb); + usb_free_coherent(d->udev, 128, st->buffer, + st->lme_urb->transfer_dma); + info("Interrupt Service Stopped"); + } + + return buffer; +} + +static void lme2510_exit(struct dvb_usb_device *d) +{ + void *usb_buffer; + + if (d != NULL) { + usb_buffer = lme2510_exit_int(d); + if (usb_buffer != NULL) + kfree(usb_buffer); + } +} + +static struct dvb_usb_device_properties lme2510_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .bInterfaceNumber = 0, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct lme2510_state), + + .download_firmware = lme2510_download_firmware, + + .power_ctrl = lme2510_powerup, + .identify_state = lme2510_identify_state, + .i2c_algo = &lme2510_i2c_algo, + + .frontend_attach = dm04_lme2510_frontend_attach, + .tuner_attach = dm04_lme2510_tuner, + .get_stream_config = lme2510_get_stream_config, + .get_adapter_count = lme2510_get_adapter_count, + .streaming_ctrl = lme2510_streaming_ctrl, + + .get_rc_config = lme2510_get_rc_config, + + .exit = lme2510_exit, + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER| + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 15, + .pid_filter = lme2510_pid_filter, + .pid_filter_ctrl = lme2510_pid_filter_ctrl, + .stream = + DVB_USB_STREAM_BULK(0x86, 10, 4096), + }, + { + } + }, +}; + +static const struct usb_device_id lme2510_id_table[] = { + { DVB_USB_DEVICE(0x3344, 0x1122, &lme2510_props, + "DM04_LME2510_DVB-S", RC_MAP_LME2510) }, + { DVB_USB_DEVICE(0x3344, 0x1120, &lme2510_props, + "DM04_LME2510C_DVB-S", RC_MAP_LME2510) }, + { DVB_USB_DEVICE(0x3344, 0x22f0, &lme2510_props, + "DM04_LME2510C_DVB-S RS2000", RC_MAP_LME2510) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, lme2510_id_table); + +static struct usb_driver lme2510_driver = { + .name = KBUILD_MODNAME, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .id_table = lme2510_id_table, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(lme2510_driver); + +MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>"); +MODULE_DESCRIPTION("LME2510(C) DVB-S USB2.0"); +MODULE_VERSION("2.06"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(LME2510_C_S7395); +MODULE_FIRMWARE(LME2510_C_LG); +MODULE_FIRMWARE(LME2510_C_S0194); +MODULE_FIRMWARE(LME2510_C_RS2000); +MODULE_FIRMWARE(LME2510_LG); +MODULE_FIRMWARE(LME2510_S0194); + diff --git a/drivers/media/usb/dvb-usb-v2/lmedm04.h b/drivers/media/usb/dvb-usb-v2/lmedm04.h new file mode 100644 index 000000000000..e9c207205c2f --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/lmedm04.h @@ -0,0 +1,175 @@ +/* DVB USB compliant linux driver for + * + * DM04/QQBOX DVB-S USB BOX LME2510C + SHARP:BS2F7HZ7395 + * LME2510C + LG TDQY-P001F + * LME2510 + LG TDQY-P001F + * + * MVB7395 (LME2510C+SHARP:BS2F7HZ7395) + * SHARP:BS2F7HZ7395 = (STV0288+Sharp IX2505V) + * + * MVB001F (LME2510+LGTDQT-P001F) + * LG TDQY - P001F =(TDA8263 + TDA10086H) + * + * MVB0001F (LME2510C+LGTDQT-P001F) + * + * 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. + * * + * see Documentation/dvb/README.dvb-usb for more information + */ +#ifndef _DVB_USB_LME2510_H_ +#define _DVB_USB_LME2510_H_ + +/* Streamer & PID + * + * Note: These commands do not actually stop the streaming + * but form some kind of packet filtering/stream count + * or tuning related functions. + * 06 XX + * offset 1 = 00 Enable Streaming + * + * + * PID + * 03 XX XX ----> reg number ---> setting....20 XX + * offset 1 = length + * offset 2 = start of data + * end byte -1 = 20 + * end byte = clear pid always a0, other wise 9c, 9a ?? + * +*/ +#define LME_ST_ON_W {0x06, 0x00} +#define LME_CLEAR_PID {0x03, 0x02, 0x20, 0xa0} +#define LME_ZERO_PID {0x03, 0x06, 0x00, 0x00, 0x01, 0x00, 0x20, 0x9c} +#define LME_ALL_PIDS {0x03, 0x06, 0x00, 0xff, 0x01, 0x1f, 0x20, 0x81} + +/* LNB Voltage + * 07 XX XX + * offset 1 = 01 + * offset 2 = 00=Voltage low 01=Voltage high + * + * LNB Power + * 03 01 XX + * offset 2 = 00=ON 01=OFF + */ + +#define LME_VOLTAGE_L {0x07, 0x01, 0x00} +#define LME_VOLTAGE_H {0x07, 0x01, 0x01} +#define LNB_ON {0x3a, 0x01, 0x00} +#define LNB_OFF {0x3a, 0x01, 0x01} + +/* Initial stv0288 settings for 7395 Frontend */ +static u8 s7395_inittab[] = { + 0x01, 0x15, + 0x02, 0x20, + 0x03, 0xa0, + 0x04, 0xa0, + 0x05, 0x12, + 0x06, 0x00, + 0x09, 0x00, + 0x0a, 0x04, + 0x0b, 0x00, + 0x0c, 0x00, + 0x0d, 0x00, + 0x0e, 0xc1, + 0x0f, 0x54, + 0x11, 0x7a, + 0x12, 0x03, + 0x13, 0x48, + 0x14, 0x84, + 0x15, 0xc5, + 0x16, 0xb8, + 0x17, 0x9c, + 0x18, 0x00, + 0x19, 0xa6, + 0x1a, 0x88, + 0x1b, 0x8f, + 0x1c, 0xf0, + 0x20, 0x0b, + 0x21, 0x54, + 0x22, 0xff, + 0x23, 0x01, + 0x28, 0x46, + 0x29, 0x66, + 0x2a, 0x90, + 0x2b, 0xfa, + 0x2c, 0xd9, + 0x30, 0x0, + 0x31, 0x1e, + 0x32, 0x14, + 0x33, 0x0f, + 0x34, 0x09, + 0x35, 0x0c, + 0x36, 0x05, + 0x37, 0x2f, + 0x38, 0x16, + 0x39, 0xbd, + 0x3a, 0x0, + 0x3b, 0x13, + 0x3c, 0x11, + 0x3d, 0x30, + 0x40, 0x63, + 0x41, 0x04, + 0x42, 0x20, + 0x43, 0x00, + 0x44, 0x00, + 0x45, 0x00, + 0x46, 0x00, + 0x47, 0x00, + 0x4a, 0x00, + 0x50, 0x10, + 0x51, 0x36, + 0x52, 0x21, + 0x53, 0x94, + 0x54, 0xb2, + 0x55, 0x29, + 0x56, 0x64, + 0x57, 0x2b, + 0x58, 0x54, + 0x59, 0x86, + 0x5a, 0x00, + 0x5b, 0x9b, + 0x5c, 0x08, + 0x5d, 0x7f, + 0x5e, 0xff, + 0x5f, 0x8d, + 0x70, 0x0, + 0x71, 0x0, + 0x72, 0x0, + 0x74, 0x0, + 0x75, 0x0, + 0x76, 0x0, + 0x81, 0x0, + 0x82, 0x3f, + 0x83, 0x3f, + 0x84, 0x0, + 0x85, 0x0, + 0x88, 0x0, + 0x89, 0x0, + 0x8a, 0x0, + 0x8b, 0x0, + 0x8c, 0x0, + 0x90, 0x0, + 0x91, 0x0, + 0x92, 0x0, + 0x93, 0x0, + 0x94, 0x1c, + 0x97, 0x0, + 0xa0, 0x48, + 0xa1, 0x0, + 0xb0, 0xb8, + 0xb1, 0x3a, + 0xb2, 0x10, + 0xb3, 0x82, + 0xb4, 0x80, + 0xb5, 0x82, + 0xb6, 0x82, + 0xb7, 0x82, + 0xb8, 0x20, + 0xb9, 0x0, + 0xf0, 0x0, + 0xf1, 0x0, + 0xf2, 0xc0, + 0xff, 0xff, +}; +#endif diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.c new file mode 100644 index 000000000000..d83df4bb72d3 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.c @@ -0,0 +1,612 @@ +/* + * mxl111sf-demod.c - driver for the MaxLinear MXL111SF DVB-T demodulator + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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 "mxl111sf-demod.h" +#include "mxl111sf-reg.h" + +/* debug */ +static int mxl111sf_demod_debug; +module_param_named(debug, mxl111sf_demod_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); + +#define mxl_dbg(fmt, arg...) \ + if (mxl111sf_demod_debug) \ + mxl_printk(KERN_DEBUG, fmt, ##arg) + +/* ------------------------------------------------------------------------ */ + +struct mxl111sf_demod_state { + struct mxl111sf_state *mxl_state; + + struct mxl111sf_demod_config *cfg; + + struct dvb_frontend fe; +}; + +/* ------------------------------------------------------------------------ */ + +static int mxl111sf_demod_read_reg(struct mxl111sf_demod_state *state, + u8 addr, u8 *data) +{ + return (state->cfg->read_reg) ? + state->cfg->read_reg(state->mxl_state, addr, data) : + -EINVAL; +} + +static int mxl111sf_demod_write_reg(struct mxl111sf_demod_state *state, + u8 addr, u8 data) +{ + return (state->cfg->write_reg) ? + state->cfg->write_reg(state->mxl_state, addr, data) : + -EINVAL; +} + +static +int mxl111sf_demod_program_regs(struct mxl111sf_demod_state *state, + struct mxl111sf_reg_ctrl_info *ctrl_reg_info) +{ + return (state->cfg->program_regs) ? + state->cfg->program_regs(state->mxl_state, ctrl_reg_info) : + -EINVAL; +} + +/* ------------------------------------------------------------------------ */ +/* TPS */ + +static +int mxl1x1sf_demod_get_tps_code_rate(struct mxl111sf_demod_state *state, + fe_code_rate_t *code_rate) +{ + u8 val; + int ret = mxl111sf_demod_read_reg(state, V6_CODE_RATE_TPS_REG, &val); + /* bit<2:0> - 000:1/2, 001:2/3, 010:3/4, 011:5/6, 100:7/8 */ + if (mxl_fail(ret)) + goto fail; + + switch (val & V6_CODE_RATE_TPS_MASK) { + case 0: + *code_rate = FEC_1_2; + break; + case 1: + *code_rate = FEC_2_3; + break; + case 2: + *code_rate = FEC_3_4; + break; + case 3: + *code_rate = FEC_5_6; + break; + case 4: + *code_rate = FEC_7_8; + break; + } +fail: + return ret; +} + +static +int mxl1x1sf_demod_get_tps_modulation(struct mxl111sf_demod_state *state, + fe_modulation_t *modulation) +{ + u8 val; + int ret = mxl111sf_demod_read_reg(state, V6_MODORDER_TPS_REG, &val); + /* Constellation, 00 : QPSK, 01 : 16QAM, 10:64QAM */ + if (mxl_fail(ret)) + goto fail; + + switch ((val & V6_PARAM_CONSTELLATION_MASK) >> 4) { + case 0: + *modulation = QPSK; + break; + case 1: + *modulation = QAM_16; + break; + case 2: + *modulation = QAM_64; + break; + } +fail: + return ret; +} + +static +int mxl1x1sf_demod_get_tps_guard_fft_mode(struct mxl111sf_demod_state *state, + fe_transmit_mode_t *fft_mode) +{ + u8 val; + int ret = mxl111sf_demod_read_reg(state, V6_MODE_TPS_REG, &val); + /* FFT Mode, 00:2K, 01:8K, 10:4K */ + if (mxl_fail(ret)) + goto fail; + + switch ((val & V6_PARAM_FFT_MODE_MASK) >> 2) { + case 0: + *fft_mode = TRANSMISSION_MODE_2K; + break; + case 1: + *fft_mode = TRANSMISSION_MODE_8K; + break; + case 2: + *fft_mode = TRANSMISSION_MODE_4K; + break; + } +fail: + return ret; +} + +static +int mxl1x1sf_demod_get_tps_guard_interval(struct mxl111sf_demod_state *state, + fe_guard_interval_t *guard) +{ + u8 val; + int ret = mxl111sf_demod_read_reg(state, V6_CP_TPS_REG, &val); + /* 00:1/32, 01:1/16, 10:1/8, 11:1/4 */ + if (mxl_fail(ret)) + goto fail; + + switch ((val & V6_PARAM_GI_MASK) >> 4) { + case 0: + *guard = GUARD_INTERVAL_1_32; + break; + case 1: + *guard = GUARD_INTERVAL_1_16; + break; + case 2: + *guard = GUARD_INTERVAL_1_8; + break; + case 3: + *guard = GUARD_INTERVAL_1_4; + break; + } +fail: + return ret; +} + +static +int mxl1x1sf_demod_get_tps_hierarchy(struct mxl111sf_demod_state *state, + fe_hierarchy_t *hierarchy) +{ + u8 val; + int ret = mxl111sf_demod_read_reg(state, V6_TPS_HIERACHY_REG, &val); + /* bit<6:4> - 000:Non hierarchy, 001:1, 010:2, 011:4 */ + if (mxl_fail(ret)) + goto fail; + + switch ((val & V6_TPS_HIERARCHY_INFO_MASK) >> 6) { + case 0: + *hierarchy = HIERARCHY_NONE; + break; + case 1: + *hierarchy = HIERARCHY_1; + break; + case 2: + *hierarchy = HIERARCHY_2; + break; + case 3: + *hierarchy = HIERARCHY_4; + break; + } +fail: + return ret; +} + +/* ------------------------------------------------------------------------ */ +/* LOCKS */ + +static +int mxl1x1sf_demod_get_sync_lock_status(struct mxl111sf_demod_state *state, + int *sync_lock) +{ + u8 val = 0; + int ret = mxl111sf_demod_read_reg(state, V6_SYNC_LOCK_REG, &val); + if (mxl_fail(ret)) + goto fail; + *sync_lock = (val & SYNC_LOCK_MASK) >> 4; +fail: + return ret; +} + +static +int mxl1x1sf_demod_get_rs_lock_status(struct mxl111sf_demod_state *state, + int *rs_lock) +{ + u8 val = 0; + int ret = mxl111sf_demod_read_reg(state, V6_RS_LOCK_DET_REG, &val); + if (mxl_fail(ret)) + goto fail; + *rs_lock = (val & RS_LOCK_DET_MASK) >> 3; +fail: + return ret; +} + +static +int mxl1x1sf_demod_get_tps_lock_status(struct mxl111sf_demod_state *state, + int *tps_lock) +{ + u8 val = 0; + int ret = mxl111sf_demod_read_reg(state, V6_TPS_LOCK_REG, &val); + if (mxl_fail(ret)) + goto fail; + *tps_lock = (val & V6_PARAM_TPS_LOCK_MASK) >> 6; +fail: + return ret; +} + +static +int mxl1x1sf_demod_get_fec_lock_status(struct mxl111sf_demod_state *state, + int *fec_lock) +{ + u8 val = 0; + int ret = mxl111sf_demod_read_reg(state, V6_IRQ_STATUS_REG, &val); + if (mxl_fail(ret)) + goto fail; + *fec_lock = (val & IRQ_MASK_FEC_LOCK) >> 4; +fail: + return ret; +} + +#if 0 +static +int mxl1x1sf_demod_get_cp_lock_status(struct mxl111sf_demod_state *state, + int *cp_lock) +{ + u8 val = 0; + int ret = mxl111sf_demod_read_reg(state, V6_CP_LOCK_DET_REG, &val); + if (mxl_fail(ret)) + goto fail; + *cp_lock = (val & V6_CP_LOCK_DET_MASK) >> 2; +fail: + return ret; +} +#endif + +static int mxl1x1sf_demod_reset_irq_status(struct mxl111sf_demod_state *state) +{ + return mxl111sf_demod_write_reg(state, 0x0e, 0xff); +} + +/* ------------------------------------------------------------------------ */ + +static int mxl111sf_demod_set_frontend(struct dvb_frontend *fe) +{ + struct mxl111sf_demod_state *state = fe->demodulator_priv; + int ret = 0; + + struct mxl111sf_reg_ctrl_info phy_pll_patch[] = { + {0x00, 0xff, 0x01}, /* change page to 1 */ + {0x40, 0xff, 0x05}, + {0x40, 0xff, 0x01}, + {0x41, 0xff, 0xca}, + {0x41, 0xff, 0xc0}, + {0x00, 0xff, 0x00}, /* change page to 0 */ + {0, 0, 0} + }; + + mxl_dbg("()"); + + if (fe->ops.tuner_ops.set_params) { + ret = fe->ops.tuner_ops.set_params(fe); + if (mxl_fail(ret)) + goto fail; + msleep(50); + } + ret = mxl111sf_demod_program_regs(state, phy_pll_patch); + mxl_fail(ret); + msleep(50); + ret = mxl1x1sf_demod_reset_irq_status(state); + mxl_fail(ret); + msleep(100); +fail: + return ret; +} + +/* ------------------------------------------------------------------------ */ + +#if 0 +/* resets TS Packet error count */ +/* After setting 7th bit of V5_PER_COUNT_RESET_REG, it should be reset to 0. */ +static +int mxl1x1sf_demod_reset_packet_error_count(struct mxl111sf_demod_state *state) +{ + struct mxl111sf_reg_ctrl_info reset_per_count[] = { + {0x20, 0x01, 0x01}, + {0x20, 0x01, 0x00}, + {0, 0, 0} + }; + return mxl111sf_demod_program_regs(state, reset_per_count); +} +#endif + +/* returns TS Packet error count */ +/* PER Count = FEC_PER_COUNT * (2 ** (FEC_PER_SCALE * 4)) */ +static int mxl111sf_demod_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct mxl111sf_demod_state *state = fe->demodulator_priv; + u32 fec_per_count, fec_per_scale; + u8 val; + int ret; + + *ucblocks = 0; + + /* FEC_PER_COUNT Register */ + ret = mxl111sf_demod_read_reg(state, V6_FEC_PER_COUNT_REG, &val); + if (mxl_fail(ret)) + goto fail; + + fec_per_count = val; + + /* FEC_PER_SCALE Register */ + ret = mxl111sf_demod_read_reg(state, V6_FEC_PER_SCALE_REG, &val); + if (mxl_fail(ret)) + goto fail; + + val &= V6_FEC_PER_SCALE_MASK; + val *= 4; + + fec_per_scale = 1 << val; + + fec_per_count *= fec_per_scale; + + *ucblocks = fec_per_count; +fail: + return ret; +} + +#ifdef MXL111SF_DEMOD_ENABLE_CALCULATIONS +/* FIXME: leaving this enabled breaks the build on some architectures, + * and we shouldn't have any floating point math in the kernel, anyway. + * + * These macros need to be re-written, but it's harmless to simply + * return zero for now. */ +#define CALCULATE_BER(avg_errors, count) \ + ((u32)(avg_errors * 4)/(count*64*188*8)) +#define CALCULATE_SNR(data) \ + ((u32)((10 * (u32)data / 64) - 2.5)) +#else +#define CALCULATE_BER(avg_errors, count) 0 +#define CALCULATE_SNR(data) 0 +#endif + +static int mxl111sf_demod_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct mxl111sf_demod_state *state = fe->demodulator_priv; + u8 val1, val2, val3; + int ret; + + *ber = 0; + + ret = mxl111sf_demod_read_reg(state, V6_RS_AVG_ERRORS_LSB_REG, &val1); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_demod_read_reg(state, V6_RS_AVG_ERRORS_MSB_REG, &val2); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_demod_read_reg(state, V6_N_ACCUMULATE_REG, &val3); + if (mxl_fail(ret)) + goto fail; + + *ber = CALCULATE_BER((val1 | (val2 << 8)), val3); +fail: + return ret; +} + +static int mxl111sf_demod_calc_snr(struct mxl111sf_demod_state *state, + u16 *snr) +{ + u8 val1, val2; + int ret; + + *snr = 0; + + ret = mxl111sf_demod_read_reg(state, V6_SNR_RB_LSB_REG, &val1); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_demod_read_reg(state, V6_SNR_RB_MSB_REG, &val2); + if (mxl_fail(ret)) + goto fail; + + *snr = CALCULATE_SNR(val1 | ((val2 & 0x03) << 8)); +fail: + return ret; +} + +static int mxl111sf_demod_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct mxl111sf_demod_state *state = fe->demodulator_priv; + + int ret = mxl111sf_demod_calc_snr(state, snr); + if (mxl_fail(ret)) + goto fail; + + *snr /= 10; /* 0.1 dB */ +fail: + return ret; +} + +static int mxl111sf_demod_read_status(struct dvb_frontend *fe, + fe_status_t *status) +{ + struct mxl111sf_demod_state *state = fe->demodulator_priv; + int ret, locked, cr_lock, sync_lock, fec_lock; + + *status = 0; + + ret = mxl1x1sf_demod_get_rs_lock_status(state, &locked); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_demod_get_tps_lock_status(state, &cr_lock); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_demod_get_sync_lock_status(state, &sync_lock); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_demod_get_fec_lock_status(state, &fec_lock); + if (mxl_fail(ret)) + goto fail; + + if (locked) + *status |= FE_HAS_SIGNAL; + if (cr_lock) + *status |= FE_HAS_CARRIER; + if (sync_lock) + *status |= FE_HAS_SYNC; + if (fec_lock) /* false positives? */ + *status |= FE_HAS_VITERBI; + + if ((locked) && (cr_lock) && (sync_lock)) + *status |= FE_HAS_LOCK; +fail: + return ret; +} + +static int mxl111sf_demod_read_signal_strength(struct dvb_frontend *fe, + u16 *signal_strength) +{ + struct mxl111sf_demod_state *state = fe->demodulator_priv; + fe_modulation_t modulation; + u16 snr; + + mxl111sf_demod_calc_snr(state, &snr); + mxl1x1sf_demod_get_tps_modulation(state, &modulation); + + switch (modulation) { + case QPSK: + *signal_strength = (snr >= 1300) ? + min(65535, snr * 44) : snr * 38; + break; + case QAM_16: + *signal_strength = (snr >= 1500) ? + min(65535, snr * 38) : snr * 33; + break; + case QAM_64: + *signal_strength = (snr >= 2000) ? + min(65535, snr * 29) : snr * 25; + break; + default: + *signal_strength = 0; + return -EINVAL; + } + + return 0; +} + +static int mxl111sf_demod_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct mxl111sf_demod_state *state = fe->demodulator_priv; + + mxl_dbg("()"); +#if 0 + p->inversion = /* FIXME */ ? INVERSION_ON : INVERSION_OFF; +#endif + if (fe->ops.tuner_ops.get_bandwidth) + fe->ops.tuner_ops.get_bandwidth(fe, &p->bandwidth_hz); + if (fe->ops.tuner_ops.get_frequency) + fe->ops.tuner_ops.get_frequency(fe, &p->frequency); + mxl1x1sf_demod_get_tps_code_rate(state, &p->code_rate_HP); + mxl1x1sf_demod_get_tps_code_rate(state, &p->code_rate_LP); + mxl1x1sf_demod_get_tps_modulation(state, &p->modulation); + mxl1x1sf_demod_get_tps_guard_fft_mode(state, + &p->transmission_mode); + mxl1x1sf_demod_get_tps_guard_interval(state, + &p->guard_interval); + mxl1x1sf_demod_get_tps_hierarchy(state, + &p->hierarchy); + + return 0; +} + +static +int mxl111sf_demod_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 1000; + return 0; +} + +static void mxl111sf_demod_release(struct dvb_frontend *fe) +{ + struct mxl111sf_demod_state *state = fe->demodulator_priv; + mxl_dbg("()"); + kfree(state); + fe->demodulator_priv = NULL; +} + +static struct dvb_frontend_ops mxl111sf_demod_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "MaxLinear MxL111SF DVB-T demodulator", + .frequency_min = 177000000, + .frequency_max = 858000000, + .frequency_stepsize = 166666, + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | + FE_CAN_HIERARCHY_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_RECOVER + }, + .release = mxl111sf_demod_release, +#if 0 + .init = mxl111sf_init, + .i2c_gate_ctrl = mxl111sf_i2c_gate_ctrl, +#endif + .set_frontend = mxl111sf_demod_set_frontend, + .get_frontend = mxl111sf_demod_get_frontend, + .get_tune_settings = mxl111sf_demod_get_tune_settings, + .read_status = mxl111sf_demod_read_status, + .read_signal_strength = mxl111sf_demod_read_signal_strength, + .read_ber = mxl111sf_demod_read_ber, + .read_snr = mxl111sf_demod_read_snr, + .read_ucblocks = mxl111sf_demod_read_ucblocks, +}; + +struct dvb_frontend *mxl111sf_demod_attach(struct mxl111sf_state *mxl_state, + struct mxl111sf_demod_config *cfg) +{ + struct mxl111sf_demod_state *state = NULL; + + mxl_dbg("()"); + + state = kzalloc(sizeof(struct mxl111sf_demod_state), GFP_KERNEL); + if (state == NULL) + return NULL; + + state->mxl_state = mxl_state; + state->cfg = cfg; + + memcpy(&state->fe.ops, &mxl111sf_demod_ops, + sizeof(struct dvb_frontend_ops)); + + state->fe.demodulator_priv = state; + return &state->fe; +} +EXPORT_SYMBOL_GPL(mxl111sf_demod_attach); + +MODULE_DESCRIPTION("MaxLinear MxL111SF DVB-T demodulator driver"); +MODULE_AUTHOR("Michael Krufky <mkrufky@kernellabs.com>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.h new file mode 100644 index 000000000000..432706ae5274 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.h @@ -0,0 +1,55 @@ +/* + * mxl111sf-demod.h - driver for the MaxLinear MXL111SF DVB-T demodulator + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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. + */ + +#ifndef __MXL111SF_DEMOD_H__ +#define __MXL111SF_DEMOD_H__ + +#include "dvb_frontend.h" +#include "mxl111sf.h" + +struct mxl111sf_demod_config { + int (*read_reg)(struct mxl111sf_state *state, u8 addr, u8 *data); + int (*write_reg)(struct mxl111sf_state *state, u8 addr, u8 data); + int (*program_regs)(struct mxl111sf_state *state, + struct mxl111sf_reg_ctrl_info *ctrl_reg_info); +}; + +#if defined(CONFIG_DVB_USB_MXL111SF) || \ + (defined(CONFIG_DVB_USB_MXL111SF_MODULE) && defined(MODULE)) +extern +struct dvb_frontend *mxl111sf_demod_attach(struct mxl111sf_state *mxl_state, + struct mxl111sf_demod_config *cfg); +#else +static inline +struct dvb_frontend *mxl111sf_demod_attach(struct mxl111sf_state *mxl_state, + struct mxl111sf_demod_config *cfg) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_USB_MXL111SF */ + +#endif /* __MXL111SF_DEMOD_H__ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.c new file mode 100644 index 000000000000..e4121cb8f5ef --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.c @@ -0,0 +1,763 @@ +/* + * mxl111sf-gpio.c - driver for the MaxLinear MXL111SF + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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 "mxl111sf-gpio.h" +#include "mxl111sf-i2c.h" +#include "mxl111sf.h" + +/* ------------------------------------------------------------------------- */ + +#define MXL_GPIO_MUX_REG_0 0x84 +#define MXL_GPIO_MUX_REG_1 0x89 +#define MXL_GPIO_MUX_REG_2 0x82 + +#define MXL_GPIO_DIR_INPUT 0 +#define MXL_GPIO_DIR_OUTPUT 1 + + +static int mxl111sf_set_gpo_state(struct mxl111sf_state *state, u8 pin, u8 val) +{ + int ret; + u8 tmp; + + mxl_debug_adv("(%d, %d)", pin, val); + + if ((pin > 0) && (pin < 8)) { + ret = mxl111sf_read_reg(state, 0x19, &tmp); + if (mxl_fail(ret)) + goto fail; + tmp &= ~(1 << (pin - 1)); + tmp |= (val << (pin - 1)); + ret = mxl111sf_write_reg(state, 0x19, tmp); + if (mxl_fail(ret)) + goto fail; + } else if (pin <= 10) { + if (pin == 0) + pin += 7; + ret = mxl111sf_read_reg(state, 0x30, &tmp); + if (mxl_fail(ret)) + goto fail; + tmp &= ~(1 << (pin - 3)); + tmp |= (val << (pin - 3)); + ret = mxl111sf_write_reg(state, 0x30, tmp); + if (mxl_fail(ret)) + goto fail; + } else + ret = -EINVAL; +fail: + return ret; +} + +static int mxl111sf_get_gpi_state(struct mxl111sf_state *state, u8 pin, u8 *val) +{ + int ret; + u8 tmp; + + mxl_debug("(0x%02x)", pin); + + *val = 0; + + switch (pin) { + case 0: + case 1: + case 2: + case 3: + ret = mxl111sf_read_reg(state, 0x23, &tmp); + if (mxl_fail(ret)) + goto fail; + *val = (tmp >> (pin + 4)) & 0x01; + break; + case 4: + case 5: + case 6: + case 7: + ret = mxl111sf_read_reg(state, 0x2f, &tmp); + if (mxl_fail(ret)) + goto fail; + *val = (tmp >> pin) & 0x01; + break; + case 8: + case 9: + case 10: + ret = mxl111sf_read_reg(state, 0x22, &tmp); + if (mxl_fail(ret)) + goto fail; + *val = (tmp >> (pin - 3)) & 0x01; + break; + default: + return -EINVAL; /* invalid pin */ + } +fail: + return ret; +} + +struct mxl_gpio_cfg { + u8 pin; + u8 dir; + u8 val; +}; + +static int mxl111sf_config_gpio_pins(struct mxl111sf_state *state, + struct mxl_gpio_cfg *gpio_cfg) +{ + int ret; + u8 tmp; + + mxl_debug_adv("(%d, %d)", gpio_cfg->pin, gpio_cfg->dir); + + switch (gpio_cfg->pin) { + case 0: + case 1: + case 2: + case 3: + ret = mxl111sf_read_reg(state, MXL_GPIO_MUX_REG_0, &tmp); + if (mxl_fail(ret)) + goto fail; + tmp &= ~(1 << (gpio_cfg->pin + 4)); + tmp |= (gpio_cfg->dir << (gpio_cfg->pin + 4)); + ret = mxl111sf_write_reg(state, MXL_GPIO_MUX_REG_0, tmp); + if (mxl_fail(ret)) + goto fail; + break; + case 4: + case 5: + case 6: + case 7: + ret = mxl111sf_read_reg(state, MXL_GPIO_MUX_REG_1, &tmp); + if (mxl_fail(ret)) + goto fail; + tmp &= ~(1 << gpio_cfg->pin); + tmp |= (gpio_cfg->dir << gpio_cfg->pin); + ret = mxl111sf_write_reg(state, MXL_GPIO_MUX_REG_1, tmp); + if (mxl_fail(ret)) + goto fail; + break; + case 8: + case 9: + case 10: + ret = mxl111sf_read_reg(state, MXL_GPIO_MUX_REG_2, &tmp); + if (mxl_fail(ret)) + goto fail; + tmp &= ~(1 << (gpio_cfg->pin - 3)); + tmp |= (gpio_cfg->dir << (gpio_cfg->pin - 3)); + ret = mxl111sf_write_reg(state, MXL_GPIO_MUX_REG_2, tmp); + if (mxl_fail(ret)) + goto fail; + break; + default: + return -EINVAL; /* invalid pin */ + } + + ret = (MXL_GPIO_DIR_OUTPUT == gpio_cfg->dir) ? + mxl111sf_set_gpo_state(state, + gpio_cfg->pin, gpio_cfg->val) : + mxl111sf_get_gpi_state(state, + gpio_cfg->pin, &gpio_cfg->val); + mxl_fail(ret); +fail: + return ret; +} + +static int mxl111sf_hw_do_set_gpio(struct mxl111sf_state *state, + int gpio, int direction, int val) +{ + struct mxl_gpio_cfg gpio_config = { + .pin = gpio, + .dir = direction, + .val = val, + }; + + mxl_debug("(%d, %d, %d)", gpio, direction, val); + + return mxl111sf_config_gpio_pins(state, &gpio_config); +} + +/* ------------------------------------------------------------------------- */ + +#define PIN_MUX_MPEG_MODE_MASK 0x40 /* 0x17 <6> */ +#define PIN_MUX_MPEG_PAR_EN_MASK 0x01 /* 0x18 <0> */ +#define PIN_MUX_MPEG_SER_EN_MASK 0x02 /* 0x18 <1> */ +#define PIN_MUX_MPG_IN_MUX_MASK 0x80 /* 0x3D <7> */ +#define PIN_MUX_BT656_ENABLE_MASK 0x04 /* 0x12 <2> */ +#define PIN_MUX_I2S_ENABLE_MASK 0x40 /* 0x15 <6> */ +#define PIN_MUX_SPI_MODE_MASK 0x10 /* 0x3D <4> */ +#define PIN_MUX_MCLK_EN_CTRL_MASK 0x10 /* 0x82 <4> */ +#define PIN_MUX_MPSYN_EN_CTRL_MASK 0x20 /* 0x82 <5> */ +#define PIN_MUX_MDVAL_EN_CTRL_MASK 0x40 /* 0x82 <6> */ +#define PIN_MUX_MPERR_EN_CTRL_MASK 0x80 /* 0x82 <7> */ +#define PIN_MUX_MDAT_EN_0_MASK 0x10 /* 0x84 <4> */ +#define PIN_MUX_MDAT_EN_1_MASK 0x20 /* 0x84 <5> */ +#define PIN_MUX_MDAT_EN_2_MASK 0x40 /* 0x84 <6> */ +#define PIN_MUX_MDAT_EN_3_MASK 0x80 /* 0x84 <7> */ +#define PIN_MUX_MDAT_EN_4_MASK 0x10 /* 0x89 <4> */ +#define PIN_MUX_MDAT_EN_5_MASK 0x20 /* 0x89 <5> */ +#define PIN_MUX_MDAT_EN_6_MASK 0x40 /* 0x89 <6> */ +#define PIN_MUX_MDAT_EN_7_MASK 0x80 /* 0x89 <7> */ + +int mxl111sf_config_pin_mux_modes(struct mxl111sf_state *state, + enum mxl111sf_mux_config pin_mux_config) +{ + u8 r12, r15, r17, r18, r3D, r82, r84, r89; + int ret; + + mxl_debug("(%d)", pin_mux_config); + + ret = mxl111sf_read_reg(state, 0x17, &r17); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_read_reg(state, 0x18, &r18); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_read_reg(state, 0x12, &r12); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_read_reg(state, 0x15, &r15); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_read_reg(state, 0x82, &r82); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_read_reg(state, 0x84, &r84); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_read_reg(state, 0x89, &r89); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_read_reg(state, 0x3D, &r3D); + if (mxl_fail(ret)) + goto fail; + + switch (pin_mux_config) { + case PIN_MUX_TS_OUT_PARALLEL: + /* mpeg_mode = 1 */ + r17 |= PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 1 */ + r18 |= PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 0 */ + r18 &= ~PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 0 */ + r15 &= ~PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 1 */ + r82 |= PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 1 */ + r82 |= PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 1 */ + r82 |= PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 1 */ + r82 |= PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0xF */ + r84 |= 0xF0; + /* mdat_en_ctrl[7:4] = 0xF */ + r89 |= 0xF0; + break; + case PIN_MUX_TS_OUT_SERIAL: + /* mpeg_mode = 1 */ + r17 |= PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 1 */ + r18 |= PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 0 */ + r15 &= ~PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 1 */ + r82 |= PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 1 */ + r82 |= PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 1 */ + r82 |= PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 1 */ + r82 |= PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0xF */ + r84 |= 0xF0; + /* mdat_en_ctrl[7:4] = 0xF */ + r89 |= 0xF0; + break; + case PIN_MUX_GPIO_MODE: + /* mpeg_mode = 0 */ + r17 &= ~PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 0 */ + r18 &= ~PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 0 */ + r15 &= ~PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + case PIN_MUX_TS_SERIAL_IN_MODE_0: + /* mpeg_mode = 0 */ + r17 &= ~PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 1 */ + r18 |= PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 0 */ + r15 &= ~PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + case PIN_MUX_TS_SERIAL_IN_MODE_1: + /* mpeg_mode = 0 */ + r17 &= ~PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 1 */ + r18 |= PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 1 */ + r3D |= PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 0 */ + r15 &= ~PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + case PIN_MUX_TS_SPI_IN_MODE_1: + /* mpeg_mode = 0 */ + r17 &= ~PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 1 */ + r18 |= PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 1 */ + r3D |= PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 1 */ + r15 |= PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 1 */ + r3D |= PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + case PIN_MUX_TS_SPI_IN_MODE_0: + /* mpeg_mode = 0 */ + r17 &= ~PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 1 */ + r18 |= PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 1 */ + r15 |= PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 1 */ + r3D |= PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + case PIN_MUX_TS_PARALLEL_IN: + /* mpeg_mode = 0 */ + r17 &= ~PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 1 */ + r18 |= PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 0 */ + r18 &= ~PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 0 */ + r15 &= ~PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + case PIN_MUX_BT656_I2S_MODE: + /* mpeg_mode = 0 */ + r17 &= ~PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 0 */ + r18 &= ~PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 1 */ + r12 |= PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 1 */ + r15 |= PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + case PIN_MUX_DEFAULT: + default: + /* mpeg_mode = 1 */ + r17 |= PIN_MUX_MPEG_MODE_MASK; + /* mpeg_par_en = 0 */ + r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK; + /* mpeg_ser_en = 0 */ + r18 &= ~PIN_MUX_MPEG_SER_EN_MASK; + /* mpg_in_mux = 0 */ + r3D &= ~PIN_MUX_MPG_IN_MUX_MASK; + /* bt656_enable = 0 */ + r12 &= ~PIN_MUX_BT656_ENABLE_MASK; + /* i2s_enable = 0 */ + r15 &= ~PIN_MUX_I2S_ENABLE_MASK; + /* spi_mode = 0 */ + r3D &= ~PIN_MUX_SPI_MODE_MASK; + /* mclk_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK; + /* mperr_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK; + /* mdval_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK; + /* mpsyn_en_ctrl = 0 */ + r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK; + /* mdat_en_ctrl[3:0] = 0x0 */ + r84 &= 0x0F; + /* mdat_en_ctrl[7:4] = 0x0 */ + r89 &= 0x0F; + break; + } + + ret = mxl111sf_write_reg(state, 0x17, r17); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x18, r18); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x12, r12); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x15, r15); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x82, r82); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x84, r84); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x89, r89); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x3D, r3D); + if (mxl_fail(ret)) + goto fail; +fail: + return ret; +} + +/* ------------------------------------------------------------------------- */ + +static int mxl111sf_hw_set_gpio(struct mxl111sf_state *state, int gpio, int val) +{ + return mxl111sf_hw_do_set_gpio(state, gpio, MXL_GPIO_DIR_OUTPUT, val); +} + +static int mxl111sf_hw_gpio_initialize(struct mxl111sf_state *state) +{ + u8 gpioval = 0x07; /* write protect enabled, signal LEDs off */ + int i, ret; + + mxl_debug("()"); + + for (i = 3; i < 8; i++) { + ret = mxl111sf_hw_set_gpio(state, i, (gpioval >> i) & 0x01); + if (mxl_fail(ret)) + break; + } + + return ret; +} + +#define PCA9534_I2C_ADDR (0x40 >> 1) +static int pca9534_set_gpio(struct mxl111sf_state *state, int gpio, int val) +{ + u8 w[2] = { 1, 0 }; + u8 r = 0; + struct i2c_msg msg[] = { + { .addr = PCA9534_I2C_ADDR, + .flags = 0, .buf = w, .len = 1 }, + { .addr = PCA9534_I2C_ADDR, + .flags = I2C_M_RD, .buf = &r, .len = 1 }, + }; + + mxl_debug("(%d, %d)", gpio, val); + + /* read current GPIO levels from flip-flop */ + i2c_transfer(&state->d->i2c_adap, msg, 2); + + /* prepare write buffer with current GPIO levels */ + msg[0].len = 2; +#if 0 + w[0] = 1; +#endif + w[1] = r; + + /* clear the desired GPIO */ + w[1] &= ~(1 << gpio); + + /* set the desired GPIO value */ + w[1] |= ((val ? 1 : 0) << gpio); + + /* write new GPIO levels to flip-flop */ + i2c_transfer(&state->d->i2c_adap, &msg[0], 1); + + return 0; +} + +static int pca9534_init_port_expander(struct mxl111sf_state *state) +{ + u8 w[2] = { 1, 0x07 }; /* write protect enabled, signal LEDs off */ + + struct i2c_msg msg = { + .addr = PCA9534_I2C_ADDR, + .flags = 0, .buf = w, .len = 2 + }; + + mxl_debug("()"); + + i2c_transfer(&state->d->i2c_adap, &msg, 1); + + /* configure all pins as outputs */ + w[0] = 3; + w[1] = 0; + + i2c_transfer(&state->d->i2c_adap, &msg, 1); + + return 0; +} + +int mxl111sf_set_gpio(struct mxl111sf_state *state, int gpio, int val) +{ + mxl_debug("(%d, %d)", gpio, val); + + switch (state->gpio_port_expander) { + default: + mxl_printk(KERN_ERR, + "gpio_port_expander undefined, assuming PCA9534"); + /* fall-thru */ + case mxl111sf_PCA9534: + return pca9534_set_gpio(state, gpio, val); + case mxl111sf_gpio_hw: + return mxl111sf_hw_set_gpio(state, gpio, val); + } +} + +static int mxl111sf_probe_port_expander(struct mxl111sf_state *state) +{ + int ret; + u8 w = 1; + u8 r = 0; + struct i2c_msg msg[] = { + { .flags = 0, .buf = &w, .len = 1 }, + { .flags = I2C_M_RD, .buf = &r, .len = 1 }, + }; + + mxl_debug("()"); + + msg[0].addr = 0x70 >> 1; + msg[1].addr = 0x70 >> 1; + + /* read current GPIO levels from flip-flop */ + ret = i2c_transfer(&state->d->i2c_adap, msg, 2); + if (ret == 2) { + state->port_expander_addr = msg[0].addr; + state->gpio_port_expander = mxl111sf_PCA9534; + mxl_debug("found port expander at 0x%02x", + state->port_expander_addr); + return 0; + } + + msg[0].addr = 0x40 >> 1; + msg[1].addr = 0x40 >> 1; + + ret = i2c_transfer(&state->d->i2c_adap, msg, 2); + if (ret == 2) { + state->port_expander_addr = msg[0].addr; + state->gpio_port_expander = mxl111sf_PCA9534; + mxl_debug("found port expander at 0x%02x", + state->port_expander_addr); + return 0; + } + state->port_expander_addr = 0xff; + state->gpio_port_expander = mxl111sf_gpio_hw; + mxl_debug("using hardware gpio"); + return 0; +} + +int mxl111sf_init_port_expander(struct mxl111sf_state *state) +{ + mxl_debug("()"); + + if (0x00 == state->port_expander_addr) + mxl111sf_probe_port_expander(state); + + switch (state->gpio_port_expander) { + default: + mxl_printk(KERN_ERR, + "gpio_port_expander undefined, assuming PCA9534"); + /* fall-thru */ + case mxl111sf_PCA9534: + return pca9534_init_port_expander(state); + case mxl111sf_gpio_hw: + return mxl111sf_hw_gpio_initialize(state); + } +} + +/* ------------------------------------------------------------------------ */ + +int mxl111sf_gpio_mode_switch(struct mxl111sf_state *state, unsigned int mode) +{ +/* GPO: + * 3 - ATSC/MH# | 1 = ATSC transport, 0 = MH transport | default 0 + * 4 - ATSC_RST## | 1 = ATSC enable, 0 = ATSC Reset | default 0 + * 5 - ATSC_EN | 1 = ATSC power enable, 0 = ATSC power off | default 0 + * 6 - MH_RESET# | 1 = MH enable, 0 = MH Reset | default 0 + * 7 - MH_EN | 1 = MH power enable, 0 = MH power off | default 0 + */ + mxl_debug("(%d)", mode); + + switch (mode) { + case MXL111SF_GPIO_MOD_MH: + mxl111sf_set_gpio(state, 4, 0); + mxl111sf_set_gpio(state, 5, 0); + msleep(50); + mxl111sf_set_gpio(state, 7, 1); + msleep(50); + mxl111sf_set_gpio(state, 6, 1); + msleep(50); + + mxl111sf_set_gpio(state, 3, 0); + break; + case MXL111SF_GPIO_MOD_ATSC: + mxl111sf_set_gpio(state, 6, 0); + mxl111sf_set_gpio(state, 7, 0); + msleep(50); + mxl111sf_set_gpio(state, 5, 1); + msleep(50); + mxl111sf_set_gpio(state, 4, 1); + msleep(50); + mxl111sf_set_gpio(state, 3, 1); + break; + default: /* DVBT / STANDBY */ + mxl111sf_init_port_expander(state); + break; + } + return 0; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.h new file mode 100644 index 000000000000..0220f54299a5 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.h @@ -0,0 +1,56 @@ +/* + * mxl111sf-gpio.h - driver for the MaxLinear MXL111SF + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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. + */ + +#ifndef _DVB_USB_MXL111SF_GPIO_H_ +#define _DVB_USB_MXL111SF_GPIO_H_ + +#include "mxl111sf.h" + +int mxl111sf_set_gpio(struct mxl111sf_state *state, int gpio, int val); +int mxl111sf_init_port_expander(struct mxl111sf_state *state); + +#define MXL111SF_GPIO_MOD_DVBT 0 +#define MXL111SF_GPIO_MOD_MH 1 +#define MXL111SF_GPIO_MOD_ATSC 2 +int mxl111sf_gpio_mode_switch(struct mxl111sf_state *state, unsigned int mode); + +enum mxl111sf_mux_config { + PIN_MUX_DEFAULT = 0, + PIN_MUX_TS_OUT_PARALLEL, + PIN_MUX_TS_OUT_SERIAL, + PIN_MUX_GPIO_MODE, + PIN_MUX_TS_SERIAL_IN_MODE_0, + PIN_MUX_TS_SERIAL_IN_MODE_1, + PIN_MUX_TS_SPI_IN_MODE_0, + PIN_MUX_TS_SPI_IN_MODE_1, + PIN_MUX_TS_PARALLEL_IN, + PIN_MUX_BT656_I2S_MODE, +}; + +int mxl111sf_config_pin_mux_modes(struct mxl111sf_state *state, + enum mxl111sf_mux_config pin_mux_config); + +#endif /* _DVB_USB_MXL111SF_GPIO_H_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c new file mode 100644 index 000000000000..34434557ef65 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c @@ -0,0 +1,850 @@ +/* + * mxl111sf-i2c.c - driver for the MaxLinear MXL111SF + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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 "mxl111sf-i2c.h" +#include "mxl111sf.h" + +/* SW-I2C ----------------------------------------------------------------- */ + +#define SW_I2C_ADDR 0x1a +#define SW_I2C_EN 0x02 +#define SW_SCL_OUT 0x04 +#define SW_SDA_OUT 0x08 +#define SW_SDA_IN 0x04 + +#define SW_I2C_BUSY_ADDR 0x2f +#define SW_I2C_BUSY 0x02 + +static int mxl111sf_i2c_bitbang_sendbyte(struct mxl111sf_state *state, + u8 byte) +{ + int i, ret; + u8 data = 0; + + mxl_i2c("(0x%02x)", byte); + + ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &data); + if (mxl_fail(ret)) + goto fail; + + for (i = 0; i < 8; i++) { + + data = (byte & (0x80 >> i)) ? SW_SDA_OUT : 0; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | data); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | data | SW_SCL_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | data); + if (mxl_fail(ret)) + goto fail; + } + + /* last bit was 0 so we need to release SDA */ + if (!(byte & 1)) { + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + } + + /* CLK high for ACK readback */ + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &data); + if (mxl_fail(ret)) + goto fail; + + /* drop the CLK after getting ACK, SDA will go high right away */ + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + + if (data & SW_SDA_IN) + ret = -EIO; +fail: + return ret; +} + +static int mxl111sf_i2c_bitbang_recvbyte(struct mxl111sf_state *state, + u8 *pbyte) +{ + int i, ret; + u8 byte = 0; + u8 data = 0; + + mxl_i2c("()"); + + *pbyte = 0; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + + for (i = 0; i < 8; i++) { + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | + SW_SCL_OUT | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &data); + if (mxl_fail(ret)) + goto fail; + + if (data & SW_SDA_IN) + byte |= (0x80 >> i); + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + } + *pbyte = byte; +fail: + return ret; +} + +static int mxl111sf_i2c_start(struct mxl111sf_state *state) +{ + int ret; + + mxl_i2c("()"); + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SCL_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN); /* start */ + mxl_fail(ret); +fail: + return ret; +} + +static int mxl111sf_i2c_stop(struct mxl111sf_state *state) +{ + int ret; + + mxl_i2c("()"); + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN); /* stop */ + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SCL_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_SCL_OUT | SW_SDA_OUT); + mxl_fail(ret); +fail: + return ret; +} + +static int mxl111sf_i2c_ack(struct mxl111sf_state *state) +{ + int ret; + u8 b = 0; + + mxl_i2c("()"); + + ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &b); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN); + if (mxl_fail(ret)) + goto fail; + + /* pull SDA low */ + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SCL_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SDA_OUT); + mxl_fail(ret); +fail: + return ret; +} + +static int mxl111sf_i2c_nack(struct mxl111sf_state *state) +{ + int ret; + + mxl_i2c("()"); + + /* SDA high to signal last byte read from slave */ + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, SW_I2C_ADDR, + 0x10 | SW_I2C_EN | SW_SDA_OUT); + mxl_fail(ret); +fail: + return ret; +} + +/* ------------------------------------------------------------------------ */ + +static int mxl111sf_i2c_sw_xfer_msg(struct mxl111sf_state *state, + struct i2c_msg *msg) +{ + int i, ret; + + mxl_i2c("()"); + + if (msg->flags & I2C_M_RD) { + + ret = mxl111sf_i2c_start(state); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_i2c_bitbang_sendbyte(state, + (msg->addr << 1) | 0x01); + if (mxl_fail(ret)) { + mxl111sf_i2c_stop(state); + goto fail; + } + + for (i = 0; i < msg->len; i++) { + ret = mxl111sf_i2c_bitbang_recvbyte(state, + &msg->buf[i]); + if (mxl_fail(ret)) { + mxl111sf_i2c_stop(state); + goto fail; + } + + if (i < msg->len - 1) + mxl111sf_i2c_ack(state); + } + + mxl111sf_i2c_nack(state); + + ret = mxl111sf_i2c_stop(state); + if (mxl_fail(ret)) + goto fail; + + } else { + + ret = mxl111sf_i2c_start(state); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_i2c_bitbang_sendbyte(state, + (msg->addr << 1) & 0xfe); + if (mxl_fail(ret)) { + mxl111sf_i2c_stop(state); + goto fail; + } + + for (i = 0; i < msg->len; i++) { + ret = mxl111sf_i2c_bitbang_sendbyte(state, + msg->buf[i]); + if (mxl_fail(ret)) { + mxl111sf_i2c_stop(state); + goto fail; + } + } + + /* FIXME: we only want to do this on the last transaction */ + mxl111sf_i2c_stop(state); + } +fail: + return ret; +} + +/* HW-I2C ----------------------------------------------------------------- */ + +#define USB_WRITE_I2C_CMD 0x99 +#define USB_READ_I2C_CMD 0xdd +#define USB_END_I2C_CMD 0xfe + +#define USB_WRITE_I2C_CMD_LEN 26 +#define USB_READ_I2C_CMD_LEN 24 + +#define I2C_MUX_REG 0x30 +#define I2C_CONTROL_REG 0x00 +#define I2C_SLAVE_ADDR_REG 0x08 +#define I2C_DATA_REG 0x0c +#define I2C_INT_STATUS_REG 0x10 + +static int mxl111sf_i2c_send_data(struct mxl111sf_state *state, + u8 index, u8 *wdata) +{ + int ret = mxl111sf_ctrl_msg(state->d, wdata[0], + &wdata[1], 25, NULL, 0); + mxl_fail(ret); + + return ret; +} + +static int mxl111sf_i2c_get_data(struct mxl111sf_state *state, + u8 index, u8 *wdata, u8 *rdata) +{ + int ret = mxl111sf_ctrl_msg(state->d, wdata[0], + &wdata[1], 25, rdata, 24); + mxl_fail(ret); + + return ret; +} + +static u8 mxl111sf_i2c_check_status(struct mxl111sf_state *state) +{ + u8 status = 0; + u8 buf[26]; + + mxl_i2c_adv("()"); + + buf[0] = USB_READ_I2C_CMD; + buf[1] = 0x00; + + buf[2] = I2C_INT_STATUS_REG; + buf[3] = 0x00; + buf[4] = 0x00; + + buf[5] = USB_END_I2C_CMD; + + mxl111sf_i2c_get_data(state, 0, buf, buf); + + if (buf[1] & 0x04) + status = 1; + + return status; +} + +static u8 mxl111sf_i2c_check_fifo(struct mxl111sf_state *state) +{ + u8 status = 0; + u8 buf[26]; + + mxl_i2c("()"); + + buf[0] = USB_READ_I2C_CMD; + buf[1] = 0x00; + + buf[2] = I2C_MUX_REG; + buf[3] = 0x00; + buf[4] = 0x00; + + buf[5] = I2C_INT_STATUS_REG; + buf[6] = 0x00; + buf[7] = 0x00; + buf[8] = USB_END_I2C_CMD; + + mxl111sf_i2c_get_data(state, 0, buf, buf); + + if (0x08 == (buf[1] & 0x08)) + status = 1; + + if ((buf[5] & 0x02) == 0x02) + mxl_i2c("(buf[5] & 0x02) == 0x02"); /* FIXME */ + + return status; +} + +static int mxl111sf_i2c_readagain(struct mxl111sf_state *state, + u8 count, u8 *rbuf) +{ + u8 i2c_w_data[26]; + u8 i2c_r_data[24]; + u8 i = 0; + u8 fifo_status = 0; + int status = 0; + + mxl_i2c("read %d bytes", count); + + while ((fifo_status == 0) && (i++ < 5)) + fifo_status = mxl111sf_i2c_check_fifo(state); + + i2c_w_data[0] = 0xDD; + i2c_w_data[1] = 0x00; + + for (i = 2; i < 26; i++) + i2c_w_data[i] = 0xFE; + + for (i = 0; i < count; i++) { + i2c_w_data[2+(i*3)] = 0x0C; + i2c_w_data[3+(i*3)] = 0x00; + i2c_w_data[4+(i*3)] = 0x00; + } + + mxl111sf_i2c_get_data(state, 0, i2c_w_data, i2c_r_data); + + /* Check for I2C NACK status */ + if (mxl111sf_i2c_check_status(state) == 1) { + mxl_i2c("error!"); + } else { + for (i = 0; i < count; i++) { + rbuf[i] = i2c_r_data[(i*3)+1]; + mxl_i2c("%02x\t %02x", + i2c_r_data[(i*3)+1], + i2c_r_data[(i*3)+2]); + } + + status = 1; + } + + return status; +} + +#define HWI2C400 1 +static int mxl111sf_i2c_hw_xfer_msg(struct mxl111sf_state *state, + struct i2c_msg *msg) +{ + int i, k, ret = 0; + u16 index = 0; + u8 buf[26]; + u8 i2c_r_data[24]; + u16 block_len; + u16 left_over_len; + u8 rd_status[8]; + u8 ret_status; + u8 readbuff[26]; + + mxl_i2c("addr: 0x%02x, read buff len: %d, write buff len: %d", + msg->addr, (msg->flags & I2C_M_RD) ? msg->len : 0, + (!(msg->flags & I2C_M_RD)) ? msg->len : 0); + + for (index = 0; index < 26; index++) + buf[index] = USB_END_I2C_CMD; + + /* command to indicate data payload is destined for I2C interface */ + buf[0] = USB_WRITE_I2C_CMD; + buf[1] = 0x00; + + /* enable I2C interface */ + buf[2] = I2C_MUX_REG; + buf[3] = 0x80; + buf[4] = 0x00; + + /* enable I2C interface */ + buf[5] = I2C_MUX_REG; + buf[6] = 0x81; + buf[7] = 0x00; + + /* set Timeout register on I2C interface */ + buf[8] = 0x14; + buf[9] = 0xff; + buf[10] = 0x00; +#if 0 + /* enable Interrupts on I2C interface */ + buf[8] = 0x24; + buf[9] = 0xF7; + buf[10] = 0x00; +#endif + buf[11] = 0x24; + buf[12] = 0xF7; + buf[13] = 0x00; + + ret = mxl111sf_i2c_send_data(state, 0, buf); + + /* write data on I2C bus */ + if (!(msg->flags & I2C_M_RD) && (msg->len > 0)) { + mxl_i2c("%d\t%02x", msg->len, msg->buf[0]); + + /* control register on I2C interface to initialize I2C bus */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0x5E; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + + /* I2C Slave device Address */ + buf[5] = I2C_SLAVE_ADDR_REG; + buf[6] = (msg->addr); + buf[7] = 0x00; + buf[8] = USB_END_I2C_CMD; + ret = mxl111sf_i2c_send_data(state, 0, buf); + + /* check for slave device status */ + if (mxl111sf_i2c_check_status(state) == 1) { + mxl_i2c("NACK writing slave address %02x", + msg->addr); + /* if NACK, stop I2C bus and exit */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0x4E; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + ret = -EIO; + goto exit; + } + + /* I2C interface can do I2C operations in block of 8 bytes of + I2C data. calculation to figure out number of blocks of i2c + data required to program */ + block_len = (msg->len / 8); + left_over_len = (msg->len % 8); + index = 0; + + mxl_i2c("block_len %d, left_over_len %d", + block_len, left_over_len); + + for (index = 0; index < block_len; index++) { + for (i = 0; i < 8; i++) { + /* write data on I2C interface */ + buf[2+(i*3)] = I2C_DATA_REG; + buf[3+(i*3)] = msg->buf[(index*8)+i]; + buf[4+(i*3)] = 0x00; + } + + ret = mxl111sf_i2c_send_data(state, 0, buf); + + /* check for I2C NACK status */ + if (mxl111sf_i2c_check_status(state) == 1) { + mxl_i2c("NACK writing slave address %02x", + msg->addr); + + /* if NACK, stop I2C bus and exit */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0x4E; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + ret = -EIO; + goto exit; + } + + } + + if (left_over_len) { + for (k = 0; k < 26; k++) + buf[k] = USB_END_I2C_CMD; + + buf[0] = 0x99; + buf[1] = 0x00; + + for (i = 0; i < left_over_len; i++) { + buf[2+(i*3)] = I2C_DATA_REG; + buf[3+(i*3)] = msg->buf[(index*8)+i]; + mxl_i2c("index = %d %d data %d", + index, i, msg->buf[(index*8)+i]); + buf[4+(i*3)] = 0x00; + } + ret = mxl111sf_i2c_send_data(state, 0, buf); + + /* check for I2C NACK status */ + if (mxl111sf_i2c_check_status(state) == 1) { + mxl_i2c("NACK writing slave address %02x", + msg->addr); + + /* if NACK, stop I2C bus and exit */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0x4E; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + ret = -EIO; + goto exit; + } + + } + + /* issue I2C STOP after write */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0x4E; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + + } + + /* read data from I2C bus */ + if ((msg->flags & I2C_M_RD) && (msg->len > 0)) { + mxl_i2c("read buf len %d", msg->len); + + /* command to indicate data payload is + destined for I2C interface */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0xDF; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + + /* I2C xfer length */ + buf[5] = 0x14; + buf[6] = (msg->len & 0xFF); + buf[7] = 0; + + /* I2C slave device Address */ + buf[8] = I2C_SLAVE_ADDR_REG; + buf[9] = msg->addr; + buf[10] = 0x00; + buf[11] = USB_END_I2C_CMD; + ret = mxl111sf_i2c_send_data(state, 0, buf); + + /* check for I2C NACK status */ + if (mxl111sf_i2c_check_status(state) == 1) { + mxl_i2c("NACK reading slave address %02x", + msg->addr); + + /* if NACK, stop I2C bus and exit */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0xC7; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + ret = -EIO; + goto exit; + } + + /* I2C interface can do I2C operations in block of 8 bytes of + I2C data. calculation to figure out number of blocks of + i2c data required to program */ + block_len = ((msg->len) / 8); + left_over_len = ((msg->len) % 8); + index = 0; + + mxl_i2c("block_len %d, left_over_len %d", + block_len, left_over_len); + + /* command to read data from I2C interface */ + buf[0] = USB_READ_I2C_CMD; + buf[1] = 0x00; + + for (index = 0; index < block_len; index++) { + /* setup I2C read request packet on I2C interface */ + for (i = 0; i < 8; i++) { + buf[2+(i*3)] = I2C_DATA_REG; + buf[3+(i*3)] = 0x00; + buf[4+(i*3)] = 0x00; + } + + ret = mxl111sf_i2c_get_data(state, 0, buf, i2c_r_data); + + /* check for I2C NACK status */ + if (mxl111sf_i2c_check_status(state) == 1) { + mxl_i2c("NACK reading slave address %02x", + msg->addr); + + /* if NACK, stop I2C bus and exit */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0xC7; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + ret = -EIO; + goto exit; + } + + /* copy data from i2c data payload to read buffer */ + for (i = 0; i < 8; i++) { + rd_status[i] = i2c_r_data[(i*3)+2]; + + if (rd_status[i] == 0x04) { + if (i < 7) { + mxl_i2c("i2c fifo empty!" + " @ %d", i); + msg->buf[(index*8)+i] = + i2c_r_data[(i*3)+1]; + /* read again */ + ret_status = + mxl111sf_i2c_readagain( + state, 8-(i+1), + readbuff); + if (ret_status == 1) { + for (k = 0; + k < 8-(i+1); + k++) { + + msg->buf[(index*8)+(k+i+1)] = + readbuff[k]; + mxl_i2c("read data: %02x\t %02x", + msg->buf[(index*8)+(k+i)], + (index*8)+(k+i)); + mxl_i2c("read data: %02x\t %02x", + msg->buf[(index*8)+(k+i+1)], + readbuff[k]); + + } + goto stop_copy; + } else { + mxl_i2c("readagain " + "ERROR!"); + } + } else { + msg->buf[(index*8)+i] = + i2c_r_data[(i*3)+1]; + } + } else { + msg->buf[(index*8)+i] = + i2c_r_data[(i*3)+1]; + } + } +stop_copy: + ; + + } + + if (left_over_len) { + for (k = 0; k < 26; k++) + buf[k] = USB_END_I2C_CMD; + + buf[0] = 0xDD; + buf[1] = 0x00; + + for (i = 0; i < left_over_len; i++) { + buf[2+(i*3)] = I2C_DATA_REG; + buf[3+(i*3)] = 0x00; + buf[4+(i*3)] = 0x00; + } + ret = mxl111sf_i2c_get_data(state, 0, buf, + i2c_r_data); + + /* check for I2C NACK status */ + if (mxl111sf_i2c_check_status(state) == 1) { + mxl_i2c("NACK reading slave address %02x", + msg->addr); + + /* if NACK, stop I2C bus and exit */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0xC7; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + ret = -EIO; + goto exit; + } + + for (i = 0; i < left_over_len; i++) { + msg->buf[(block_len*8)+i] = + i2c_r_data[(i*3)+1]; + mxl_i2c("read data: %02x\t %02x", + i2c_r_data[(i*3)+1], + i2c_r_data[(i*3)+2]); + } + } + + /* indicate I2C interface to issue NACK + after next I2C read op */ + buf[0] = USB_WRITE_I2C_CMD; + buf[1] = 0x00; + + /* control register */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0x17; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + + buf[5] = USB_END_I2C_CMD; + ret = mxl111sf_i2c_send_data(state, 0, buf); + + /* control register */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0xC7; + buf[4] = (HWI2C400) ? 0x03 : 0x0D; + + } +exit: + /* STOP and disable I2C MUX */ + buf[0] = USB_WRITE_I2C_CMD; + buf[1] = 0x00; + + /* de-initilize I2C BUS */ + buf[5] = USB_END_I2C_CMD; + mxl111sf_i2c_send_data(state, 0, buf); + + /* Control Register */ + buf[2] = I2C_CONTROL_REG; + buf[3] = 0xDF; + buf[4] = 0x03; + + /* disable I2C interface */ + buf[5] = I2C_MUX_REG; + buf[6] = 0x00; + buf[7] = 0x00; + + /* de-initilize I2C BUS */ + buf[8] = USB_END_I2C_CMD; + mxl111sf_i2c_send_data(state, 0, buf); + + /* disable I2C interface */ + buf[2] = I2C_MUX_REG; + buf[3] = 0x81; + buf[4] = 0x00; + + /* disable I2C interface */ + buf[5] = I2C_MUX_REG; + buf[6] = 0x00; + buf[7] = 0x00; + + /* disable I2C interface */ + buf[8] = I2C_MUX_REG; + buf[9] = 0x00; + buf[10] = 0x00; + + buf[11] = USB_END_I2C_CMD; + mxl111sf_i2c_send_data(state, 0, buf); + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +int mxl111sf_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg msg[], int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct mxl111sf_state *state = d->priv; + int hwi2c = (state->chip_rev > MXL111SF_V6); + int i, ret; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + for (i = 0; i < num; i++) { + ret = (hwi2c) ? + mxl111sf_i2c_hw_xfer_msg(state, &msg[i]) : + mxl111sf_i2c_sw_xfer_msg(state, &msg[i]); + if (mxl_fail(ret)) { + mxl_debug_adv("failed with error %d on i2c " + "transaction %d of %d, %sing %d bytes " + "to/from 0x%02x", ret, i+1, num, + (msg[i].flags & I2C_M_RD) ? + "read" : "writ", + msg[i].len, msg[i].addr); + + break; + } + } + + mutex_unlock(&d->i2c_mutex); + + return i == num ? num : -EREMOTEIO; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.h new file mode 100644 index 000000000000..a57a45ffb9e4 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.h @@ -0,0 +1,35 @@ +/* + * mxl111sf-i2c.h - driver for the MaxLinear MXL111SF + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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. + */ + +#ifndef _DVB_USB_MXL111SF_I2C_H_ +#define _DVB_USB_MXL111SF_I2C_H_ + +#include <linux/i2c.h> + +int mxl111sf_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg msg[], int num); + +#endif /* _DVB_USB_MXL111SF_I2C_H_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.c new file mode 100644 index 000000000000..b741b3a7a325 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.c @@ -0,0 +1,343 @@ +/* + * mxl111sf-phy.c - driver for the MaxLinear MXL111SF + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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 "mxl111sf-phy.h" +#include "mxl111sf-reg.h" + +int mxl111sf_init_tuner_demod(struct mxl111sf_state *state) +{ + struct mxl111sf_reg_ctrl_info mxl_111_overwrite_default[] = { + {0x07, 0xff, 0x0c}, + {0x58, 0xff, 0x9d}, + {0x09, 0xff, 0x00}, + {0x06, 0xff, 0x06}, + {0xc8, 0xff, 0x40}, /* ED_LE_WIN_OLD = 0 */ + {0x8d, 0x01, 0x01}, /* NEGATE_Q */ + {0x32, 0xff, 0xac}, /* DIG_RFREFSELECT = 12 */ + {0x42, 0xff, 0x43}, /* DIG_REG_AMP = 4 */ + {0x74, 0xff, 0xc4}, /* SSPUR_FS_PRIO = 4 */ + {0x71, 0xff, 0xe6}, /* SPUR_ROT_PRIO_VAL = 1 */ + {0x83, 0xff, 0x64}, /* INF_FILT1_THD_SC = 100 */ + {0x85, 0xff, 0x64}, /* INF_FILT2_THD_SC = 100 */ + {0x88, 0xff, 0xf0}, /* INF_THD = 240 */ + {0x6f, 0xf0, 0xb0}, /* DFE_DLY = 11 */ + {0x00, 0xff, 0x01}, /* Change to page 1 */ + {0x81, 0xff, 0x11}, /* DSM_FERR_BYPASS = 1 */ + {0xf4, 0xff, 0x07}, /* DIG_FREQ_CORR = 1 */ + {0xd4, 0x1f, 0x0f}, /* SPUR_TEST_NOISE_TH = 15 */ + {0xd6, 0xff, 0x0c}, /* SPUR_TEST_NOISE_PAPR = 12 */ + {0x00, 0xff, 0x00}, /* Change to page 0 */ + {0, 0, 0} + }; + + mxl_debug("()"); + + return mxl111sf_ctrl_program_regs(state, mxl_111_overwrite_default); +} + +int mxl1x1sf_soft_reset(struct mxl111sf_state *state) +{ + int ret; + mxl_debug("()"); + + ret = mxl111sf_write_reg(state, 0xff, 0x00); /* AIC */ + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_write_reg(state, 0x02, 0x01); /* get out of reset */ + mxl_fail(ret); +fail: + return ret; +} + +int mxl1x1sf_set_device_mode(struct mxl111sf_state *state, int mode) +{ + int ret; + + mxl_debug("(%s)", MXL_SOC_MODE == mode ? + "MXL_SOC_MODE" : "MXL_TUNER_MODE"); + + /* set device mode */ + ret = mxl111sf_write_reg(state, 0x03, + MXL_SOC_MODE == mode ? 0x01 : 0x00); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg_mask(state, + 0x7d, 0x40, MXL_SOC_MODE == mode ? + 0x00 : /* enable impulse noise filter, + INF_BYP = 0 */ + 0x40); /* disable impulse noise filter, + INF_BYP = 1 */ + if (mxl_fail(ret)) + goto fail; + + state->device_mode = mode; +fail: + return ret; +} + +/* power up tuner */ +int mxl1x1sf_top_master_ctrl(struct mxl111sf_state *state, int onoff) +{ + mxl_debug("(%d)", onoff); + + return mxl111sf_write_reg(state, 0x01, onoff ? 0x01 : 0x00); +} + +int mxl111sf_disable_656_port(struct mxl111sf_state *state) +{ + mxl_debug("()"); + + return mxl111sf_write_reg_mask(state, 0x12, 0x04, 0x00); +} + +int mxl111sf_enable_usb_output(struct mxl111sf_state *state) +{ + mxl_debug("()"); + + return mxl111sf_write_reg_mask(state, 0x17, 0x40, 0x00); +} + +/* initialize TSIF as input port of MxL1X1SF for MPEG2 data transfer */ +int mxl111sf_config_mpeg_in(struct mxl111sf_state *state, + unsigned int parallel_serial, + unsigned int msb_lsb_1st, + unsigned int clock_phase, + unsigned int mpeg_valid_pol, + unsigned int mpeg_sync_pol) +{ + int ret; + u8 mode, tmp; + + mxl_debug("(%u,%u,%u,%u,%u)", parallel_serial, msb_lsb_1st, + clock_phase, mpeg_valid_pol, mpeg_sync_pol); + + /* Enable PIN MUX */ + ret = mxl111sf_write_reg(state, V6_PIN_MUX_MODE_REG, V6_ENABLE_PIN_MUX); + mxl_fail(ret); + + /* Configure MPEG Clock phase */ + mxl111sf_read_reg(state, V6_MPEG_IN_CLK_INV_REG, &mode); + + if (clock_phase == TSIF_NORMAL) + mode &= ~V6_INVERTED_CLK_PHASE; + else + mode |= V6_INVERTED_CLK_PHASE; + + ret = mxl111sf_write_reg(state, V6_MPEG_IN_CLK_INV_REG, mode); + mxl_fail(ret); + + /* Configure data input mode, MPEG Valid polarity, MPEG Sync polarity + * Get current configuration */ + ret = mxl111sf_read_reg(state, V6_MPEG_IN_CTRL_REG, &mode); + mxl_fail(ret); + + /* Data Input mode */ + if (parallel_serial == TSIF_INPUT_PARALLEL) { + /* Disable serial mode */ + mode &= ~V6_MPEG_IN_DATA_SERIAL; + + /* Enable Parallel mode */ + mode |= V6_MPEG_IN_DATA_PARALLEL; + } else { + /* Disable Parallel mode */ + mode &= ~V6_MPEG_IN_DATA_PARALLEL; + + /* Enable Serial Mode */ + mode |= V6_MPEG_IN_DATA_SERIAL; + + /* If serial interface is chosen, configure + MSB or LSB order in transmission */ + ret = mxl111sf_read_reg(state, + V6_MPEG_INOUT_BIT_ORDER_CTRL_REG, + &tmp); + mxl_fail(ret); + + if (msb_lsb_1st == MPEG_SER_MSB_FIRST_ENABLED) + tmp |= V6_MPEG_SER_MSB_FIRST; + else + tmp &= ~V6_MPEG_SER_MSB_FIRST; + + ret = mxl111sf_write_reg(state, + V6_MPEG_INOUT_BIT_ORDER_CTRL_REG, + tmp); + mxl_fail(ret); + } + + /* MPEG Sync polarity */ + if (mpeg_sync_pol == TSIF_NORMAL) + mode &= ~V6_INVERTED_MPEG_SYNC; + else + mode |= V6_INVERTED_MPEG_SYNC; + + /* MPEG Valid polarity */ + if (mpeg_valid_pol == 0) + mode &= ~V6_INVERTED_MPEG_VALID; + else + mode |= V6_INVERTED_MPEG_VALID; + + ret = mxl111sf_write_reg(state, V6_MPEG_IN_CTRL_REG, mode); + mxl_fail(ret); + + return ret; +} + +int mxl111sf_init_i2s_port(struct mxl111sf_state *state, u8 sample_size) +{ + static struct mxl111sf_reg_ctrl_info init_i2s[] = { + {0x1b, 0xff, 0x1e}, /* pin mux mode, Choose 656/I2S input */ + {0x15, 0x60, 0x60}, /* Enable I2S */ + {0x17, 0xe0, 0x20}, /* Input, MPEG MODE USB, + Inverted 656 Clock, I2S_SOFT_RESET, + 0 : Normal operation, 1 : Reset State */ +#if 0 + {0x12, 0x01, 0x00}, /* AUDIO_IRQ_CLR (Overflow Indicator) */ +#endif + {0x00, 0xff, 0x02}, /* Change to Control Page */ + {0x26, 0x0d, 0x0d}, /* I2S_MODE & BT656_SRC_SEL for FPGA only */ + {0x00, 0xff, 0x00}, + {0, 0, 0} + }; + int ret; + + mxl_debug("(0x%02x)", sample_size); + + ret = mxl111sf_ctrl_program_regs(state, init_i2s); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, V6_I2S_NUM_SAMPLES_REG, sample_size); + mxl_fail(ret); +fail: + return ret; +} + +int mxl111sf_disable_i2s_port(struct mxl111sf_state *state) +{ + static struct mxl111sf_reg_ctrl_info disable_i2s[] = { + {0x15, 0x40, 0x00}, + {0, 0, 0} + }; + + mxl_debug("()"); + + return mxl111sf_ctrl_program_regs(state, disable_i2s); +} + +int mxl111sf_config_i2s(struct mxl111sf_state *state, + u8 msb_start_pos, u8 data_width) +{ + int ret; + u8 tmp; + + mxl_debug("(0x%02x, 0x%02x)", msb_start_pos, data_width); + + ret = mxl111sf_read_reg(state, V6_I2S_STREAM_START_BIT_REG, &tmp); + if (mxl_fail(ret)) + goto fail; + + tmp &= 0xe0; + tmp |= msb_start_pos; + ret = mxl111sf_write_reg(state, V6_I2S_STREAM_START_BIT_REG, tmp); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_read_reg(state, V6_I2S_STREAM_END_BIT_REG, &tmp); + if (mxl_fail(ret)) + goto fail; + + tmp &= 0xe0; + tmp |= data_width; + ret = mxl111sf_write_reg(state, V6_I2S_STREAM_END_BIT_REG, tmp); + mxl_fail(ret); +fail: + return ret; +} + +int mxl111sf_config_spi(struct mxl111sf_state *state, int onoff) +{ + u8 val; + int ret; + + mxl_debug("(%d)", onoff); + + ret = mxl111sf_write_reg(state, 0x00, 0x02); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_read_reg(state, V8_SPI_MODE_REG, &val); + if (mxl_fail(ret)) + goto fail; + + if (onoff) + val |= 0x04; + else + val &= ~0x04; + + ret = mxl111sf_write_reg(state, V8_SPI_MODE_REG, val); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_write_reg(state, 0x00, 0x00); + mxl_fail(ret); +fail: + return ret; +} + +int mxl111sf_idac_config(struct mxl111sf_state *state, + u8 control_mode, u8 current_setting, + u8 current_value, u8 hysteresis_value) +{ + int ret; + u8 val; + /* current value will be set for both automatic & manual IDAC control */ + val = current_value; + + if (control_mode == IDAC_MANUAL_CONTROL) { + /* enable manual control of IDAC */ + val |= IDAC_MANUAL_CONTROL_BIT_MASK; + + if (current_setting == IDAC_CURRENT_SINKING_ENABLE) + /* enable current sinking in manual mode */ + val |= IDAC_CURRENT_SINKING_BIT_MASK; + else + /* disable current sinking in manual mode */ + val &= ~IDAC_CURRENT_SINKING_BIT_MASK; + } else { + /* disable manual control of IDAC */ + val &= ~IDAC_MANUAL_CONTROL_BIT_MASK; + + /* set hysteresis value reg: 0x0B<5:0> */ + ret = mxl111sf_write_reg(state, V6_IDAC_HYSTERESIS_REG, + (hysteresis_value & 0x3F)); + mxl_fail(ret); + } + + ret = mxl111sf_write_reg(state, V6_IDAC_SETTINGS_REG, val); + mxl_fail(ret); + + return ret; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.h new file mode 100644 index 000000000000..f0756071d347 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.h @@ -0,0 +1,53 @@ +/* + * mxl111sf-phy.h - driver for the MaxLinear MXL111SF + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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. + */ + +#ifndef _DVB_USB_MXL111SF_PHY_H_ +#define _DVB_USB_MXL111SF_PHY_H_ + +#include "mxl111sf.h" + +int mxl1x1sf_soft_reset(struct mxl111sf_state *state); +int mxl1x1sf_set_device_mode(struct mxl111sf_state *state, int mode); +int mxl1x1sf_top_master_ctrl(struct mxl111sf_state *state, int onoff); +int mxl111sf_disable_656_port(struct mxl111sf_state *state); +int mxl111sf_init_tuner_demod(struct mxl111sf_state *state); +int mxl111sf_enable_usb_output(struct mxl111sf_state *state); +int mxl111sf_config_mpeg_in(struct mxl111sf_state *state, + unsigned int parallel_serial, + unsigned int msb_lsb_1st, + unsigned int clock_phase, + unsigned int mpeg_valid_pol, + unsigned int mpeg_sync_pol); +int mxl111sf_config_i2s(struct mxl111sf_state *state, + u8 msb_start_pos, u8 data_width); +int mxl111sf_init_i2s_port(struct mxl111sf_state *state, u8 sample_size); +int mxl111sf_disable_i2s_port(struct mxl111sf_state *state); +int mxl111sf_config_spi(struct mxl111sf_state *state, int onoff); +int mxl111sf_idac_config(struct mxl111sf_state *state, + u8 control_mode, u8 current_setting, + u8 current_value, u8 hysteresis_value); + +#endif /* _DVB_USB_MXL111SF_PHY_H_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-reg.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-reg.h new file mode 100644 index 000000000000..17831b0fb9db --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-reg.h @@ -0,0 +1,179 @@ +/* + * mxl111sf-reg.h - driver for the MaxLinear MXL111SF + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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. + */ + +#ifndef _DVB_USB_MXL111SF_REG_H_ +#define _DVB_USB_MXL111SF_REG_H_ + +#define CHIP_ID_REG 0xFC +#define TOP_CHIP_REV_ID_REG 0xFA + +#define V6_SNR_RB_LSB_REG 0x27 +#define V6_SNR_RB_MSB_REG 0x28 + +#define V6_N_ACCUMULATE_REG 0x11 +#define V6_RS_AVG_ERRORS_LSB_REG 0x2C +#define V6_RS_AVG_ERRORS_MSB_REG 0x2D + +#define V6_IRQ_STATUS_REG 0x24 +#define IRQ_MASK_FEC_LOCK 0x10 + +#define V6_SYNC_LOCK_REG 0x28 +#define SYNC_LOCK_MASK 0x10 + +#define V6_RS_LOCK_DET_REG 0x28 +#define RS_LOCK_DET_MASK 0x08 + +#define V6_INITACQ_NODETECT_REG 0x20 +#define V6_FORCE_NFFT_CPSIZE_REG 0x20 + +#define V6_CODE_RATE_TPS_REG 0x29 +#define V6_CODE_RATE_TPS_MASK 0x07 + + +#define V6_CP_LOCK_DET_REG 0x28 +#define V6_CP_LOCK_DET_MASK 0x04 + +#define V6_TPS_HIERACHY_REG 0x29 +#define V6_TPS_HIERARCHY_INFO_MASK 0x40 + +#define V6_MODORDER_TPS_REG 0x2A +#define V6_PARAM_CONSTELLATION_MASK 0x30 + +#define V6_MODE_TPS_REG 0x2A +#define V6_PARAM_FFT_MODE_MASK 0x0C + + +#define V6_CP_TPS_REG 0x29 +#define V6_PARAM_GI_MASK 0x30 + +#define V6_TPS_LOCK_REG 0x2A +#define V6_PARAM_TPS_LOCK_MASK 0x40 + +#define V6_FEC_PER_COUNT_REG 0x2E +#define V6_FEC_PER_SCALE_REG 0x2B +#define V6_FEC_PER_SCALE_MASK 0x03 +#define V6_FEC_PER_CLR_REG 0x20 +#define V6_FEC_PER_CLR_MASK 0x01 + +#define V6_PIN_MUX_MODE_REG 0x1B +#define V6_ENABLE_PIN_MUX 0x1E + +#define V6_I2S_NUM_SAMPLES_REG 0x16 + +#define V6_MPEG_IN_CLK_INV_REG 0x17 +#define V6_MPEG_IN_CTRL_REG 0x18 + +#define V6_INVERTED_CLK_PHASE 0x20 +#define V6_MPEG_IN_DATA_PARALLEL 0x01 +#define V6_MPEG_IN_DATA_SERIAL 0x02 + +#define V6_INVERTED_MPEG_SYNC 0x04 +#define V6_INVERTED_MPEG_VALID 0x08 + +#define TSIF_INPUT_PARALLEL 0 +#define TSIF_INPUT_SERIAL 1 +#define TSIF_NORMAL 0 + +#define V6_MPEG_INOUT_BIT_ORDER_CTRL_REG 0x19 +#define V6_MPEG_SER_MSB_FIRST 0x80 +#define MPEG_SER_MSB_FIRST_ENABLED 0x01 + +#define V6_656_I2S_BUFF_STATUS_REG 0x2F +#define V6_656_OVERFLOW_MASK_BIT 0x08 +#define V6_I2S_OVERFLOW_MASK_BIT 0x01 + +#define V6_I2S_STREAM_START_BIT_REG 0x14 +#define V6_I2S_STREAM_END_BIT_REG 0x15 +#define I2S_RIGHT_JUSTIFIED 0 +#define I2S_LEFT_JUSTIFIED 1 +#define I2S_DATA_FORMAT 2 + +#define V6_TUNER_LOOP_THRU_CONTROL_REG 0x09 +#define V6_ENABLE_LOOP_THRU 0x01 + +#define TOTAL_NUM_IF_OUTPUT_FREQ 16 + +#define TUNER_NORMAL_IF_SPECTRUM 0x0 +#define TUNER_INVERT_IF_SPECTRUM 0x10 + +#define V6_TUNER_IF_SEL_REG 0x06 +#define V6_TUNER_IF_FCW_REG 0x3C +#define V6_TUNER_IF_FCW_BYP_REG 0x3D +#define V6_RF_LOCK_STATUS_REG 0x23 + +#define NUM_DIG_TV_CHANNEL 1000 + +#define V6_DIG_CLK_FREQ_SEL_REG 0x07 +#define V6_REF_SYNTH_INT_REG 0x5C +#define V6_REF_SYNTH_REMAIN_REG 0x58 +#define V6_DIG_RFREFSELECT_REG 0x32 +#define V6_XTAL_CLK_OUT_GAIN_REG 0x31 +#define V6_TUNER_LOOP_THRU_CTRL_REG 0x09 +#define V6_DIG_XTAL_ENABLE_REG 0x06 +#define V6_DIG_XTAL_BIAS_REG 0x66 +#define V6_XTAL_CAP_REG 0x08 + +#define V6_GPO_CTRL_REG 0x18 +#define MXL_GPO_0 0x00 +#define MXL_GPO_1 0x01 +#define V6_GPO_0_MASK 0x10 +#define V6_GPO_1_MASK 0x20 + +#define V6_111SF_GPO_CTRL_REG 0x19 +#define MXL_111SF_GPO_1 0x00 +#define MXL_111SF_GPO_2 0x01 +#define MXL_111SF_GPO_3 0x02 +#define MXL_111SF_GPO_4 0x03 +#define MXL_111SF_GPO_5 0x04 +#define MXL_111SF_GPO_6 0x05 +#define MXL_111SF_GPO_7 0x06 + +#define MXL_111SF_GPO_0_MASK 0x01 +#define MXL_111SF_GPO_1_MASK 0x02 +#define MXL_111SF_GPO_2_MASK 0x04 +#define MXL_111SF_GPO_3_MASK 0x08 +#define MXL_111SF_GPO_4_MASK 0x10 +#define MXL_111SF_GPO_5_MASK 0x20 +#define MXL_111SF_GPO_6_MASK 0x40 + +#define V6_ATSC_CONFIG_REG 0x0A + +#define MXL_MODE_REG 0x03 +#define START_TUNE_REG 0x1C + +#define V6_IDAC_HYSTERESIS_REG 0x0B +#define V6_IDAC_SETTINGS_REG 0x0C +#define IDAC_MANUAL_CONTROL 1 +#define IDAC_CURRENT_SINKING_ENABLE 1 +#define IDAC_MANUAL_CONTROL_BIT_MASK 0x80 +#define IDAC_CURRENT_SINKING_BIT_MASK 0x40 + +#define V8_SPI_MODE_REG 0xE9 + +#define V6_DIG_RF_PWR_LSB_REG 0x46 +#define V6_DIG_RF_PWR_MSB_REG 0x47 + +#endif /* _DVB_USB_MXL111SF_REG_H_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.c new file mode 100644 index 000000000000..ef4c65fcbb73 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.c @@ -0,0 +1,527 @@ +/* + * mxl111sf-tuner.c - driver for the MaxLinear MXL111SF CMOS tuner + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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 "mxl111sf-tuner.h" +#include "mxl111sf-phy.h" +#include "mxl111sf-reg.h" + +/* debug */ +static int mxl111sf_tuner_debug; +module_param_named(debug, mxl111sf_tuner_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); + +#define mxl_dbg(fmt, arg...) \ + if (mxl111sf_tuner_debug) \ + mxl_printk(KERN_DEBUG, fmt, ##arg) + +#define err pr_err + +/* ------------------------------------------------------------------------ */ + +struct mxl111sf_tuner_state { + struct mxl111sf_state *mxl_state; + + struct mxl111sf_tuner_config *cfg; + + enum mxl_if_freq if_freq; + + u32 frequency; + u32 bandwidth; +}; + +static int mxl111sf_tuner_read_reg(struct mxl111sf_tuner_state *state, + u8 addr, u8 *data) +{ + return (state->cfg->read_reg) ? + state->cfg->read_reg(state->mxl_state, addr, data) : + -EINVAL; +} + +static int mxl111sf_tuner_write_reg(struct mxl111sf_tuner_state *state, + u8 addr, u8 data) +{ + return (state->cfg->write_reg) ? + state->cfg->write_reg(state->mxl_state, addr, data) : + -EINVAL; +} + +static int mxl111sf_tuner_program_regs(struct mxl111sf_tuner_state *state, + struct mxl111sf_reg_ctrl_info *ctrl_reg_info) +{ + return (state->cfg->program_regs) ? + state->cfg->program_regs(state->mxl_state, ctrl_reg_info) : + -EINVAL; +} + +static int mxl1x1sf_tuner_top_master_ctrl(struct mxl111sf_tuner_state *state, + int onoff) +{ + return (state->cfg->top_master_ctrl) ? + state->cfg->top_master_ctrl(state->mxl_state, onoff) : + -EINVAL; +} + +/* ------------------------------------------------------------------------ */ + +static struct mxl111sf_reg_ctrl_info mxl_phy_tune_rf[] = { + {0x1d, 0x7f, 0x00}, /* channel bandwidth section 1/2/3, + DIG_MODEINDEX, _A, _CSF, */ + {0x1e, 0xff, 0x00}, /* channel frequency (lo and fractional) */ + {0x1f, 0xff, 0x00}, /* channel frequency (hi for integer portion) */ + {0, 0, 0} +}; + +/* ------------------------------------------------------------------------ */ + +static struct mxl111sf_reg_ctrl_info *mxl111sf_calc_phy_tune_regs(u32 freq, + u8 bw) +{ + u8 filt_bw; + + /* set channel bandwidth */ + switch (bw) { + case 0: /* ATSC */ + filt_bw = 25; + break; + case 1: /* QAM */ + filt_bw = 69; + break; + case 6: + filt_bw = 21; + break; + case 7: + filt_bw = 42; + break; + case 8: + filt_bw = 63; + break; + default: + err("%s: invalid bandwidth setting!", __func__); + return NULL; + } + + /* calculate RF channel */ + freq /= 1000000; + + freq *= 64; +#if 0 + /* do round */ + freq += 0.5; +#endif + /* set bandwidth */ + mxl_phy_tune_rf[0].data = filt_bw; + + /* set RF */ + mxl_phy_tune_rf[1].data = (freq & 0xff); + mxl_phy_tune_rf[2].data = (freq >> 8) & 0xff; + + /* start tune */ + return mxl_phy_tune_rf; +} + +static int mxl1x1sf_tuner_set_if_output_freq(struct mxl111sf_tuner_state *state) +{ + int ret; + u8 ctrl; +#if 0 + u16 iffcw; + u32 if_freq; +#endif + mxl_dbg("(IF polarity = %d, IF freq = 0x%02x)", + state->cfg->invert_spectrum, state->cfg->if_freq); + + /* set IF polarity */ + ctrl = state->cfg->invert_spectrum; + + ctrl |= state->cfg->if_freq; + + ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_SEL_REG, ctrl); + if (mxl_fail(ret)) + goto fail; + +#if 0 + if_freq /= 1000000; + + /* do round */ + if_freq += 0.5; + + if (MXL_IF_LO == state->cfg->if_freq) { + ctrl = 0x08; + iffcw = (u16)(if_freq / (108 * 4096)); + } else if (MXL_IF_HI == state->cfg->if_freq) { + ctrl = 0x08; + iffcw = (u16)(if_freq / (216 * 4096)); + } else { + ctrl = 0; + iffcw = 0; + } + + ctrl |= (iffcw >> 8); +#endif + ret = mxl111sf_tuner_read_reg(state, V6_TUNER_IF_FCW_BYP_REG, &ctrl); + if (mxl_fail(ret)) + goto fail; + + ctrl &= 0xf0; + ctrl |= 0x90; + + ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_FCW_BYP_REG, ctrl); + if (mxl_fail(ret)) + goto fail; + +#if 0 + ctrl = iffcw & 0x00ff; +#endif + ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_FCW_REG, ctrl); + if (mxl_fail(ret)) + goto fail; + + state->if_freq = state->cfg->if_freq; +fail: + return ret; +} + +static int mxl1x1sf_tune_rf(struct dvb_frontend *fe, u32 freq, u8 bw) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + static struct mxl111sf_reg_ctrl_info *reg_ctrl_array; + int ret; + u8 mxl_mode; + + mxl_dbg("(freq = %d, bw = 0x%x)", freq, bw); + + /* stop tune */ + ret = mxl111sf_tuner_write_reg(state, START_TUNE_REG, 0); + if (mxl_fail(ret)) + goto fail; + + /* check device mode */ + ret = mxl111sf_tuner_read_reg(state, MXL_MODE_REG, &mxl_mode); + if (mxl_fail(ret)) + goto fail; + + /* Fill out registers for channel tune */ + reg_ctrl_array = mxl111sf_calc_phy_tune_regs(freq, bw); + if (!reg_ctrl_array) + return -EINVAL; + + ret = mxl111sf_tuner_program_regs(state, reg_ctrl_array); + if (mxl_fail(ret)) + goto fail; + + if ((mxl_mode & MXL_DEV_MODE_MASK) == MXL_TUNER_MODE) { + /* IF tuner mode only */ + mxl1x1sf_tuner_top_master_ctrl(state, 0); + mxl1x1sf_tuner_top_master_ctrl(state, 1); + mxl1x1sf_tuner_set_if_output_freq(state); + } + + ret = mxl111sf_tuner_write_reg(state, START_TUNE_REG, 1); + if (mxl_fail(ret)) + goto fail; + + if (state->cfg->ant_hunt) + state->cfg->ant_hunt(fe); +fail: + return ret; +} + +static int mxl1x1sf_tuner_get_lock_status(struct mxl111sf_tuner_state *state, + int *rf_synth_lock, + int *ref_synth_lock) +{ + int ret; + u8 data; + + *rf_synth_lock = 0; + *ref_synth_lock = 0; + + ret = mxl111sf_tuner_read_reg(state, V6_RF_LOCK_STATUS_REG, &data); + if (mxl_fail(ret)) + goto fail; + + *ref_synth_lock = ((data & 0x03) == 0x03) ? 1 : 0; + *rf_synth_lock = ((data & 0x0c) == 0x0c) ? 1 : 0; +fail: + return ret; +} + +#if 0 +static int mxl1x1sf_tuner_loop_thru_ctrl(struct mxl111sf_tuner_state *state, + int onoff) +{ + return mxl111sf_tuner_write_reg(state, V6_TUNER_LOOP_THRU_CTRL_REG, + onoff ? 1 : 0); +} +#endif + +/* ------------------------------------------------------------------------ */ + +static int mxl111sf_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 delsys = c->delivery_system; + struct mxl111sf_tuner_state *state = fe->tuner_priv; + int ret; + u8 bw; + + mxl_dbg("()"); + + switch (delsys) { + case SYS_ATSC: + case SYS_ATSCMH: + bw = 0; /* ATSC */ + break; + case SYS_DVBC_ANNEX_B: + bw = 1; /* US CABLE */ + break; + case SYS_DVBT: + switch (c->bandwidth_hz) { + case 6000000: + bw = 6; + break; + case 7000000: + bw = 7; + break; + case 8000000: + bw = 8; + break; + default: + err("%s: bandwidth not set!", __func__); + return -EINVAL; + } + break; + default: + err("%s: modulation type not supported!", __func__); + return -EINVAL; + } + ret = mxl1x1sf_tune_rf(fe, c->frequency, bw); + if (mxl_fail(ret)) + goto fail; + + state->frequency = c->frequency; + state->bandwidth = c->bandwidth_hz; +fail: + return ret; +} + +/* ------------------------------------------------------------------------ */ + +#if 0 +static int mxl111sf_tuner_init(struct dvb_frontend *fe) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + int ret; + + /* wake from standby handled by usb driver */ + + return ret; +} + +static int mxl111sf_tuner_sleep(struct dvb_frontend *fe) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + int ret; + + /* enter standby mode handled by usb driver */ + + return ret; +} +#endif + +/* ------------------------------------------------------------------------ */ + +static int mxl111sf_tuner_get_status(struct dvb_frontend *fe, u32 *status) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + int rf_locked, ref_locked, ret; + + *status = 0; + + ret = mxl1x1sf_tuner_get_lock_status(state, &rf_locked, &ref_locked); + if (mxl_fail(ret)) + goto fail; + mxl_info("%s%s", rf_locked ? "rf locked " : "", + ref_locked ? "ref locked" : ""); + + if ((rf_locked) || (ref_locked)) + *status |= TUNER_STATUS_LOCKED; +fail: + return ret; +} + +static int mxl111sf_get_rf_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + u8 val1, val2; + int ret; + + *strength = 0; + + ret = mxl111sf_tuner_write_reg(state, 0x00, 0x02); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_tuner_read_reg(state, V6_DIG_RF_PWR_LSB_REG, &val1); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_tuner_read_reg(state, V6_DIG_RF_PWR_MSB_REG, &val2); + if (mxl_fail(ret)) + goto fail; + + *strength = val1 | ((val2 & 0x07) << 8); +fail: + ret = mxl111sf_tuner_write_reg(state, 0x00, 0x00); + mxl_fail(ret); + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +static int mxl111sf_tuner_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + *frequency = state->frequency; + return 0; +} + +static int mxl111sf_tuner_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + *bandwidth = state->bandwidth; + return 0; +} + +static int mxl111sf_tuner_get_if_frequency(struct dvb_frontend *fe, + u32 *frequency) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + + *frequency = 0; + + switch (state->if_freq) { + case MXL_IF_4_0: /* 4.0 MHz */ + *frequency = 4000000; + break; + case MXL_IF_4_5: /* 4.5 MHz */ + *frequency = 4500000; + break; + case MXL_IF_4_57: /* 4.57 MHz */ + *frequency = 4570000; + break; + case MXL_IF_5_0: /* 5.0 MHz */ + *frequency = 5000000; + break; + case MXL_IF_5_38: /* 5.38 MHz */ + *frequency = 5380000; + break; + case MXL_IF_6_0: /* 6.0 MHz */ + *frequency = 6000000; + break; + case MXL_IF_6_28: /* 6.28 MHz */ + *frequency = 6280000; + break; + case MXL_IF_7_2: /* 7.2 MHz */ + *frequency = 7200000; + break; + case MXL_IF_35_25: /* 35.25 MHz */ + *frequency = 35250000; + break; + case MXL_IF_36: /* 36 MHz */ + *frequency = 36000000; + break; + case MXL_IF_36_15: /* 36.15 MHz */ + *frequency = 36150000; + break; + case MXL_IF_44: /* 44 MHz */ + *frequency = 44000000; + break; + } + return 0; +} + +static int mxl111sf_tuner_release(struct dvb_frontend *fe) +{ + struct mxl111sf_tuner_state *state = fe->tuner_priv; + mxl_dbg("()"); + kfree(state); + fe->tuner_priv = NULL; + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static struct dvb_tuner_ops mxl111sf_tuner_tuner_ops = { + .info = { + .name = "MaxLinear MxL111SF", +#if 0 + .frequency_min = , + .frequency_max = , + .frequency_step = , +#endif + }, +#if 0 + .init = mxl111sf_tuner_init, + .sleep = mxl111sf_tuner_sleep, +#endif + .set_params = mxl111sf_tuner_set_params, + .get_status = mxl111sf_tuner_get_status, + .get_rf_strength = mxl111sf_get_rf_strength, + .get_frequency = mxl111sf_tuner_get_frequency, + .get_bandwidth = mxl111sf_tuner_get_bandwidth, + .get_if_frequency = mxl111sf_tuner_get_if_frequency, + .release = mxl111sf_tuner_release, +}; + +struct dvb_frontend *mxl111sf_tuner_attach(struct dvb_frontend *fe, + struct mxl111sf_state *mxl_state, + struct mxl111sf_tuner_config *cfg) +{ + struct mxl111sf_tuner_state *state = NULL; + + mxl_dbg("()"); + + state = kzalloc(sizeof(struct mxl111sf_tuner_state), GFP_KERNEL); + if (state == NULL) + return NULL; + + state->mxl_state = mxl_state; + state->cfg = cfg; + + memcpy(&fe->ops.tuner_ops, &mxl111sf_tuner_tuner_ops, + sizeof(struct dvb_tuner_ops)); + + fe->tuner_priv = state; + return fe; +} +EXPORT_SYMBOL_GPL(mxl111sf_tuner_attach); + +MODULE_DESCRIPTION("MaxLinear MxL111SF CMOS tuner driver"); +MODULE_AUTHOR("Michael Krufky <mkrufky@kernellabs.com>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.h new file mode 100644 index 000000000000..ff333960b184 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.h @@ -0,0 +1,89 @@ +/* + * mxl111sf-tuner.h - driver for the MaxLinear MXL111SF CMOS tuner + * + * Copyright (C) 2010 Michael Krufky <mkrufky@kernellabs.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. + */ + +#ifndef __MXL111SF_TUNER_H__ +#define __MXL111SF_TUNER_H__ + +#include "dvb_frontend.h" + +#include "mxl111sf.h" + +enum mxl_if_freq { +#if 0 + MXL_IF_LO = 0x00, /* other IF < 9MHz */ +#endif + MXL_IF_4_0 = 0x01, /* 4.0 MHz */ + MXL_IF_4_5 = 0x02, /* 4.5 MHz */ + MXL_IF_4_57 = 0x03, /* 4.57 MHz */ + MXL_IF_5_0 = 0x04, /* 5.0 MHz */ + MXL_IF_5_38 = 0x05, /* 5.38 MHz */ + MXL_IF_6_0 = 0x06, /* 6.0 MHz */ + MXL_IF_6_28 = 0x07, /* 6.28 MHz */ + MXL_IF_7_2 = 0x08, /* 7.2 MHz */ + MXL_IF_35_25 = 0x09, /* 35.25 MHz */ + MXL_IF_36 = 0x0a, /* 36 MHz */ + MXL_IF_36_15 = 0x0b, /* 36.15 MHz */ + MXL_IF_44 = 0x0c, /* 44 MHz */ +#if 0 + MXL_IF_HI = 0x0f, /* other IF > 35 MHz and < 45 MHz */ +#endif +}; + +struct mxl111sf_tuner_config { + enum mxl_if_freq if_freq; + unsigned int invert_spectrum:1; + + int (*read_reg)(struct mxl111sf_state *state, u8 addr, u8 *data); + int (*write_reg)(struct mxl111sf_state *state, u8 addr, u8 data); + int (*program_regs)(struct mxl111sf_state *state, + struct mxl111sf_reg_ctrl_info *ctrl_reg_info); + int (*top_master_ctrl)(struct mxl111sf_state *state, int onoff); + int (*ant_hunt)(struct dvb_frontend *fe); +}; + +/* ------------------------------------------------------------------------ */ + +#if defined(CONFIG_DVB_USB_MXL111SF) || \ + (defined(CONFIG_DVB_USB_MXL111SF_MODULE) && defined(MODULE)) +extern +struct dvb_frontend *mxl111sf_tuner_attach(struct dvb_frontend *fe, + struct mxl111sf_state *mxl_state, + struct mxl111sf_tuner_config *cfg); +#else +static inline +struct dvb_frontend *mxl111sf_tuner_attach(struct dvb_frontend *fe, + struct mxl111sf_state *mxl_state + struct mxl111sf_tuner_config *cfg) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif /* __MXL111SF_TUNER_H__ */ + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf.c b/drivers/media/usb/dvb-usb-v2/mxl111sf.c new file mode 100644 index 000000000000..efdcb15358f1 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf.c @@ -0,0 +1,1431 @@ +/* + * Copyright (C) 2010 Michael Krufky (mkrufky@kernellabs.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, version 2. + * + * see Documentation/dvb/README.dvb-usb for more information + */ + +#include <linux/vmalloc.h> +#include <linux/i2c.h> + +#include "mxl111sf.h" +#include "mxl111sf-reg.h" +#include "mxl111sf-phy.h" +#include "mxl111sf-i2c.h" +#include "mxl111sf-gpio.h" + +#include "mxl111sf-demod.h" +#include "mxl111sf-tuner.h" + +#include "lgdt3305.h" +#include "lg2160.h" + +int dvb_usb_mxl111sf_debug; +module_param_named(debug, dvb_usb_mxl111sf_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level " + "(1=info, 2=xfer, 4=i2c, 8=reg, 16=adv (or-able))."); + +int dvb_usb_mxl111sf_isoc; +module_param_named(isoc, dvb_usb_mxl111sf_isoc, int, 0644); +MODULE_PARM_DESC(isoc, "enable usb isoc xfer (0=bulk, 1=isoc)."); + +int dvb_usb_mxl111sf_spi; +module_param_named(spi, dvb_usb_mxl111sf_spi, int, 0644); +MODULE_PARM_DESC(spi, "use spi rather than tp for data xfer (0=tp, 1=spi)."); + +#define ANT_PATH_AUTO 0 +#define ANT_PATH_EXTERNAL 1 +#define ANT_PATH_INTERNAL 2 + +int dvb_usb_mxl111sf_rfswitch = +#if 0 + ANT_PATH_AUTO; +#else + ANT_PATH_EXTERNAL; +#endif + +module_param_named(rfswitch, dvb_usb_mxl111sf_rfswitch, int, 0644); +MODULE_PARM_DESC(rfswitch, "force rf switch position (0=auto, 1=ext, 2=int)."); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define deb_info pr_debug +#define deb_reg pr_debug +#define deb_adv pr_debug +#define err pr_err +#define info pr_info + +int mxl111sf_ctrl_msg(struct dvb_usb_device *d, + u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen) +{ + int wo = (rbuf == NULL || rlen == 0); /* write-only */ + int ret; + u8 sndbuf[1+wlen]; + + deb_adv("%s(wlen = %d, rlen = %d)\n", __func__, wlen, rlen); + + memset(sndbuf, 0, 1+wlen); + + sndbuf[0] = cmd; + memcpy(&sndbuf[1], wbuf, wlen); + + ret = (wo) ? dvb_usbv2_generic_write(d, sndbuf, 1+wlen) : + dvb_usbv2_generic_rw(d, sndbuf, 1+wlen, rbuf, rlen); + mxl_fail(ret); + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +#define MXL_CMD_REG_READ 0xaa +#define MXL_CMD_REG_WRITE 0x55 + +int mxl111sf_read_reg(struct mxl111sf_state *state, u8 addr, u8 *data) +{ + u8 buf[2]; + int ret; + + ret = mxl111sf_ctrl_msg(state->d, MXL_CMD_REG_READ, &addr, 1, buf, 2); + if (mxl_fail(ret)) { + mxl_debug("error reading reg: 0x%02x", addr); + goto fail; + } + + if (buf[0] == addr) + *data = buf[1]; + else { + err("invalid response reading reg: 0x%02x != 0x%02x, 0x%02x", + addr, buf[0], buf[1]); + ret = -EINVAL; + } + + deb_reg("R: (0x%02x, 0x%02x)\n", addr, *data); +fail: + return ret; +} + +int mxl111sf_write_reg(struct mxl111sf_state *state, u8 addr, u8 data) +{ + u8 buf[] = { addr, data }; + int ret; + + deb_reg("W: (0x%02x, 0x%02x)\n", addr, data); + + ret = mxl111sf_ctrl_msg(state->d, MXL_CMD_REG_WRITE, buf, 2, NULL, 0); + if (mxl_fail(ret)) + err("error writing reg: 0x%02x, val: 0x%02x", addr, data); + return ret; +} + +/* ------------------------------------------------------------------------ */ + +int mxl111sf_write_reg_mask(struct mxl111sf_state *state, + u8 addr, u8 mask, u8 data) +{ + int ret; + u8 val; + + if (mask != 0xff) { + ret = mxl111sf_read_reg(state, addr, &val); +#if 1 + /* dont know why this usually errors out on the first try */ + if (mxl_fail(ret)) + err("error writing addr: 0x%02x, mask: 0x%02x, " + "data: 0x%02x, retrying...", addr, mask, data); + + ret = mxl111sf_read_reg(state, addr, &val); +#endif + if (mxl_fail(ret)) + goto fail; + } + val &= ~mask; + val |= data; + + ret = mxl111sf_write_reg(state, addr, val); + mxl_fail(ret); +fail: + return ret; +} + +/* ------------------------------------------------------------------------ */ + +int mxl111sf_ctrl_program_regs(struct mxl111sf_state *state, + struct mxl111sf_reg_ctrl_info *ctrl_reg_info) +{ + int i, ret = 0; + + for (i = 0; ctrl_reg_info[i].addr | + ctrl_reg_info[i].mask | + ctrl_reg_info[i].data; i++) { + + ret = mxl111sf_write_reg_mask(state, + ctrl_reg_info[i].addr, + ctrl_reg_info[i].mask, + ctrl_reg_info[i].data); + if (mxl_fail(ret)) { + err("failed on reg #%d (0x%02x)", i, + ctrl_reg_info[i].addr); + break; + } + } + return ret; +} + +/* ------------------------------------------------------------------------ */ + +static int mxl1x1sf_get_chip_info(struct mxl111sf_state *state) +{ + int ret; + u8 id, ver; + char *mxl_chip, *mxl_rev; + + if ((state->chip_id) && (state->chip_ver)) + return 0; + + ret = mxl111sf_read_reg(state, CHIP_ID_REG, &id); + if (mxl_fail(ret)) + goto fail; + state->chip_id = id; + + ret = mxl111sf_read_reg(state, TOP_CHIP_REV_ID_REG, &ver); + if (mxl_fail(ret)) + goto fail; + state->chip_ver = ver; + + switch (id) { + case 0x61: + mxl_chip = "MxL101SF"; + break; + case 0x63: + mxl_chip = "MxL111SF"; + break; + default: + mxl_chip = "UNKNOWN MxL1X1"; + break; + } + switch (ver) { + case 0x36: + state->chip_rev = MXL111SF_V6; + mxl_rev = "v6"; + break; + case 0x08: + state->chip_rev = MXL111SF_V8_100; + mxl_rev = "v8_100"; + break; + case 0x18: + state->chip_rev = MXL111SF_V8_200; + mxl_rev = "v8_200"; + break; + default: + state->chip_rev = 0; + mxl_rev = "UNKNOWN REVISION"; + break; + } + info("%s detected, %s (0x%x)", mxl_chip, mxl_rev, ver); +fail: + return ret; +} + +#define get_chip_info(state) \ +({ \ + int ___ret; \ + ___ret = mxl1x1sf_get_chip_info(state); \ + if (mxl_fail(___ret)) { \ + mxl_debug("failed to get chip info" \ + " on first probe attempt"); \ + ___ret = mxl1x1sf_get_chip_info(state); \ + if (mxl_fail(___ret)) \ + err("failed to get chip info during probe"); \ + else \ + mxl_debug("probe needed a retry " \ + "in order to succeed."); \ + } \ + ___ret; \ +}) + +/* ------------------------------------------------------------------------ */ +#if 0 +static int mxl111sf_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + /* power control depends on which adapter is being woken: + * save this for init, instead, via mxl111sf_adap_fe_init */ + return 0; +} +#endif + +static int mxl111sf_adap_fe_init(struct dvb_frontend *fe) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct mxl111sf_state *state = fe_to_priv(fe); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe->id]; + int err; + + /* exit if we didnt initialize the driver yet */ + if (!state->chip_id) { + mxl_debug("driver not yet initialized, exit."); + goto fail; + } + + deb_info("%s()\n", __func__); + + mutex_lock(&state->fe_lock); + + state->alt_mode = adap_state->alt_mode; + + if (usb_set_interface(d->udev, 0, state->alt_mode) < 0) + err("set interface failed"); + + err = mxl1x1sf_soft_reset(state); + mxl_fail(err); + err = mxl111sf_init_tuner_demod(state); + mxl_fail(err); + err = mxl1x1sf_set_device_mode(state, adap_state->device_mode); + + mxl_fail(err); + mxl111sf_enable_usb_output(state); + mxl_fail(err); + mxl1x1sf_top_master_ctrl(state, 1); + mxl_fail(err); + + if ((MXL111SF_GPIO_MOD_DVBT != adap_state->gpio_mode) && + (state->chip_rev > MXL111SF_V6)) { + mxl111sf_config_pin_mux_modes(state, + PIN_MUX_TS_SPI_IN_MODE_1); + mxl_fail(err); + } + err = mxl111sf_init_port_expander(state); + if (!mxl_fail(err)) { + state->gpio_mode = adap_state->gpio_mode; + err = mxl111sf_gpio_mode_switch(state, state->gpio_mode); + mxl_fail(err); +#if 0 + err = fe->ops.init(fe); +#endif + msleep(100); /* add short delay after enabling + * the demod before touching it */ + } + + return (adap_state->fe_init) ? adap_state->fe_init(fe) : 0; +fail: + return -ENODEV; +} + +static int mxl111sf_adap_fe_sleep(struct dvb_frontend *fe) +{ + struct mxl111sf_state *state = fe_to_priv(fe); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe->id]; + int err; + + /* exit if we didnt initialize the driver yet */ + if (!state->chip_id) { + mxl_debug("driver not yet initialized, exit."); + goto fail; + } + + deb_info("%s()\n", __func__); + + err = (adap_state->fe_sleep) ? adap_state->fe_sleep(fe) : 0; + + mutex_unlock(&state->fe_lock); + + return err; +fail: + return -ENODEV; +} + + +static int mxl111sf_ep6_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct mxl111sf_state *state = fe_to_priv(fe); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe->id]; + int ret = 0; + + deb_info("%s(%d)\n", __func__, onoff); + + if (onoff) { + ret = mxl111sf_enable_usb_output(state); + mxl_fail(ret); + ret = mxl111sf_config_mpeg_in(state, 1, 1, + adap_state->ep6_clockphase, + 0, 0); + mxl_fail(ret); +#if 0 + } else { + ret = mxl111sf_disable_656_port(state); + mxl_fail(ret); +#endif + } + + return ret; +} + +static int mxl111sf_ep5_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct mxl111sf_state *state = fe_to_priv(fe); + int ret = 0; + + deb_info("%s(%d)\n", __func__, onoff); + + if (onoff) { + ret = mxl111sf_enable_usb_output(state); + mxl_fail(ret); + + ret = mxl111sf_init_i2s_port(state, 200); + mxl_fail(ret); + ret = mxl111sf_config_i2s(state, 0, 15); + mxl_fail(ret); + } else { + ret = mxl111sf_disable_i2s_port(state); + mxl_fail(ret); + } + if (state->chip_rev > MXL111SF_V6) + ret = mxl111sf_config_spi(state, onoff); + mxl_fail(ret); + + return ret; +} + +static int mxl111sf_ep4_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct mxl111sf_state *state = fe_to_priv(fe); + int ret = 0; + + deb_info("%s(%d)\n", __func__, onoff); + + if (onoff) { + ret = mxl111sf_enable_usb_output(state); + mxl_fail(ret); + } + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +static struct lgdt3305_config hauppauge_lgdt3305_config = { + .i2c_addr = 0xb2 >> 1, + .mpeg_mode = LGDT3305_MPEG_SERIAL, + .tpclk_edge = LGDT3305_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, + .deny_i2c_rptr = 1, + .spectral_inversion = 0, + .qam_if_khz = 6000, + .vsb_if_khz = 6000, +}; + +static int mxl111sf_lgdt3305_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct mxl111sf_state *state = d_to_priv(d); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id]; + int ret; + + deb_adv("%s()\n", __func__); + + /* save a pointer to the dvb_usb_device in device state */ + state->d = d; + adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; + state->alt_mode = adap_state->alt_mode; + + if (usb_set_interface(d->udev, 0, state->alt_mode) < 0) + err("set interface failed"); + + state->gpio_mode = MXL111SF_GPIO_MOD_ATSC; + adap_state->gpio_mode = state->gpio_mode; + adap_state->device_mode = MXL_TUNER_MODE; + adap_state->ep6_clockphase = 1; + + ret = mxl1x1sf_soft_reset(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_init_tuner_demod(state); + if (mxl_fail(ret)) + goto fail; + + ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_enable_usb_output(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_top_master_ctrl(state, 1); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_init_port_expander(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); + if (mxl_fail(ret)) + goto fail; + + adap->fe[fe_id] = dvb_attach(lgdt3305_attach, + &hauppauge_lgdt3305_config, + &d->i2c_adap); + if (adap->fe[fe_id]) { + state->num_frontends++; + adap_state->fe_init = adap->fe[fe_id]->ops.init; + adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init; + adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep; + adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep; + return 0; + } + ret = -EIO; +fail: + return ret; +} + +static struct lg2160_config hauppauge_lg2160_config = { + .lg_chip = LG2160, + .i2c_addr = 0x1c >> 1, + .deny_i2c_rptr = 1, + .spectral_inversion = 0, + .if_khz = 6000, +}; + +static int mxl111sf_lg2160_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct mxl111sf_state *state = d_to_priv(d); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id]; + int ret; + + deb_adv("%s()\n", __func__); + + /* save a pointer to the dvb_usb_device in device state */ + state->d = d; + adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; + state->alt_mode = adap_state->alt_mode; + + if (usb_set_interface(d->udev, 0, state->alt_mode) < 0) + err("set interface failed"); + + state->gpio_mode = MXL111SF_GPIO_MOD_MH; + adap_state->gpio_mode = state->gpio_mode; + adap_state->device_mode = MXL_TUNER_MODE; + adap_state->ep6_clockphase = 1; + + ret = mxl1x1sf_soft_reset(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_init_tuner_demod(state); + if (mxl_fail(ret)) + goto fail; + + ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_enable_usb_output(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_top_master_ctrl(state, 1); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_init_port_expander(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); + if (mxl_fail(ret)) + goto fail; + + ret = get_chip_info(state); + if (mxl_fail(ret)) + goto fail; + + adap->fe[fe_id] = dvb_attach(lg2160_attach, + &hauppauge_lg2160_config, + &d->i2c_adap); + if (adap->fe[fe_id]) { + state->num_frontends++; + adap_state->fe_init = adap->fe[fe_id]->ops.init; + adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init; + adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep; + adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep; + return 0; + } + ret = -EIO; +fail: + return ret; +} + +static struct lg2160_config hauppauge_lg2161_1019_config = { + .lg_chip = LG2161_1019, + .i2c_addr = 0x1c >> 1, + .deny_i2c_rptr = 1, + .spectral_inversion = 0, + .if_khz = 6000, + .output_if = 2, /* LG2161_OIF_SPI_MAS */ +}; + +static struct lg2160_config hauppauge_lg2161_1040_config = { + .lg_chip = LG2161_1040, + .i2c_addr = 0x1c >> 1, + .deny_i2c_rptr = 1, + .spectral_inversion = 0, + .if_khz = 6000, + .output_if = 4, /* LG2161_OIF_SPI_MAS */ +}; + +static int mxl111sf_lg2161_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct mxl111sf_state *state = d_to_priv(d); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id]; + int ret; + + deb_adv("%s()\n", __func__); + + /* save a pointer to the dvb_usb_device in device state */ + state->d = d; + adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; + state->alt_mode = adap_state->alt_mode; + + if (usb_set_interface(d->udev, 0, state->alt_mode) < 0) + err("set interface failed"); + + state->gpio_mode = MXL111SF_GPIO_MOD_MH; + adap_state->gpio_mode = state->gpio_mode; + adap_state->device_mode = MXL_TUNER_MODE; + adap_state->ep6_clockphase = 1; + + ret = mxl1x1sf_soft_reset(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_init_tuner_demod(state); + if (mxl_fail(ret)) + goto fail; + + ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_enable_usb_output(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_top_master_ctrl(state, 1); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_init_port_expander(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); + if (mxl_fail(ret)) + goto fail; + + ret = get_chip_info(state); + if (mxl_fail(ret)) + goto fail; + + adap->fe[fe_id] = dvb_attach(lg2160_attach, + (MXL111SF_V8_200 == state->chip_rev) ? + &hauppauge_lg2161_1040_config : + &hauppauge_lg2161_1019_config, + &d->i2c_adap); + if (adap->fe[fe_id]) { + state->num_frontends++; + adap_state->fe_init = adap->fe[fe_id]->ops.init; + adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init; + adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep; + adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep; + return 0; + } + ret = -EIO; +fail: + return ret; +} + +static struct lg2160_config hauppauge_lg2161_1019_ep6_config = { + .lg_chip = LG2161_1019, + .i2c_addr = 0x1c >> 1, + .deny_i2c_rptr = 1, + .spectral_inversion = 0, + .if_khz = 6000, + .output_if = 1, /* LG2161_OIF_SERIAL_TS */ +}; + +static struct lg2160_config hauppauge_lg2161_1040_ep6_config = { + .lg_chip = LG2161_1040, + .i2c_addr = 0x1c >> 1, + .deny_i2c_rptr = 1, + .spectral_inversion = 0, + .if_khz = 6000, + .output_if = 7, /* LG2161_OIF_SERIAL_TS */ +}; + +static int mxl111sf_lg2161_ep6_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct mxl111sf_state *state = d_to_priv(d); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id]; + int ret; + + deb_adv("%s()\n", __func__); + + /* save a pointer to the dvb_usb_device in device state */ + state->d = d; + adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; + state->alt_mode = adap_state->alt_mode; + + if (usb_set_interface(d->udev, 0, state->alt_mode) < 0) + err("set interface failed"); + + state->gpio_mode = MXL111SF_GPIO_MOD_MH; + adap_state->gpio_mode = state->gpio_mode; + adap_state->device_mode = MXL_TUNER_MODE; + adap_state->ep6_clockphase = 0; + + ret = mxl1x1sf_soft_reset(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_init_tuner_demod(state); + if (mxl_fail(ret)) + goto fail; + + ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_enable_usb_output(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_top_master_ctrl(state, 1); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_init_port_expander(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); + if (mxl_fail(ret)) + goto fail; + + ret = get_chip_info(state); + if (mxl_fail(ret)) + goto fail; + + adap->fe[fe_id] = dvb_attach(lg2160_attach, + (MXL111SF_V8_200 == state->chip_rev) ? + &hauppauge_lg2161_1040_ep6_config : + &hauppauge_lg2161_1019_ep6_config, + &d->i2c_adap); + if (adap->fe[fe_id]) { + state->num_frontends++; + adap_state->fe_init = adap->fe[fe_id]->ops.init; + adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init; + adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep; + adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep; + return 0; + } + ret = -EIO; +fail: + return ret; +} + +static struct mxl111sf_demod_config mxl_demod_config = { + .read_reg = mxl111sf_read_reg, + .write_reg = mxl111sf_write_reg, + .program_regs = mxl111sf_ctrl_program_regs, +}; + +static int mxl111sf_attach_demod(struct dvb_usb_adapter *adap, u8 fe_id) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct mxl111sf_state *state = d_to_priv(d); + struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id]; + int ret; + + deb_adv("%s()\n", __func__); + + /* save a pointer to the dvb_usb_device in device state */ + state->d = d; + adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 1 : 2; + state->alt_mode = adap_state->alt_mode; + + if (usb_set_interface(d->udev, 0, state->alt_mode) < 0) + err("set interface failed"); + + state->gpio_mode = MXL111SF_GPIO_MOD_DVBT; + adap_state->gpio_mode = state->gpio_mode; + adap_state->device_mode = MXL_SOC_MODE; + adap_state->ep6_clockphase = 1; + + ret = mxl1x1sf_soft_reset(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl111sf_init_tuner_demod(state); + if (mxl_fail(ret)) + goto fail; + + ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); + if (mxl_fail(ret)) + goto fail; + + ret = mxl111sf_enable_usb_output(state); + if (mxl_fail(ret)) + goto fail; + ret = mxl1x1sf_top_master_ctrl(state, 1); + if (mxl_fail(ret)) + goto fail; + + /* dont care if this fails */ + mxl111sf_init_port_expander(state); + + adap->fe[fe_id] = dvb_attach(mxl111sf_demod_attach, state, + &mxl_demod_config); + if (adap->fe[fe_id]) { + state->num_frontends++; + adap_state->fe_init = adap->fe[fe_id]->ops.init; + adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init; + adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep; + adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep; + return 0; + } + ret = -EIO; +fail: + return ret; +} + +static inline int mxl111sf_set_ant_path(struct mxl111sf_state *state, + int antpath) +{ + return mxl111sf_idac_config(state, 1, 1, + (antpath == ANT_PATH_INTERNAL) ? + 0x3f : 0x00, 0); +} + +#define DbgAntHunt(x, pwr0, pwr1, pwr2, pwr3) \ + err("%s(%d) FINAL input set to %s rxPwr:%d|%d|%d|%d\n", \ + __func__, __LINE__, \ + (ANT_PATH_EXTERNAL == x) ? "EXTERNAL" : "INTERNAL", \ + pwr0, pwr1, pwr2, pwr3) + +#define ANT_HUNT_SLEEP 90 +#define ANT_EXT_TWEAK 0 + +static int mxl111sf_ant_hunt(struct dvb_frontend *fe) +{ + struct mxl111sf_state *state = fe_to_priv(fe); + int antctrl = dvb_usb_mxl111sf_rfswitch; + + u16 rxPwrA, rxPwr0, rxPwr1, rxPwr2; + + /* FIXME: must force EXTERNAL for QAM - done elsewhere */ + mxl111sf_set_ant_path(state, antctrl == ANT_PATH_AUTO ? + ANT_PATH_EXTERNAL : antctrl); + + if (antctrl == ANT_PATH_AUTO) { +#if 0 + msleep(ANT_HUNT_SLEEP); +#endif + fe->ops.tuner_ops.get_rf_strength(fe, &rxPwrA); + + mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL); + msleep(ANT_HUNT_SLEEP); + fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr0); + + mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL); + msleep(ANT_HUNT_SLEEP); + fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr1); + + mxl111sf_set_ant_path(state, ANT_PATH_INTERNAL); + msleep(ANT_HUNT_SLEEP); + fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr2); + + if (rxPwr1+ANT_EXT_TWEAK >= rxPwr2) { + /* return with EXTERNAL enabled */ + mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL); + DbgAntHunt(ANT_PATH_EXTERNAL, rxPwrA, + rxPwr0, rxPwr1, rxPwr2); + } else { + /* return with INTERNAL enabled */ + DbgAntHunt(ANT_PATH_INTERNAL, rxPwrA, + rxPwr0, rxPwr1, rxPwr2); + } + } + return 0; +} + +static struct mxl111sf_tuner_config mxl_tuner_config = { + .if_freq = MXL_IF_6_0, /* applies to external IF output, only */ + .invert_spectrum = 0, + .read_reg = mxl111sf_read_reg, + .write_reg = mxl111sf_write_reg, + .program_regs = mxl111sf_ctrl_program_regs, + .top_master_ctrl = mxl1x1sf_top_master_ctrl, + .ant_hunt = mxl111sf_ant_hunt, +}; + +static int mxl111sf_attach_tuner(struct dvb_usb_adapter *adap) +{ + struct mxl111sf_state *state = adap_to_priv(adap); + int i; + + deb_adv("%s()\n", __func__); + + for (i = 0; i < state->num_frontends; i++) { + if (dvb_attach(mxl111sf_tuner_attach, adap->fe[i], state, + &mxl_tuner_config) == NULL) + return -EIO; + adap->fe[i]->ops.read_signal_strength = adap->fe[i]->ops.tuner_ops.get_rf_strength; + } + + return 0; +} + +static u32 mxl111sf_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +struct i2c_algorithm mxl111sf_i2c_algo = { + .master_xfer = mxl111sf_i2c_xfer, + .functionality = mxl111sf_i2c_func, +#ifdef NEED_ALGO_CONTROL + .algo_control = dummy_algo_control, +#endif +}; + +static int mxl111sf_init(struct dvb_usb_device *d) +{ + struct mxl111sf_state *state = d_to_priv(d); + int ret; + static u8 eeprom[256]; + struct i2c_client c; + + ret = get_chip_info(state); + if (mxl_fail(ret)) + err("failed to get chip info during probe"); + + mutex_init(&state->fe_lock); + + if (state->chip_rev > MXL111SF_V6) + mxl111sf_config_pin_mux_modes(state, PIN_MUX_TS_SPI_IN_MODE_1); + + c.adapter = &d->i2c_adap; + c.addr = 0xa0 >> 1; + + ret = tveeprom_read(&c, eeprom, sizeof(eeprom)); + if (mxl_fail(ret)) + return 0; + tveeprom_hauppauge_analog(&c, &state->tv, (0x84 == eeprom[0xa0]) ? + eeprom + 0xa0 : eeprom + 0x80); +#if 0 + switch (state->tv.model) { + case 117001: + case 126001: + case 138001: + break; + default: + printk(KERN_WARNING "%s: warning: " + "unknown hauppauge model #%d\n", + __func__, state->tv.model); + } +#endif + return 0; +} + +static int mxl111sf_frontend_attach_dvbt(struct dvb_usb_adapter *adap) +{ + return mxl111sf_attach_demod(adap, 0); +} + +static int mxl111sf_frontend_attach_atsc(struct dvb_usb_adapter *adap) +{ + return mxl111sf_lgdt3305_frontend_attach(adap, 0); +} + +static int mxl111sf_frontend_attach_mh(struct dvb_usb_adapter *adap) +{ + return mxl111sf_lg2160_frontend_attach(adap, 0); +} + +static int mxl111sf_frontend_attach_atsc_mh(struct dvb_usb_adapter *adap) +{ + int ret; + deb_info("%s\n", __func__); + + ret = mxl111sf_lgdt3305_frontend_attach(adap, 0); + if (ret < 0) + return ret; + + ret = mxl111sf_attach_demod(adap, 1); + if (ret < 0) + return ret; + + ret = mxl111sf_lg2160_frontend_attach(adap, 2); + if (ret < 0) + return ret; + + return ret; +} + +static int mxl111sf_frontend_attach_mercury(struct dvb_usb_adapter *adap) +{ + int ret; + deb_info("%s\n", __func__); + + ret = mxl111sf_lgdt3305_frontend_attach(adap, 0); + if (ret < 0) + return ret; + + ret = mxl111sf_attach_demod(adap, 1); + if (ret < 0) + return ret; + + ret = mxl111sf_lg2161_ep6_frontend_attach(adap, 2); + if (ret < 0) + return ret; + + return ret; +} + +static int mxl111sf_frontend_attach_mercury_mh(struct dvb_usb_adapter *adap) +{ + int ret; + deb_info("%s\n", __func__); + + ret = mxl111sf_attach_demod(adap, 0); + if (ret < 0) + return ret; + + if (dvb_usb_mxl111sf_spi) + ret = mxl111sf_lg2161_frontend_attach(adap, 1); + else + ret = mxl111sf_lg2161_ep6_frontend_attach(adap, 1); + + return ret; +} + +static void mxl111sf_stream_config_bulk(struct usb_data_stream_properties *stream, u8 endpoint) +{ + deb_info("%s: endpoint=%d size=8192\n", __func__, endpoint); + stream->type = USB_BULK; + stream->count = 5; + stream->endpoint = endpoint; + stream->u.bulk.buffersize = 8192; +} + +static void mxl111sf_stream_config_isoc(struct usb_data_stream_properties *stream, + u8 endpoint, int framesperurb, int framesize) +{ + deb_info("%s: endpoint=%d size=%d\n", __func__, endpoint, + framesperurb * framesize); + stream->type = USB_ISOC; + stream->count = 5; + stream->endpoint = endpoint; + stream->u.isoc.framesperurb = framesperurb; + stream->u.isoc.framesize = framesize; + stream->u.isoc.interval = 1; +} + +/* DVB USB Driver stuff */ + +/* dvbt mxl111sf + * bulk EP4/BULK/5/8192 + * isoc EP4/ISOC/5/96/564 + */ +static int mxl111sf_get_stream_config_dvbt(struct dvb_frontend *fe, + u8 *ts_type, struct usb_data_stream_properties *stream) +{ + deb_info("%s: fe=%d\n", __func__, fe->id); + + *ts_type = DVB_USB_FE_TS_TYPE_188; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 4, 96, 564); + else + mxl111sf_stream_config_bulk(stream, 4); + return 0; +} + +static struct dvb_usb_device_properties mxl111sf_props_dvbt = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct mxl111sf_state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .i2c_algo = &mxl111sf_i2c_algo, + .frontend_attach = mxl111sf_frontend_attach_dvbt, + .tuner_attach = mxl111sf_attach_tuner, + .init = mxl111sf_init, + .streaming_ctrl = mxl111sf_ep4_streaming_ctrl, + .get_stream_config = mxl111sf_get_stream_config_dvbt, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1), + } + } +}; + +/* atsc lgdt3305 + * bulk EP6/BULK/5/8192 + * isoc EP6/ISOC/5/24/3072 + */ +static int mxl111sf_get_stream_config_atsc(struct dvb_frontend *fe, + u8 *ts_type, struct usb_data_stream_properties *stream) +{ + deb_info("%s: fe=%d\n", __func__, fe->id); + + *ts_type = DVB_USB_FE_TS_TYPE_188; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 6, 24, 3072); + else + mxl111sf_stream_config_bulk(stream, 6); + return 0; +} + +static struct dvb_usb_device_properties mxl111sf_props_atsc = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct mxl111sf_state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .i2c_algo = &mxl111sf_i2c_algo, + .frontend_attach = mxl111sf_frontend_attach_atsc, + .tuner_attach = mxl111sf_attach_tuner, + .init = mxl111sf_init, + .streaming_ctrl = mxl111sf_ep6_streaming_ctrl, + .get_stream_config = mxl111sf_get_stream_config_atsc, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1), + } + } +}; + +/* mh lg2160 + * bulk EP5/BULK/5/8192/RAW + * isoc EP5/ISOC/5/96/200/RAW + */ +static int mxl111sf_get_stream_config_mh(struct dvb_frontend *fe, + u8 *ts_type, struct usb_data_stream_properties *stream) +{ + deb_info("%s: fe=%d\n", __func__, fe->id); + + *ts_type = DVB_USB_FE_TS_TYPE_RAW; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 5, 96, 200); + else + mxl111sf_stream_config_bulk(stream, 5); + return 0; +} + +static struct dvb_usb_device_properties mxl111sf_props_mh = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct mxl111sf_state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .i2c_algo = &mxl111sf_i2c_algo, + .frontend_attach = mxl111sf_frontend_attach_mh, + .tuner_attach = mxl111sf_attach_tuner, + .init = mxl111sf_init, + .streaming_ctrl = mxl111sf_ep5_streaming_ctrl, + .get_stream_config = mxl111sf_get_stream_config_mh, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1), + } + } +}; + +/* atsc mh lgdt3305 mxl111sf lg2160 + * bulk EP6/BULK/5/8192 EP4/BULK/5/8192 EP5/BULK/5/8192/RAW + * isoc EP6/ISOC/5/24/3072 EP4/ISOC/5/96/564 EP5/ISOC/5/96/200/RAW + */ +static int mxl111sf_get_stream_config_atsc_mh(struct dvb_frontend *fe, + u8 *ts_type, struct usb_data_stream_properties *stream) +{ + deb_info("%s: fe=%d\n", __func__, fe->id); + + if (fe->id == 0) { + *ts_type = DVB_USB_FE_TS_TYPE_188; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 6, 24, 3072); + else + mxl111sf_stream_config_bulk(stream, 6); + } else if (fe->id == 1) { + *ts_type = DVB_USB_FE_TS_TYPE_188; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 4, 96, 564); + else + mxl111sf_stream_config_bulk(stream, 4); + } else if (fe->id == 2) { + *ts_type = DVB_USB_FE_TS_TYPE_RAW; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 5, 96, 200); + else + mxl111sf_stream_config_bulk(stream, 5); + } + return 0; +} + +static int mxl111sf_streaming_ctrl_atsc_mh(struct dvb_frontend *fe, int onoff) +{ + deb_info("%s: fe=%d onoff=%d\n", __func__, fe->id, onoff); + + if (fe->id == 0) + return mxl111sf_ep6_streaming_ctrl(fe, onoff); + else if (fe->id == 1) + return mxl111sf_ep4_streaming_ctrl(fe, onoff); + else if (fe->id == 2) + return mxl111sf_ep5_streaming_ctrl(fe, onoff); + return 0; +} + +static struct dvb_usb_device_properties mxl111sf_props_atsc_mh = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct mxl111sf_state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .i2c_algo = &mxl111sf_i2c_algo, + .frontend_attach = mxl111sf_frontend_attach_atsc_mh, + .tuner_attach = mxl111sf_attach_tuner, + .init = mxl111sf_init, + .streaming_ctrl = mxl111sf_streaming_ctrl_atsc_mh, + .get_stream_config = mxl111sf_get_stream_config_atsc_mh, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1), + } + } +}; + +/* mercury lgdt3305 mxl111sf lg2161 + * tp bulk EP6/BULK/5/8192 EP4/BULK/5/8192 EP6/BULK/5/8192/RAW + * tp isoc EP6/ISOC/5/24/3072 EP4/ISOC/5/96/564 EP6/ISOC/5/24/3072/RAW + * spi bulk EP6/BULK/5/8192 EP4/BULK/5/8192 EP5/BULK/5/8192/RAW + * spi isoc EP6/ISOC/5/24/3072 EP4/ISOC/5/96/564 EP5/ISOC/5/96/200/RAW + */ +static int mxl111sf_get_stream_config_mercury(struct dvb_frontend *fe, + u8 *ts_type, struct usb_data_stream_properties *stream) +{ + deb_info("%s: fe=%d\n", __func__, fe->id); + + if (fe->id == 0) { + *ts_type = DVB_USB_FE_TS_TYPE_188; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 6, 24, 3072); + else + mxl111sf_stream_config_bulk(stream, 6); + } else if (fe->id == 1) { + *ts_type = DVB_USB_FE_TS_TYPE_188; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 4, 96, 564); + else + mxl111sf_stream_config_bulk(stream, 4); + } else if (fe->id == 2 && dvb_usb_mxl111sf_spi) { + *ts_type = DVB_USB_FE_TS_TYPE_RAW; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 5, 96, 200); + else + mxl111sf_stream_config_bulk(stream, 5); + } else if (fe->id == 2 && !dvb_usb_mxl111sf_spi) { + *ts_type = DVB_USB_FE_TS_TYPE_RAW; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 6, 24, 3072); + else + mxl111sf_stream_config_bulk(stream, 6); + } + return 0; +} + +static int mxl111sf_streaming_ctrl_mercury(struct dvb_frontend *fe, int onoff) +{ + deb_info("%s: fe=%d onoff=%d\n", __func__, fe->id, onoff); + + if (fe->id == 0) + return mxl111sf_ep6_streaming_ctrl(fe, onoff); + else if (fe->id == 1) + return mxl111sf_ep4_streaming_ctrl(fe, onoff); + else if (fe->id == 2 && dvb_usb_mxl111sf_spi) + return mxl111sf_ep5_streaming_ctrl(fe, onoff); + else if (fe->id == 2 && !dvb_usb_mxl111sf_spi) + return mxl111sf_ep6_streaming_ctrl(fe, onoff); + return 0; +} + +static struct dvb_usb_device_properties mxl111sf_props_mercury = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct mxl111sf_state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .i2c_algo = &mxl111sf_i2c_algo, + .frontend_attach = mxl111sf_frontend_attach_mercury, + .tuner_attach = mxl111sf_attach_tuner, + .init = mxl111sf_init, + .streaming_ctrl = mxl111sf_streaming_ctrl_mercury, + .get_stream_config = mxl111sf_get_stream_config_mercury, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1), + } + } +}; + +/* mercury mh mxl111sf lg2161 + * tp bulk EP4/BULK/5/8192 EP6/BULK/5/8192/RAW + * tp isoc EP4/ISOC/5/96/564 EP6/ISOC/5/24/3072/RAW + * spi bulk EP4/BULK/5/8192 EP5/BULK/5/8192/RAW + * spi isoc EP4/ISOC/5/96/564 EP5/ISOC/5/96/200/RAW + */ +static int mxl111sf_get_stream_config_mercury_mh(struct dvb_frontend *fe, + u8 *ts_type, struct usb_data_stream_properties *stream) +{ + deb_info("%s: fe=%d\n", __func__, fe->id); + + if (fe->id == 0) { + *ts_type = DVB_USB_FE_TS_TYPE_188; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 4, 96, 564); + else + mxl111sf_stream_config_bulk(stream, 4); + } else if (fe->id == 1 && dvb_usb_mxl111sf_spi) { + *ts_type = DVB_USB_FE_TS_TYPE_RAW; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 5, 96, 200); + else + mxl111sf_stream_config_bulk(stream, 5); + } else if (fe->id == 1 && !dvb_usb_mxl111sf_spi) { + *ts_type = DVB_USB_FE_TS_TYPE_RAW; + if (dvb_usb_mxl111sf_isoc) + mxl111sf_stream_config_isoc(stream, 6, 24, 3072); + else + mxl111sf_stream_config_bulk(stream, 6); + } + return 0; +} + +static int mxl111sf_streaming_ctrl_mercury_mh(struct dvb_frontend *fe, int onoff) +{ + deb_info("%s: fe=%d onoff=%d\n", __func__, fe->id, onoff); + + if (fe->id == 0) + return mxl111sf_ep4_streaming_ctrl(fe, onoff); + else if (fe->id == 1 && dvb_usb_mxl111sf_spi) + return mxl111sf_ep5_streaming_ctrl(fe, onoff); + else if (fe->id == 1 && !dvb_usb_mxl111sf_spi) + return mxl111sf_ep6_streaming_ctrl(fe, onoff); + return 0; +} + +static struct dvb_usb_device_properties mxl111sf_props_mercury_mh = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct mxl111sf_state), + + .generic_bulk_ctrl_endpoint = 0x02, + .generic_bulk_ctrl_endpoint_response = 0x81, + + .i2c_algo = &mxl111sf_i2c_algo, + .frontend_attach = mxl111sf_frontend_attach_mercury_mh, + .tuner_attach = mxl111sf_attach_tuner, + .init = mxl111sf_init, + .streaming_ctrl = mxl111sf_streaming_ctrl_mercury_mh, + .get_stream_config = mxl111sf_get_stream_config_mercury_mh, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1), + } + } +}; + +static const struct usb_device_id mxl111sf_id_table[] = { + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc600, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc601, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc602, &mxl111sf_props_mh, "HCW 126xxx", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc603, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc604, &mxl111sf_props_dvbt, "Hauppauge 126xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc609, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60a, &mxl111sf_props_mh, "HCW 126xxx", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60b, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60c, &mxl111sf_props_dvbt, "Hauppauge 126xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc653, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc65b, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb700, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb701, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb702, &mxl111sf_props_mh, "HCW 117xxx", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb703, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb704, &mxl111sf_props_dvbt, "Hauppauge 117xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb753, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb763, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb764, &mxl111sf_props_dvbt, "Hauppauge 117xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd853, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd854, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd863, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd864, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d3, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d4, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e3, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e4, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8ff, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc612, &mxl111sf_props_mercury_mh, "Hauppauge 126xxx", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc613, &mxl111sf_props_mercury, "Hauppauge WinTV-Aero-M", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61a, &mxl111sf_props_mercury_mh, "Hauppauge 126xxx", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61b, &mxl111sf_props_mercury, "Hauppauge WinTV-Aero-M", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb757, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) }, + { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb767, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) }, + { } +}; +MODULE_DEVICE_TABLE(usb, mxl111sf_id_table); + +static struct usb_driver mxl111sf_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = mxl111sf_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(mxl111sf_usb_driver); + +MODULE_AUTHOR("Michael Krufky <mkrufky@kernellabs.com>"); +MODULE_DESCRIPTION("Driver for MaxLinear MxL111SF"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf.h b/drivers/media/usb/dvb-usb-v2/mxl111sf.h new file mode 100644 index 000000000000..9816de86e48c --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2010 Michael Krufky (mkrufky@kernellabs.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, version 2. + * + * see Documentation/dvb/README.dvb-usb for more information + */ + +#ifndef _DVB_USB_MXL111SF_H_ +#define _DVB_USB_MXL111SF_H_ + +#ifdef DVB_USB_LOG_PREFIX +#undef DVB_USB_LOG_PREFIX +#endif +#define DVB_USB_LOG_PREFIX "mxl111sf" +#include "dvb_usb.h" +#include <media/tveeprom.h> + +#define MXL_EP1_REG_READ 1 +#define MXL_EP2_REG_WRITE 2 +#define MXL_EP3_INTERRUPT 3 +#define MXL_EP4_MPEG2 4 +#define MXL_EP5_I2S 5 +#define MXL_EP6_656 6 +#define MXL_EP6_MPEG2 6 + +#ifdef USING_ENUM_mxl111sf_current_mode +enum mxl111sf_current_mode { + mxl_mode_dvbt = MXL_EP4_MPEG2, + mxl_mode_mh = MXL_EP5_I2S, + mxl_mode_atsc = MXL_EP6_MPEG2, +}; +#endif + +enum mxl111sf_gpio_port_expander { + mxl111sf_gpio_hw, + mxl111sf_PCA9534, +}; + +struct mxl111sf_adap_state { + int alt_mode; + int gpio_mode; + int device_mode; + int ep6_clockphase; + int (*fe_init)(struct dvb_frontend *); + int (*fe_sleep)(struct dvb_frontend *); +}; + +struct mxl111sf_state { + struct dvb_usb_device *d; + + enum mxl111sf_gpio_port_expander gpio_port_expander; + u8 port_expander_addr; + + u8 chip_id; + u8 chip_ver; +#define MXL111SF_V6 1 +#define MXL111SF_V8_100 2 +#define MXL111SF_V8_200 3 + u8 chip_rev; + +#ifdef USING_ENUM_mxl111sf_current_mode + enum mxl111sf_current_mode current_mode; +#endif + +#define MXL_TUNER_MODE 0 +#define MXL_SOC_MODE 1 +#define MXL_DEV_MODE_MASK 0x01 +#if 1 + int device_mode; +#endif + /* use usb alt setting 1 for EP4 ISOC transfer (dvb-t), + EP5 BULK transfer (atsc-mh), + EP6 BULK transfer (atsc/qam), + use usb alt setting 2 for EP4 BULK transfer (dvb-t), + EP5 ISOC transfer (atsc-mh), + EP6 ISOC transfer (atsc/qam), + */ + int alt_mode; + int gpio_mode; + struct tveeprom tv; + + struct mutex fe_lock; + u8 num_frontends; + struct mxl111sf_adap_state adap_state[3]; +}; + +int mxl111sf_read_reg(struct mxl111sf_state *state, u8 addr, u8 *data); +int mxl111sf_write_reg(struct mxl111sf_state *state, u8 addr, u8 data); + +struct mxl111sf_reg_ctrl_info { + u8 addr; + u8 mask; + u8 data; +}; + +int mxl111sf_write_reg_mask(struct mxl111sf_state *state, + u8 addr, u8 mask, u8 data); +int mxl111sf_ctrl_program_regs(struct mxl111sf_state *state, + struct mxl111sf_reg_ctrl_info *ctrl_reg_info); + +/* needed for hardware i2c functions in mxl111sf-i2c.c: + * mxl111sf_i2c_send_data / mxl111sf_i2c_get_data */ +int mxl111sf_ctrl_msg(struct dvb_usb_device *d, + u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen); + +#define mxl_printk(kern, fmt, arg...) \ + printk(kern "%s: " fmt "\n", __func__, ##arg) + +#define mxl_info(fmt, arg...) \ + mxl_printk(KERN_INFO, fmt, ##arg) + +extern int dvb_usb_mxl111sf_debug; +#define mxl_debug(fmt, arg...) \ + if (dvb_usb_mxl111sf_debug) \ + mxl_printk(KERN_DEBUG, fmt, ##arg) + +#define MXL_I2C_DBG 0x04 +#define MXL_ADV_DBG 0x10 +#define mxl_debug_adv(fmt, arg...) \ + if (dvb_usb_mxl111sf_debug & MXL_ADV_DBG) \ + mxl_printk(KERN_DEBUG, fmt, ##arg) + +#define mxl_i2c(fmt, arg...) \ + if (dvb_usb_mxl111sf_debug & MXL_I2C_DBG) \ + mxl_printk(KERN_DEBUG, fmt, ##arg) + +#define mxl_i2c_adv(fmt, arg...) \ + if ((dvb_usb_mxl111sf_debug & (MXL_I2C_DBG | MXL_ADV_DBG)) == \ + (MXL_I2C_DBG | MXL_ADV_DBG)) \ + mxl_printk(KERN_DEBUG, fmt, ##arg) + +/* The following allows the mxl_fail() macro defined below to work + * in externel modules, such as mxl111sf-tuner.ko, even though + * dvb_usb_mxl111sf_debug is not defined within those modules */ +#if (defined(__MXL111SF_TUNER_H__)) || (defined(__MXL111SF_DEMOD_H__)) +#define MXL_ADV_DEBUG_ENABLED MXL_ADV_DBG +#else +#define MXL_ADV_DEBUG_ENABLED dvb_usb_mxl111sf_debug +#endif + +#define mxl_fail(ret) \ +({ \ + int __ret; \ + __ret = (ret < 0); \ + if ((__ret) && (MXL_ADV_DEBUG_ENABLED & MXL_ADV_DBG)) \ + mxl_printk(KERN_ERR, "error %d on line %d", \ + ret, __LINE__); \ + __ret; \ +}) + +#endif /* _DVB_USB_MXL111SF_H_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c new file mode 100644 index 000000000000..adabba8d28bc --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c @@ -0,0 +1,1370 @@ +/* + * Realtek RTL28xxU DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2012 Thomas Mair <thomas.mair86@googlemail.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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "rtl28xxu.h" + +#include "rtl2830.h" +#include "rtl2832.h" + +#include "qt1010.h" +#include "mt2060.h" +#include "mxl5005s.h" +#include "fc0012.h" +#include "fc0013.h" +#include "e4000.h" +#include "fc2580.h" +#include "tua9001.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int rtl28xxu_ctrl_msg(struct dvb_usb_device *d, struct rtl28xxu_req *req) +{ + int ret; + unsigned int pipe; + u8 requesttype; + u8 *buf; + + buf = kmalloc(req->size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err; + } + + if (req->index & CMD_WR_FLAG) { + /* write */ + memcpy(buf, req->data, req->size); + requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); + pipe = usb_sndctrlpipe(d->udev, 0); + } else { + /* read */ + requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); + pipe = usb_rcvctrlpipe(d->udev, 0); + } + + ret = usb_control_msg(d->udev, pipe, 0, requesttype, req->value, + req->index, buf, req->size, 1000); + + dvb_usb_dbg_usb_control_msg(d->udev, 0, requesttype, req->value, + req->index, buf, req->size); + + if (ret > 0) + ret = 0; + + /* read request, copy returned data to return buf */ + if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) + memcpy(req->data, buf, req->size); + + kfree(buf); + + if (ret) + goto err; + + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl28xx_wr_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len) +{ + struct rtl28xxu_req req; + + if (reg < 0x3000) + req.index = CMD_USB_WR; + else if (reg < 0x4000) + req.index = CMD_SYS_WR; + else + req.index = CMD_IR_WR; + + req.value = reg; + req.size = len; + req.data = val; + + return rtl28xxu_ctrl_msg(d, &req); +} + +static int rtl2831_rd_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len) +{ + struct rtl28xxu_req req; + + if (reg < 0x3000) + req.index = CMD_USB_RD; + else if (reg < 0x4000) + req.index = CMD_SYS_RD; + else + req.index = CMD_IR_RD; + + req.value = reg; + req.size = len; + req.data = val; + + return rtl28xxu_ctrl_msg(d, &req); +} + +static int rtl28xx_wr_reg(struct dvb_usb_device *d, u16 reg, u8 val) +{ + return rtl28xx_wr_regs(d, reg, &val, 1); +} + +static int rtl28xx_rd_reg(struct dvb_usb_device *d, u16 reg, u8 *val) +{ + return rtl2831_rd_regs(d, reg, val, 1); +} + +static int rtl28xx_wr_reg_mask(struct dvb_usb_device *d, u16 reg, u8 val, + u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = rtl28xx_rd_reg(d, reg, &tmp); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return rtl28xx_wr_reg(d, reg, val); +} + +/* I2C */ +static int rtl28xxu_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + int ret; + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct rtl28xxu_priv *priv = d->priv; + struct rtl28xxu_req req; + + /* + * It is not known which are real I2C bus xfer limits, but testing + * with RTL2831U + MT2060 gives max RD 24 and max WR 22 bytes. + * TODO: find out RTL2832U lens + */ + + /* + * I2C adapter logic looks rather complicated due to fact it handles + * three different access methods. Those methods are; + * 1) integrated demod access + * 2) old I2C access + * 3) new I2C access + * + * Used method is selected in order 1, 2, 3. Method 3 can handle all + * requests but there is two reasons why not use it always; + * 1) It is most expensive, usually two USB messages are needed + * 2) At least RTL2831U does not support it + * + * Method 3 is needed in case of I2C write+read (typical register read) + * where write is more than one byte. + */ + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + if (num == 2 && !(msg[0].flags & I2C_M_RD) && + (msg[1].flags & I2C_M_RD)) { + if (msg[0].len > 24 || msg[1].len > 24) { + /* TODO: check msg[0].len max */ + ret = -EOPNOTSUPP; + goto err_mutex_unlock; + } else if (msg[0].addr == 0x10) { + /* method 1 - integrated demod */ + req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); + req.index = CMD_DEMOD_RD | priv->page; + req.size = msg[1].len; + req.data = &msg[1].buf[0]; + ret = rtl28xxu_ctrl_msg(d, &req); + } else if (msg[0].len < 2) { + /* method 2 - old I2C */ + req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); + req.index = CMD_I2C_RD; + req.size = msg[1].len; + req.data = &msg[1].buf[0]; + ret = rtl28xxu_ctrl_msg(d, &req); + } else { + /* method 3 - new I2C */ + req.value = (msg[0].addr << 1); + req.index = CMD_I2C_DA_WR; + req.size = msg[0].len; + req.data = msg[0].buf; + ret = rtl28xxu_ctrl_msg(d, &req); + if (ret) + goto err_mutex_unlock; + + req.value = (msg[0].addr << 1); + req.index = CMD_I2C_DA_RD; + req.size = msg[1].len; + req.data = msg[1].buf; + ret = rtl28xxu_ctrl_msg(d, &req); + } + } else if (num == 1 && !(msg[0].flags & I2C_M_RD)) { + if (msg[0].len > 22) { + /* TODO: check msg[0].len max */ + ret = -EOPNOTSUPP; + goto err_mutex_unlock; + } else if (msg[0].addr == 0x10) { + /* method 1 - integrated demod */ + if (msg[0].buf[0] == 0x00) { + /* save demod page for later demod access */ + priv->page = msg[0].buf[1]; + ret = 0; + } else { + req.value = (msg[0].buf[0] << 8) | + (msg[0].addr << 1); + req.index = CMD_DEMOD_WR | priv->page; + req.size = msg[0].len-1; + req.data = &msg[0].buf[1]; + ret = rtl28xxu_ctrl_msg(d, &req); + } + } else if (msg[0].len < 23) { + /* method 2 - old I2C */ + req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); + req.index = CMD_I2C_WR; + req.size = msg[0].len-1; + req.data = &msg[0].buf[1]; + ret = rtl28xxu_ctrl_msg(d, &req); + } else { + /* method 3 - new I2C */ + req.value = (msg[0].addr << 1); + req.index = CMD_I2C_DA_WR; + req.size = msg[0].len; + req.data = msg[0].buf; + ret = rtl28xxu_ctrl_msg(d, &req); + } + } else { + ret = -EINVAL; + } + +err_mutex_unlock: + mutex_unlock(&d->i2c_mutex); + + return ret ? ret : num; +} + +static u32 rtl28xxu_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm rtl28xxu_i2c_algo = { + .master_xfer = rtl28xxu_i2c_xfer, + .functionality = rtl28xxu_i2c_func, +}; + +static int rtl2831u_read_config(struct dvb_usb_device *d) +{ + struct rtl28xxu_priv *priv = d_to_priv(d); + int ret; + u8 buf[1]; + /* open RTL2831U/RTL2830 I2C gate */ + struct rtl28xxu_req req_gate_open = {0x0120, 0x0011, 0x0001, "\x08"}; + /* tuner probes */ + struct rtl28xxu_req req_mt2060 = {0x00c0, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_qt1010 = {0x0fc4, CMD_I2C_RD, 1, buf}; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* + * RTL2831U GPIOs + * ========================================================= + * GPIO0 | tuner#0 | 0 off | 1 on | MXL5005S (?) + * GPIO2 | LED | 0 off | 1 on | + * GPIO4 | tuner#1 | 0 on | 1 off | MT2060 + */ + + /* GPIO direction */ + ret = rtl28xx_wr_reg(d, SYS_GPIO_DIR, 0x0a); + if (ret) + goto err; + + /* enable as output GPIO0, GPIO2, GPIO4 */ + ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_EN, 0x15); + if (ret) + goto err; + + /* + * Probe used tuner. We need to know used tuner before demod attach + * since there is some demod params needed to set according to tuner. + */ + + /* demod needs some time to wake up */ + msleep(20); + + priv->tuner_name = "NONE"; + + /* open demod I2C gate */ + ret = rtl28xxu_ctrl_msg(d, &req_gate_open); + if (ret) + goto err; + + /* check QT1010 ID(?) register; reg=0f val=2c */ + ret = rtl28xxu_ctrl_msg(d, &req_qt1010); + if (ret == 0 && buf[0] == 0x2c) { + priv->tuner = TUNER_RTL2830_QT1010; + priv->tuner_name = "QT1010"; + goto found; + } + + /* open demod I2C gate */ + ret = rtl28xxu_ctrl_msg(d, &req_gate_open); + if (ret) + goto err; + + /* check MT2060 ID register; reg=00 val=63 */ + ret = rtl28xxu_ctrl_msg(d, &req_mt2060); + if (ret == 0 && buf[0] == 0x63) { + priv->tuner = TUNER_RTL2830_MT2060; + priv->tuner_name = "MT2060"; + goto found; + } + + /* assume MXL5005S */ + priv->tuner = TUNER_RTL2830_MXL5005S; + priv->tuner_name = "MXL5005S"; + goto found; + +found: + dev_dbg(&d->udev->dev, "%s: tuner=%s\n", __func__, priv->tuner_name); + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_read_config(struct dvb_usb_device *d) +{ + struct rtl28xxu_priv *priv = d_to_priv(d); + int ret; + u8 buf[2]; + /* open RTL2832U/RTL2832 I2C gate */ + struct rtl28xxu_req req_gate_open = {0x0120, 0x0011, 0x0001, "\x18"}; + /* close RTL2832U/RTL2832 I2C gate */ + struct rtl28xxu_req req_gate_close = {0x0120, 0x0011, 0x0001, "\x10"}; + /* tuner probes */ + struct rtl28xxu_req req_fc0012 = {0x00c6, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_fc0013 = {0x00c6, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_mt2266 = {0x00c0, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_fc2580 = {0x01ac, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_mt2063 = {0x00c0, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_max3543 = {0x00c0, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_tua9001 = {0x7ec0, CMD_I2C_RD, 2, buf}; + struct rtl28xxu_req req_mxl5007t = {0xd9c0, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_e4000 = {0x02c8, CMD_I2C_RD, 1, buf}; + struct rtl28xxu_req req_tda18272 = {0x00c0, CMD_I2C_RD, 2, buf}; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* enable GPIO3 and GPIO6 as output */ + ret = rtl28xx_wr_reg_mask(d, SYS_GPIO_DIR, 0x00, 0x40); + if (ret) + goto err; + + ret = rtl28xx_wr_reg_mask(d, SYS_GPIO_OUT_EN, 0x48, 0x48); + if (ret) + goto err; + + /* + * Probe used tuner. We need to know used tuner before demod attach + * since there is some demod params needed to set according to tuner. + */ + + /* open demod I2C gate */ + ret = rtl28xxu_ctrl_msg(d, &req_gate_open); + if (ret) + goto err; + + priv->tuner_name = "NONE"; + + /* check FC0012 ID register; reg=00 val=a1 */ + ret = rtl28xxu_ctrl_msg(d, &req_fc0012); + if (ret == 0 && buf[0] == 0xa1) { + priv->tuner = TUNER_RTL2832_FC0012; + priv->tuner_name = "FC0012"; + goto found; + } + + /* check FC0013 ID register; reg=00 val=a3 */ + ret = rtl28xxu_ctrl_msg(d, &req_fc0013); + if (ret == 0 && buf[0] == 0xa3) { + priv->tuner = TUNER_RTL2832_FC0013; + priv->tuner_name = "FC0013"; + goto found; + } + + /* check MT2266 ID register; reg=00 val=85 */ + ret = rtl28xxu_ctrl_msg(d, &req_mt2266); + if (ret == 0 && buf[0] == 0x85) { + priv->tuner = TUNER_RTL2832_MT2266; + priv->tuner_name = "MT2266"; + goto found; + } + + /* check FC2580 ID register; reg=01 val=56 */ + ret = rtl28xxu_ctrl_msg(d, &req_fc2580); + if (ret == 0 && buf[0] == 0x56) { + priv->tuner = TUNER_RTL2832_FC2580; + priv->tuner_name = "FC2580"; + goto found; + } + + /* check MT2063 ID register; reg=00 val=9e || 9c */ + ret = rtl28xxu_ctrl_msg(d, &req_mt2063); + if (ret == 0 && (buf[0] == 0x9e || buf[0] == 0x9c)) { + priv->tuner = TUNER_RTL2832_MT2063; + priv->tuner_name = "MT2063"; + goto found; + } + + /* check MAX3543 ID register; reg=00 val=38 */ + ret = rtl28xxu_ctrl_msg(d, &req_max3543); + if (ret == 0 && buf[0] == 0x38) { + priv->tuner = TUNER_RTL2832_MAX3543; + priv->tuner_name = "MAX3543"; + goto found; + } + + /* check TUA9001 ID register; reg=7e val=2328 */ + ret = rtl28xxu_ctrl_msg(d, &req_tua9001); + if (ret == 0 && buf[0] == 0x23 && buf[1] == 0x28) { + priv->tuner = TUNER_RTL2832_TUA9001; + priv->tuner_name = "TUA9001"; + goto found; + } + + /* check MXL5007R ID register; reg=d9 val=14 */ + ret = rtl28xxu_ctrl_msg(d, &req_mxl5007t); + if (ret == 0 && buf[0] == 0x14) { + priv->tuner = TUNER_RTL2832_MXL5007T; + priv->tuner_name = "MXL5007T"; + goto found; + } + + /* check E4000 ID register; reg=02 val=40 */ + ret = rtl28xxu_ctrl_msg(d, &req_e4000); + if (ret == 0 && buf[0] == 0x40) { + priv->tuner = TUNER_RTL2832_E4000; + priv->tuner_name = "E4000"; + goto found; + } + + /* check TDA18272 ID register; reg=00 val=c760 */ + ret = rtl28xxu_ctrl_msg(d, &req_tda18272); + if (ret == 0 && (buf[0] == 0xc7 || buf[1] == 0x60)) { + priv->tuner = TUNER_RTL2832_TDA18272; + priv->tuner_name = "TDA18272"; + goto found; + } + +found: + dev_dbg(&d->udev->dev, "%s: tuner=%s\n", __func__, priv->tuner_name); + + /* close demod I2C gate */ + ret = rtl28xxu_ctrl_msg(d, &req_gate_close); + if (ret < 0) + goto err; + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct rtl2830_config rtl28xxu_rtl2830_mt2060_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .ts_mode = 0, + .spec_inv = 1, + .vtop = 0x20, + .krf = 0x04, + .agc_targ_val = 0x2d, + +}; + +static struct rtl2830_config rtl28xxu_rtl2830_qt1010_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .ts_mode = 0, + .spec_inv = 1, + .vtop = 0x20, + .krf = 0x04, + .agc_targ_val = 0x2d, +}; + +static struct rtl2830_config rtl28xxu_rtl2830_mxl5005s_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .ts_mode = 0, + .spec_inv = 0, + .vtop = 0x3f, + .krf = 0x04, + .agc_targ_val = 0x3e, +}; + +static int rtl2831u_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_usb_device *d = adap_to_d(adap); + struct rtl28xxu_priv *priv = d_to_priv(d); + struct rtl2830_config *rtl2830_config; + int ret; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + switch (priv->tuner) { + case TUNER_RTL2830_QT1010: + rtl2830_config = &rtl28xxu_rtl2830_qt1010_config; + break; + case TUNER_RTL2830_MT2060: + rtl2830_config = &rtl28xxu_rtl2830_mt2060_config; + break; + case TUNER_RTL2830_MXL5005S: + rtl2830_config = &rtl28xxu_rtl2830_mxl5005s_config; + break; + default: + dev_err(&d->udev->dev, "%s: unknown tuner=%s\n", + KBUILD_MODNAME, priv->tuner_name); + ret = -ENODEV; + goto err; + } + + /* attach demodulator */ + adap->fe[0] = dvb_attach(rtl2830_attach, rtl2830_config, &d->i2c_adap); + if (!adap->fe[0]) { + ret = -ENODEV; + goto err; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct rtl2832_config rtl28xxu_rtl2832_fc0012_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .if_dvbt = 0, + .tuner = TUNER_RTL2832_FC0012 +}; + +static struct rtl2832_config rtl28xxu_rtl2832_fc0013_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .if_dvbt = 0, + .tuner = TUNER_RTL2832_FC0013 +}; + +static struct rtl2832_config rtl28xxu_rtl2832_tua9001_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .tuner = TUNER_RTL2832_TUA9001, +}; + +static struct rtl2832_config rtl28xxu_rtl2832_e4000_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .tuner = TUNER_RTL2832_E4000, +}; + +static int rtl2832u_fc0012_tuner_callback(struct dvb_usb_device *d, + int cmd, int arg) +{ + int ret; + u8 val; + + dev_dbg(&d->udev->dev, "%s: cmd=%d arg=%d\n", __func__, cmd, arg); + + switch (cmd) { + case FC_FE_CALLBACK_VHF_ENABLE: + /* set output values */ + ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &val); + if (ret) + goto err; + + if (arg) + val &= 0xbf; /* set GPIO6 low */ + else + val |= 0x40; /* set GPIO6 high */ + + + ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, val); + if (ret) + goto err; + break; + default: + ret = -EINVAL; + goto err; + } + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_tua9001_tuner_callback(struct dvb_usb_device *d, + int cmd, int arg) +{ + int ret; + u8 val; + + dev_dbg(&d->udev->dev, "%s: cmd=%d arg=%d\n", __func__, cmd, arg); + + /* + * CEN always enabled by hardware wiring + * RESETN GPIO4 + * RXEN GPIO1 + */ + + switch (cmd) { + case TUA9001_CMD_RESETN: + if (arg) + val = (1 << 4); + else + val = (0 << 4); + + ret = rtl28xx_wr_reg_mask(d, SYS_GPIO_OUT_VAL, val, 0x10); + if (ret) + goto err; + break; + case TUA9001_CMD_RXEN: + if (arg) + val = (1 << 1); + else + val = (0 << 1); + + ret = rtl28xx_wr_reg_mask(d, SYS_GPIO_OUT_VAL, val, 0x02); + if (ret) + goto err; + break; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_tuner_callback(struct dvb_usb_device *d, int cmd, int arg) +{ + struct rtl28xxu_priv *priv = d->priv; + + switch (priv->tuner) { + case TUNER_RTL2832_FC0012: + return rtl2832u_fc0012_tuner_callback(d, cmd, arg); + case TUNER_RTL2832_TUA9001: + return rtl2832u_tua9001_tuner_callback(d, cmd, arg); + default: + break; + } + + return 0; +} + +static int rtl2832u_frontend_callback(void *adapter_priv, int component, + int cmd, int arg) +{ + struct i2c_adapter *adap = adapter_priv; + struct dvb_usb_device *d = i2c_get_adapdata(adap); + + dev_dbg(&d->udev->dev, "%s: component=%d cmd=%d arg=%d\n", + __func__, component, cmd, arg); + + switch (component) { + case DVB_FRONTEND_COMPONENT_TUNER: + return rtl2832u_tuner_callback(d, cmd, arg); + default: + break; + } + + return 0; +} + +static int rtl2832u_frontend_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct dvb_usb_device *d = adap_to_d(adap); + struct rtl28xxu_priv *priv = d_to_priv(d); + struct rtl2832_config *rtl2832_config; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + switch (priv->tuner) { + case TUNER_RTL2832_FC0012: + rtl2832_config = &rtl28xxu_rtl2832_fc0012_config; + break; + case TUNER_RTL2832_FC0013: + rtl2832_config = &rtl28xxu_rtl2832_fc0013_config; + break; + case TUNER_RTL2832_FC2580: + /* FIXME: do not abuse fc0012 settings */ + rtl2832_config = &rtl28xxu_rtl2832_fc0012_config; + break; + case TUNER_RTL2832_TUA9001: + rtl2832_config = &rtl28xxu_rtl2832_tua9001_config; + break; + case TUNER_RTL2832_E4000: + rtl2832_config = &rtl28xxu_rtl2832_e4000_config; + break; + default: + dev_err(&d->udev->dev, "%s: unknown tuner=%s\n", + KBUILD_MODNAME, priv->tuner_name); + ret = -ENODEV; + goto err; + } + + /* attach demodulator */ + adap->fe[0] = dvb_attach(rtl2832_attach, rtl2832_config, &d->i2c_adap); + if (!adap->fe[0]) { + ret = -ENODEV; + goto err; + } + + /* set fe callback */ + adap->fe[0]->callback = rtl2832u_frontend_callback; + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct qt1010_config rtl28xxu_qt1010_config = { + .i2c_address = 0x62, /* 0xc4 */ +}; + +static struct mt2060_config rtl28xxu_mt2060_config = { + .i2c_address = 0x60, /* 0xc0 */ + .clock_out = 0, +}; + +static struct mxl5005s_config rtl28xxu_mxl5005s_config = { + .i2c_address = 0x63, /* 0xc6 */ + .if_freq = IF_FREQ_4570000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_C_H, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .AgcMasterByte = 0x00, +}; + +static int rtl2831u_tuner_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct dvb_usb_device *d = adap_to_d(adap); + struct rtl28xxu_priv *priv = d_to_priv(d); + struct i2c_adapter *rtl2830_tuner_i2c; + struct dvb_frontend *fe; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* use rtl2830 driver I2C adapter, for more info see rtl2830 driver */ + rtl2830_tuner_i2c = rtl2830_get_tuner_i2c_adapter(adap->fe[0]); + + switch (priv->tuner) { + case TUNER_RTL2830_QT1010: + fe = dvb_attach(qt1010_attach, adap->fe[0], + rtl2830_tuner_i2c, &rtl28xxu_qt1010_config); + break; + case TUNER_RTL2830_MT2060: + fe = dvb_attach(mt2060_attach, adap->fe[0], + rtl2830_tuner_i2c, &rtl28xxu_mt2060_config, + 1220); + break; + case TUNER_RTL2830_MXL5005S: + fe = dvb_attach(mxl5005s_attach, adap->fe[0], + rtl2830_tuner_i2c, &rtl28xxu_mxl5005s_config); + break; + default: + fe = NULL; + dev_err(&d->udev->dev, "%s: unknown tuner=%d\n", KBUILD_MODNAME, + priv->tuner); + } + + if (fe == NULL) { + ret = -ENODEV; + goto err; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static const struct e4000_config rtl2832u_e4000_config = { + .i2c_addr = 0x64, + .clock = 28800000, +}; + +static const struct fc2580_config rtl2832u_fc2580_config = { + .i2c_addr = 0x56, + .clock = 16384000, +}; + +static struct tua9001_config rtl2832u_tua9001_config = { + .i2c_addr = 0x60, +}; + +static int rtl2832u_tuner_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct dvb_usb_device *d = adap_to_d(adap); + struct rtl28xxu_priv *priv = d_to_priv(d); + struct dvb_frontend *fe; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + switch (priv->tuner) { + case TUNER_RTL2832_FC0012: + fe = dvb_attach(fc0012_attach, adap->fe[0], + &d->i2c_adap, 0xc6>>1, 0, FC_XTAL_28_8_MHZ); + + /* since fc0012 includs reading the signal strength delegate + * that to the tuner driver */ + adap->fe[0]->ops.read_signal_strength = + adap->fe[0]->ops.tuner_ops.get_rf_strength; + return 0; + break; + case TUNER_RTL2832_FC0013: + fe = dvb_attach(fc0013_attach, adap->fe[0], + &d->i2c_adap, 0xc6>>1, 0, FC_XTAL_28_8_MHZ); + + /* fc0013 also supports signal strength reading */ + adap->fe[0]->ops.read_signal_strength = + adap->fe[0]->ops.tuner_ops.get_rf_strength; + return 0; + case TUNER_RTL2832_E4000: + fe = dvb_attach(e4000_attach, adap->fe[0], &d->i2c_adap, + &rtl2832u_e4000_config); + break; + case TUNER_RTL2832_FC2580: + fe = dvb_attach(fc2580_attach, adap->fe[0], &d->i2c_adap, + &rtl2832u_fc2580_config); + break; + case TUNER_RTL2832_TUA9001: + /* enable GPIO1 and GPIO4 as output */ + ret = rtl28xx_wr_reg_mask(d, SYS_GPIO_DIR, 0x00, 0x12); + if (ret) + goto err; + + ret = rtl28xx_wr_reg_mask(d, SYS_GPIO_OUT_EN, 0x12, 0x12); + if (ret) + goto err; + + fe = dvb_attach(tua9001_attach, adap->fe[0], &d->i2c_adap, + &rtl2832u_tua9001_config); + break; + default: + fe = NULL; + dev_err(&d->udev->dev, "%s: unknown tuner=%d\n", KBUILD_MODNAME, + priv->tuner); + } + + if (fe == NULL) { + ret = -ENODEV; + goto err; + } + + return 0; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl28xxu_init(struct dvb_usb_device *d) +{ + int ret; + u8 val; + + dev_dbg(&d->udev->dev, "%s:\n", __func__); + + /* init USB endpoints */ + ret = rtl28xx_rd_reg(d, USB_SYSCTL_0, &val); + if (ret) + goto err; + + /* enable DMA and Full Packet Mode*/ + val |= 0x09; + ret = rtl28xx_wr_reg(d, USB_SYSCTL_0, val); + if (ret) + goto err; + + /* set EPA maximum packet size to 0x0200 */ + ret = rtl28xx_wr_regs(d, USB_EPA_MAXPKT, "\x00\x02\x00\x00", 4); + if (ret) + goto err; + + /* change EPA FIFO length */ + ret = rtl28xx_wr_regs(d, USB_EPA_FIFO_CFG, "\x14\x00\x00\x00", 4); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2831u_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + int ret; + u8 gpio, sys0, epa_ctl[2]; + + dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff); + + /* demod adc */ + ret = rtl28xx_rd_reg(d, SYS_SYS0, &sys0); + if (ret) + goto err; + + /* tuner power, read GPIOs */ + ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &gpio); + if (ret) + goto err; + + dev_dbg(&d->udev->dev, "%s: RD SYS0=%02x GPIO_OUT_VAL=%02x\n", __func__, + sys0, gpio); + + if (onoff) { + gpio |= 0x01; /* GPIO0 = 1 */ + gpio &= (~0x10); /* GPIO4 = 0 */ + gpio |= 0x04; /* GPIO2 = 1, LED on */ + sys0 = sys0 & 0x0f; + sys0 |= 0xe0; + epa_ctl[0] = 0x00; /* clear stall */ + epa_ctl[1] = 0x00; /* clear reset */ + } else { + gpio &= (~0x01); /* GPIO0 = 0 */ + gpio |= 0x10; /* GPIO4 = 1 */ + gpio &= (~0x04); /* GPIO2 = 1, LED off */ + sys0 = sys0 & (~0xc0); + epa_ctl[0] = 0x10; /* set stall */ + epa_ctl[1] = 0x02; /* set reset */ + } + + dev_dbg(&d->udev->dev, "%s: WR SYS0=%02x GPIO_OUT_VAL=%02x\n", __func__, + sys0, gpio); + + /* demod adc */ + ret = rtl28xx_wr_reg(d, SYS_SYS0, sys0); + if (ret) + goto err; + + /* tuner power, write GPIOs */ + ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, gpio); + if (ret) + goto err; + + /* streaming EP: stall & reset */ + ret = rtl28xx_wr_regs(d, USB_EPA_CTL, epa_ctl, 2); + if (ret) + goto err; + + if (onoff) + usb_clear_halt(d->udev, usb_rcvbulkpipe(d->udev, 0x81)); + + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + int ret; + u8 val; + + dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff); + + if (onoff) { + /* set output values */ + ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &val); + if (ret) + goto err; + + val |= 0x08; + val &= 0xef; + + ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, val); + if (ret) + goto err; + + /* demod_ctl_1 */ + ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL1, &val); + if (ret) + goto err; + + val &= 0xef; + + ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL1, val); + if (ret) + goto err; + + /* demod control */ + /* PLL enable */ + ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); + if (ret) + goto err; + + /* bit 7 to 1 */ + val |= 0x80; + + ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); + if (ret) + goto err; + + ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); + if (ret) + goto err; + + val |= 0x20; + + ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); + if (ret) + goto err; + + mdelay(5); + + /*enable ADC_Q and ADC_I */ + ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); + if (ret) + goto err; + + val |= 0x48; + + ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); + if (ret) + goto err; + + /* streaming EP: clear stall & reset */ + ret = rtl28xx_wr_regs(d, USB_EPA_CTL, "\x00\x00", 2); + if (ret) + goto err; + + ret = usb_clear_halt(d->udev, usb_rcvbulkpipe(d->udev, 0x81)); + if (ret) + goto err; + } else { + /* demod_ctl_1 */ + ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL1, &val); + if (ret) + goto err; + + val |= 0x0c; + + ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL1, val); + if (ret) + goto err; + + /* set output values */ + ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &val); + if (ret) + goto err; + + val |= 0x10; + + ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, val); + if (ret) + goto err; + + /* demod control */ + ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); + if (ret) + goto err; + + val &= 0x37; + + ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); + if (ret) + goto err; + + /* streaming EP: set stall & reset */ + ret = rtl28xx_wr_regs(d, USB_EPA_CTL, "\x10\x02", 2); + if (ret) + goto err; + } + + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + + +static int rtl2831u_rc_query(struct dvb_usb_device *d) +{ + int ret, i; + struct rtl28xxu_priv *priv = d->priv; + u8 buf[5]; + u32 rc_code; + struct rtl28xxu_reg_val rc_nec_tab[] = { + { 0x3033, 0x80 }, + { 0x3020, 0x43 }, + { 0x3021, 0x16 }, + { 0x3022, 0x16 }, + { 0x3023, 0x5a }, + { 0x3024, 0x2d }, + { 0x3025, 0x16 }, + { 0x3026, 0x01 }, + { 0x3028, 0xb0 }, + { 0x3029, 0x04 }, + { 0x302c, 0x88 }, + { 0x302e, 0x13 }, + { 0x3030, 0xdf }, + { 0x3031, 0x05 }, + }; + + /* init remote controller */ + if (!priv->rc_active) { + for (i = 0; i < ARRAY_SIZE(rc_nec_tab); i++) { + ret = rtl28xx_wr_reg(d, rc_nec_tab[i].reg, + rc_nec_tab[i].val); + if (ret) + goto err; + } + priv->rc_active = true; + } + + ret = rtl2831_rd_regs(d, SYS_IRRC_RP, buf, 5); + if (ret) + goto err; + + if (buf[4] & 0x01) { + if (buf[2] == (u8) ~buf[3]) { + if (buf[0] == (u8) ~buf[1]) { + /* NEC standard (16 bit) */ + rc_code = buf[0] << 8 | buf[2]; + } else { + /* NEC extended (24 bit) */ + rc_code = buf[0] << 16 | + buf[1] << 8 | buf[2]; + } + } else { + /* NEC full (32 bit) */ + rc_code = buf[0] << 24 | buf[1] << 16 | + buf[2] << 8 | buf[3]; + } + + rc_keydown(d->rc_dev, rc_code, 0); + + ret = rtl28xx_wr_reg(d, SYS_IRRC_SR, 1); + if (ret) + goto err; + + /* repeated intentionally to avoid extra keypress */ + ret = rtl28xx_wr_reg(d, SYS_IRRC_SR, 1); + if (ret) + goto err; + } + + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2831u_get_rc_config(struct dvb_usb_device *d, + struct dvb_usb_rc *rc) +{ + rc->map_name = RC_MAP_EMPTY; + rc->allowed_protos = RC_TYPE_NEC; + rc->query = rtl2831u_rc_query; + rc->interval = 400; + + return 0; +} + +static int rtl2832u_rc_query(struct dvb_usb_device *d) +{ + int ret, i; + struct rtl28xxu_priv *priv = d->priv; + u8 buf[128]; + int len; + struct rtl28xxu_reg_val rc_nec_tab[] = { + { IR_RX_CTRL, 0x20 }, + { IR_RX_BUF_CTRL, 0x80 }, + { IR_RX_IF, 0xff }, + { IR_RX_IE, 0xff }, + { IR_MAX_DURATION0, 0xd0 }, + { IR_MAX_DURATION1, 0x07 }, + { IR_IDLE_LEN0, 0xc0 }, + { IR_IDLE_LEN1, 0x00 }, + { IR_GLITCH_LEN, 0x03 }, + { IR_RX_CLK, 0x09 }, + { IR_RX_CFG, 0x1c }, + { IR_MAX_H_TOL_LEN, 0x1e }, + { IR_MAX_L_TOL_LEN, 0x1e }, + { IR_RX_CTRL, 0x80 }, + }; + + /* init remote controller */ + if (!priv->rc_active) { + for (i = 0; i < ARRAY_SIZE(rc_nec_tab); i++) { + ret = rtl28xx_wr_reg(d, rc_nec_tab[i].reg, + rc_nec_tab[i].val); + if (ret) + goto err; + } + priv->rc_active = true; + } + + ret = rtl28xx_rd_reg(d, IR_RX_IF, &buf[0]); + if (ret) + goto err; + + if (buf[0] != 0x83) + goto exit; + + ret = rtl28xx_rd_reg(d, IR_RX_BC, &buf[0]); + if (ret) + goto err; + + len = buf[0]; + ret = rtl2831_rd_regs(d, IR_RX_BUF, buf, len); + + /* TODO: pass raw IR to Kernel IR decoder */ + + ret = rtl28xx_wr_reg(d, IR_RX_IF, 0x03); + ret = rtl28xx_wr_reg(d, IR_RX_BUF_CTRL, 0x80); + ret = rtl28xx_wr_reg(d, IR_RX_CTRL, 0x80); + +exit: + return ret; +err: + dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_get_rc_config(struct dvb_usb_device *d, + struct dvb_usb_rc *rc) +{ + rc->map_name = RC_MAP_EMPTY; + rc->allowed_protos = RC_TYPE_NEC; + rc->query = rtl2832u_rc_query; + rc->interval = 400; + + return 0; +} + +static const struct dvb_usb_device_properties rtl2831u_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct rtl28xxu_priv), + + .power_ctrl = rtl2831u_power_ctrl, + .i2c_algo = &rtl28xxu_i2c_algo, + .read_config = rtl2831u_read_config, + .frontend_attach = rtl2831u_frontend_attach, + .tuner_attach = rtl2831u_tuner_attach, + .init = rtl28xxu_init, + .get_rc_config = rtl2831u_get_rc_config, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x81, 6, 8 * 512), + }, + }, +}; + +static const struct dvb_usb_device_properties rtl2832u_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct rtl28xxu_priv), + + .power_ctrl = rtl2832u_power_ctrl, + .i2c_algo = &rtl28xxu_i2c_algo, + .read_config = rtl2832u_read_config, + .frontend_attach = rtl2832u_frontend_attach, + .tuner_attach = rtl2832u_tuner_attach, + .init = rtl28xxu_init, + .get_rc_config = rtl2832u_get_rc_config, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x81, 6, 8 * 512), + }, + }, +}; + +static const struct usb_device_id rtl28xxu_id_table[] = { + { DVB_USB_DEVICE(USB_VID_REALTEK, USB_PID_REALTEK_RTL2831U, + &rtl2831u_props, "Realtek RTL2831U reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT, + &rtl2831u_props, "Freecom USB2.0 DVB-T", NULL) }, + { DVB_USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT_2, + &rtl2831u_props, "Freecom USB2.0 DVB-T", NULL) }, + + { DVB_USB_DEVICE(USB_VID_REALTEK, 0x2832, + &rtl2832u_props, "Realtek RTL2832U reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_REALTEK, 0x2838, + &rtl2832u_props, "Realtek RTL2832U reference design", NULL) }, + { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_BLACK_REV1, + &rtl2832u_props, "Terratec Cinergy T Stick Black", NULL) }, + { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_DELOCK_USB2_DVBT, + &rtl2832u_props, "G-Tek Electronics Group Lifeview LV5TDLX DVB-T", NULL) }, + { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK, + &rtl2832u_props, "NOXON DAB/DAB+ USB dongle", NULL) }, + { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_TREKSTOR_TERRES_2_0, + &rtl2832u_props, "Trekstor DVB-T Stick Terres 2.0", NULL) }, + { DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1101, + &rtl2832u_props, "Dexatek DK DVB-T Dongle", NULL) }, + { DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6680, + &rtl2832u_props, "DigitalNow Quad DVB-T Receiver", NULL) }, + { DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00d3, + &rtl2832u_props, "TerraTec Cinergy T Stick RC (Rev. 3)", NULL) }, + { } +}; +MODULE_DEVICE_TABLE(usb, rtl28xxu_id_table); + +static struct usb_driver rtl28xxu_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl28xxu_id_table, + .probe = dvb_usbv2_probe, + .disconnect = dvb_usbv2_disconnect, + .suspend = dvb_usbv2_suspend, + .resume = dvb_usbv2_resume, + .reset_resume = dvb_usbv2_reset_resume, + .no_dynamic_id = 1, + .soft_unbind = 1, +}; + +module_usb_driver(rtl28xxu_usb_driver); + +MODULE_DESCRIPTION("Realtek RTL28xxU DVB USB driver"); +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_AUTHOR("Thomas Mair <thomas.mair86@googlemail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.h b/drivers/media/usb/dvb-usb-v2/rtl28xxu.h new file mode 100644 index 000000000000..2f3af2d3b6ce --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.h @@ -0,0 +1,245 @@ +/* + * Realtek RTL28xxU DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RTL28XXU_H +#define RTL28XXU_H + +#include "dvb_usb.h" + +/* + * USB commands + * (usb_control_msg() index parameter) + */ + +#define DEMOD 0x0000 +#define USB 0x0100 +#define SYS 0x0200 +#define I2C 0x0300 +#define I2C_DA 0x0600 + +#define CMD_WR_FLAG 0x0010 +#define CMD_DEMOD_RD 0x0000 +#define CMD_DEMOD_WR 0x0010 +#define CMD_USB_RD 0x0100 +#define CMD_USB_WR 0x0110 +#define CMD_SYS_RD 0x0200 +#define CMD_IR_RD 0x0201 +#define CMD_IR_WR 0x0211 +#define CMD_SYS_WR 0x0210 +#define CMD_I2C_RD 0x0300 +#define CMD_I2C_WR 0x0310 +#define CMD_I2C_DA_RD 0x0600 +#define CMD_I2C_DA_WR 0x0610 + + +struct rtl28xxu_priv { + u8 chip_id; + u8 tuner; + char *tuner_name; + u8 page; /* integrated demod active register page */ + bool rc_active; +}; + +enum rtl28xxu_chip_id { + CHIP_ID_NONE, + CHIP_ID_RTL2831U, + CHIP_ID_RTL2832U, +}; + +/* XXX: Hack. This must be keep sync with rtl2832 demod driver. */ +enum rtl28xxu_tuner { + TUNER_NONE, + + TUNER_RTL2830_QT1010 = 0x10, + TUNER_RTL2830_MT2060, + TUNER_RTL2830_MXL5005S, + + TUNER_RTL2832_MT2266 = 0x20, + TUNER_RTL2832_FC2580, + TUNER_RTL2832_MT2063, + TUNER_RTL2832_MAX3543, + TUNER_RTL2832_TUA9001, + TUNER_RTL2832_MXL5007T, + TUNER_RTL2832_FC0012, + TUNER_RTL2832_E4000, + TUNER_RTL2832_TDA18272, + TUNER_RTL2832_FC0013, +}; + +struct rtl28xxu_req { + u16 value; + u16 index; + u16 size; + u8 *data; +}; + +struct rtl28xxu_reg_val { + u16 reg; + u8 val; +}; + +/* + * memory map + * + * 0x0000 DEMOD : demodulator + * 0x2000 USB : SIE, USB endpoint, debug, DMA + * 0x3000 SYS : system + * 0xfc00 RC : remote controller (not RTL2831U) + */ + +/* + * USB registers + */ +/* SIE Control Registers */ +#define USB_SYSCTL 0x2000 /* USB system control */ +#define USB_SYSCTL_0 0x2000 /* USB system control */ +#define USB_SYSCTL_1 0x2001 /* USB system control */ +#define USB_SYSCTL_2 0x2002 /* USB system control */ +#define USB_SYSCTL_3 0x2003 /* USB system control */ +#define USB_IRQSTAT 0x2008 /* SIE interrupt status */ +#define USB_IRQEN 0x200C /* SIE interrupt enable */ +#define USB_CTRL 0x2010 /* USB control */ +#define USB_STAT 0x2014 /* USB status */ +#define USB_DEVADDR 0x2018 /* USB device address */ +#define USB_TEST 0x201C /* USB test mode */ +#define USB_FRAME_NUMBER 0x2020 /* frame number */ +#define USB_FIFO_ADDR 0x2028 /* address of SIE FIFO RAM */ +#define USB_FIFO_CMD 0x202A /* SIE FIFO RAM access command */ +#define USB_FIFO_DATA 0x2030 /* SIE FIFO RAM data */ +/* Endpoint Registers */ +#define EP0_SETUPA 0x20F8 /* EP 0 setup packet lower byte */ +#define EP0_SETUPB 0x20FC /* EP 0 setup packet higher byte */ +#define USB_EP0_CFG 0x2104 /* EP 0 configure */ +#define USB_EP0_CTL 0x2108 /* EP 0 control */ +#define USB_EP0_STAT 0x210C /* EP 0 status */ +#define USB_EP0_IRQSTAT 0x2110 /* EP 0 interrupt status */ +#define USB_EP0_IRQEN 0x2114 /* EP 0 interrupt enable */ +#define USB_EP0_MAXPKT 0x2118 /* EP 0 max packet size */ +#define USB_EP0_BC 0x2120 /* EP 0 FIFO byte counter */ +#define USB_EPA_CFG 0x2144 /* EP A configure */ +#define USB_EPA_CFG_0 0x2144 /* EP A configure */ +#define USB_EPA_CFG_1 0x2145 /* EP A configure */ +#define USB_EPA_CFG_2 0x2146 /* EP A configure */ +#define USB_EPA_CFG_3 0x2147 /* EP A configure */ +#define USB_EPA_CTL 0x2148 /* EP A control */ +#define USB_EPA_CTL_0 0x2148 /* EP A control */ +#define USB_EPA_CTL_1 0x2149 /* EP A control */ +#define USB_EPA_CTL_2 0x214A /* EP A control */ +#define USB_EPA_CTL_3 0x214B /* EP A control */ +#define USB_EPA_STAT 0x214C /* EP A status */ +#define USB_EPA_IRQSTAT 0x2150 /* EP A interrupt status */ +#define USB_EPA_IRQEN 0x2154 /* EP A interrupt enable */ +#define USB_EPA_MAXPKT 0x2158 /* EP A max packet size */ +#define USB_EPA_MAXPKT_0 0x2158 /* EP A max packet size */ +#define USB_EPA_MAXPKT_1 0x2159 /* EP A max packet size */ +#define USB_EPA_MAXPKT_2 0x215A /* EP A max packet size */ +#define USB_EPA_MAXPKT_3 0x215B /* EP A max packet size */ +#define USB_EPA_FIFO_CFG 0x2160 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_0 0x2160 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_1 0x2161 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_2 0x2162 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_3 0x2163 /* EP A FIFO configure */ +/* Debug Registers */ +#define USB_PHYTSTDIS 0x2F04 /* PHY test disable */ +#define USB_TOUT_VAL 0x2F08 /* USB time-out time */ +#define USB_VDRCTRL 0x2F10 /* UTMI vendor signal control */ +#define USB_VSTAIN 0x2F14 /* UTMI vendor signal status in */ +#define USB_VLOADM 0x2F18 /* UTMI load vendor signal status in */ +#define USB_VSTAOUT 0x2F1C /* UTMI vendor signal status out */ +#define USB_UTMI_TST 0x2F80 /* UTMI test */ +#define USB_UTMI_STATUS 0x2F84 /* UTMI status */ +#define USB_TSTCTL 0x2F88 /* test control */ +#define USB_TSTCTL2 0x2F8C /* test control 2 */ +#define USB_PID_FORCE 0x2F90 /* force PID */ +#define USB_PKTERR_CNT 0x2F94 /* packet error counter */ +#define USB_RXERR_CNT 0x2F98 /* RX error counter */ +#define USB_MEM_BIST 0x2F9C /* MEM BIST test */ +#define USB_SLBBIST 0x2FA0 /* self-loop-back BIST */ +#define USB_CNTTEST 0x2FA4 /* counter test */ +#define USB_PHYTST 0x2FC0 /* USB PHY test */ +#define USB_DBGIDX 0x2FF0 /* select individual block debug signal */ +#define USB_DBGMUX 0x2FF4 /* debug signal module mux */ + +/* + * SYS registers + */ +/* demod control registers */ +#define SYS_SYS0 0x3000 /* include DEMOD_CTL, GPO, GPI, GPOE */ +#define SYS_DEMOD_CTL 0x3000 /* control register for DVB-T demodulator */ +/* GPIO registers */ +#define SYS_GPIO_OUT_VAL 0x3001 /* output value of GPIO */ +#define SYS_GPIO_IN_VAL 0x3002 /* input value of GPIO */ +#define SYS_GPIO_OUT_EN 0x3003 /* output enable of GPIO */ +#define SYS_SYS1 0x3004 /* include GPD, SYSINTE, SYSINTS, GP_CFG0 */ +#define SYS_GPIO_DIR 0x3004 /* direction control for GPIO */ +#define SYS_SYSINTE 0x3005 /* system interrupt enable */ +#define SYS_SYSINTS 0x3006 /* system interrupt status */ +#define SYS_GPIO_CFG0 0x3007 /* PAD configuration for GPIO0-GPIO3 */ +#define SYS_SYS2 0x3008 /* include GP_CFG1 and 3 reserved bytes */ +#define SYS_GPIO_CFG1 0x3008 /* PAD configuration for GPIO4 */ +#define SYS_DEMOD_CTL1 0x300B + +/* IrDA registers */ +#define SYS_IRRC_PSR 0x3020 /* IR protocol selection */ +#define SYS_IRRC_PER 0x3024 /* IR protocol extension */ +#define SYS_IRRC_SF 0x3028 /* IR sampling frequency */ +#define SYS_IRRC_DPIR 0x302C /* IR data package interval */ +#define SYS_IRRC_CR 0x3030 /* IR control */ +#define SYS_IRRC_RP 0x3034 /* IR read port */ +#define SYS_IRRC_SR 0x3038 /* IR status */ +/* I2C master registers */ +#define SYS_I2CCR 0x3040 /* I2C clock */ +#define SYS_I2CMCR 0x3044 /* I2C master control */ +#define SYS_I2CMSTR 0x3048 /* I2C master SCL timing */ +#define SYS_I2CMSR 0x304C /* I2C master status */ +#define SYS_I2CMFR 0x3050 /* I2C master FIFO */ + +/* + * IR registers + */ +#define IR_RX_BUF 0xFC00 +#define IR_RX_IE 0xFD00 +#define IR_RX_IF 0xFD01 +#define IR_RX_CTRL 0xFD02 +#define IR_RX_CFG 0xFD03 +#define IR_MAX_DURATION0 0xFD04 +#define IR_MAX_DURATION1 0xFD05 +#define IR_IDLE_LEN0 0xFD06 +#define IR_IDLE_LEN1 0xFD07 +#define IR_GLITCH_LEN 0xFD08 +#define IR_RX_BUF_CTRL 0xFD09 +#define IR_RX_BUF_DATA 0xFD0A +#define IR_RX_BC 0xFD0B +#define IR_RX_CLK 0xFD0C +#define IR_RX_C_COUNT_L 0xFD0D +#define IR_RX_C_COUNT_H 0xFD0E +#define IR_SUSPEND_CTRL 0xFD10 +#define IR_ERR_TOL_CTRL 0xFD11 +#define IR_UNIT_LEN 0xFD12 +#define IR_ERR_TOL_LEN 0xFD13 +#define IR_MAX_H_TOL_LEN 0xFD14 +#define IR_MAX_L_TOL_LEN 0xFD15 +#define IR_MASK_CTRL 0xFD16 +#define IR_MASK_DATA 0xFD17 +#define IR_RES_MASK_ADDR 0xFD18 +#define IR_RES_MASK_T_LEN 0xFD19 + +#endif diff --git a/drivers/media/usb/dvb-usb-v2/usb_urb.c b/drivers/media/usb/dvb-usb-v2/usb_urb.c new file mode 100644 index 000000000000..5989b6590377 --- /dev/null +++ b/drivers/media/usb/dvb-usb-v2/usb_urb.c @@ -0,0 +1,358 @@ +/* usb-urb.c is part of the DVB USB library. + * + * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@desy.de) + * see dvb-usb-init.c for copyright information. + * + * This file keeps functions for initializing and handling the + * BULK and ISOC USB data transfers in a generic way. + * Can be used for DVB-only and also, that's the plan, for + * Hybrid USB devices (analog and DVB). + */ +#include "dvb_usb_common.h" + +/* URB stuff for streaming */ + +int usb_urb_reconfig(struct usb_data_stream *stream, + struct usb_data_stream_properties *props); + +static void usb_urb_complete(struct urb *urb) +{ + struct usb_data_stream *stream = urb->context; + int ptype = usb_pipetype(urb->pipe); + int i; + u8 *b; + + dev_dbg_ratelimited(&stream->udev->dev, "%s: %s urb completed " \ + "status=%d length=%d/%d pack_num=%d errors=%d\n", + __func__, ptype == PIPE_ISOCHRONOUS ? "isoc" : "bulk", + urb->status, urb->actual_length, + urb->transfer_buffer_length, + urb->number_of_packets, urb->error_count); + + switch (urb->status) { + case 0: /* success */ + case -ETIMEDOUT: /* NAK */ + break; + case -ECONNRESET: /* kill */ + case -ENOENT: + case -ESHUTDOWN: + return; + default: /* error */ + dev_dbg_ratelimited(&stream->udev->dev, + "%s: urb completition failed=%d\n", + __func__, urb->status); + break; + } + + b = (u8 *) urb->transfer_buffer; + switch (ptype) { + case PIPE_ISOCHRONOUS: + for (i = 0; i < urb->number_of_packets; i++) { + if (urb->iso_frame_desc[i].status != 0) + dev_dbg(&stream->udev->dev, "%s: iso frame " \ + "descriptor has an error=%d\n", + __func__, + urb->iso_frame_desc[i].status); + else if (urb->iso_frame_desc[i].actual_length > 0) + stream->complete(stream, + b + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].actual_length); + + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = 0; + } + break; + case PIPE_BULK: + if (urb->actual_length > 0) + stream->complete(stream, b, urb->actual_length); + break; + default: + dev_err(&stream->udev->dev, "%s: unknown endpoint type in " \ + "completition handler\n", KBUILD_MODNAME); + return; + } + usb_submit_urb(urb, GFP_ATOMIC); +} + +int usb_urb_killv2(struct usb_data_stream *stream) +{ + int i; + for (i = 0; i < stream->urbs_submitted; i++) { + dev_dbg(&stream->udev->dev, "%s: kill urb=%d\n", __func__, i); + /* stop the URB */ + usb_kill_urb(stream->urb_list[i]); + } + stream->urbs_submitted = 0; + return 0; +} + +int usb_urb_submitv2(struct usb_data_stream *stream, + struct usb_data_stream_properties *props) +{ + int i, ret; + + if (props) { + ret = usb_urb_reconfig(stream, props); + if (ret < 0) + return ret; + } + + for (i = 0; i < stream->urbs_initialized; i++) { + dev_dbg(&stream->udev->dev, "%s: submit urb=%d\n", __func__, i); + ret = usb_submit_urb(stream->urb_list[i], GFP_ATOMIC); + if (ret) { + dev_err(&stream->udev->dev, "%s: could not submit " \ + "urb no. %d - get them all back\n", + KBUILD_MODNAME, i); + usb_urb_killv2(stream); + return ret; + } + stream->urbs_submitted++; + } + return 0; +} + +int usb_urb_free_urbs(struct usb_data_stream *stream) +{ + int i; + + usb_urb_killv2(stream); + + for (i = stream->urbs_initialized - 1; i >= 0; i--) { + if (stream->urb_list[i]) { + dev_dbg(&stream->udev->dev, "%s: free urb=%d\n", + __func__, i); + /* free the URBs */ + usb_free_urb(stream->urb_list[i]); + } + } + stream->urbs_initialized = 0; + + return 0; +} + +static int usb_urb_alloc_bulk_urbs(struct usb_data_stream *stream) +{ + int i, j; + + /* allocate the URBs */ + for (i = 0; i < stream->props.count; i++) { + dev_dbg(&stream->udev->dev, "%s: alloc urb=%d\n", __func__, i); + stream->urb_list[i] = usb_alloc_urb(0, GFP_ATOMIC); + if (!stream->urb_list[i]) { + dev_dbg(&stream->udev->dev, "%s: failed\n", __func__); + for (j = 0; j < i; j++) + usb_free_urb(stream->urb_list[j]); + return -ENOMEM; + } + usb_fill_bulk_urb(stream->urb_list[i], + stream->udev, + usb_rcvbulkpipe(stream->udev, + stream->props.endpoint), + stream->buf_list[i], + stream->props.u.bulk.buffersize, + usb_urb_complete, stream); + + stream->urb_list[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + stream->urb_list[i]->transfer_dma = stream->dma_addr[i]; + stream->urbs_initialized++; + } + return 0; +} + +static int usb_urb_alloc_isoc_urbs(struct usb_data_stream *stream) +{ + int i, j; + + /* allocate the URBs */ + for (i = 0; i < stream->props.count; i++) { + struct urb *urb; + int frame_offset = 0; + dev_dbg(&stream->udev->dev, "%s: alloc urb=%d\n", __func__, i); + stream->urb_list[i] = usb_alloc_urb( + stream->props.u.isoc.framesperurb, GFP_ATOMIC); + if (!stream->urb_list[i]) { + dev_dbg(&stream->udev->dev, "%s: failed\n", __func__); + for (j = 0; j < i; j++) + usb_free_urb(stream->urb_list[j]); + return -ENOMEM; + } + + urb = stream->urb_list[i]; + + urb->dev = stream->udev; + urb->context = stream; + urb->complete = usb_urb_complete; + urb->pipe = usb_rcvisocpipe(stream->udev, + stream->props.endpoint); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->interval = stream->props.u.isoc.interval; + urb->number_of_packets = stream->props.u.isoc.framesperurb; + urb->transfer_buffer_length = stream->props.u.isoc.framesize * + stream->props.u.isoc.framesperurb; + urb->transfer_buffer = stream->buf_list[i]; + urb->transfer_dma = stream->dma_addr[i]; + + for (j = 0; j < stream->props.u.isoc.framesperurb; j++) { + urb->iso_frame_desc[j].offset = frame_offset; + urb->iso_frame_desc[j].length = + stream->props.u.isoc.framesize; + frame_offset += stream->props.u.isoc.framesize; + } + + stream->urbs_initialized++; + } + return 0; +} + +int usb_free_stream_buffers(struct usb_data_stream *stream) +{ + if (stream->state & USB_STATE_URB_BUF) { + while (stream->buf_num) { + stream->buf_num--; + dev_dbg(&stream->udev->dev, "%s: free buf=%d\n", + __func__, stream->buf_num); + usb_free_coherent(stream->udev, stream->buf_size, + stream->buf_list[stream->buf_num], + stream->dma_addr[stream->buf_num]); + } + } + + stream->state &= ~USB_STATE_URB_BUF; + + return 0; +} + +int usb_alloc_stream_buffers(struct usb_data_stream *stream, int num, + unsigned long size) +{ + stream->buf_num = 0; + stream->buf_size = size; + + dev_dbg(&stream->udev->dev, "%s: all in all I will use %lu bytes for " \ + "streaming\n", __func__, num * size); + + for (stream->buf_num = 0; stream->buf_num < num; stream->buf_num++) { + stream->buf_list[stream->buf_num] = usb_alloc_coherent( + stream->udev, size, GFP_ATOMIC, + &stream->dma_addr[stream->buf_num]); + if (!stream->buf_list[stream->buf_num]) { + dev_dbg(&stream->udev->dev, "%s: alloc buf=%d failed\n", + __func__, stream->buf_num); + usb_free_stream_buffers(stream); + return -ENOMEM; + } + + dev_dbg(&stream->udev->dev, "%s: alloc buf=%d %p (dma %llu)\n", + __func__, stream->buf_num, + stream->buf_list[stream->buf_num], + (long long)stream->dma_addr[stream->buf_num]); + memset(stream->buf_list[stream->buf_num], 0, size); + stream->state |= USB_STATE_URB_BUF; + } + + return 0; +} + +int usb_urb_reconfig(struct usb_data_stream *stream, + struct usb_data_stream_properties *props) +{ + int buf_size; + + if (!props) + return 0; + + /* check allocated buffers are large enough for the request */ + if (props->type == USB_BULK) { + buf_size = stream->props.u.bulk.buffersize; + } else if (props->type == USB_ISOC) { + buf_size = props->u.isoc.framesize * props->u.isoc.framesperurb; + } else { + dev_err(&stream->udev->dev, "%s: invalid endpoint type=%d\n", + KBUILD_MODNAME, props->type); + return -EINVAL; + } + + if (stream->buf_num < props->count || stream->buf_size < buf_size) { + dev_err(&stream->udev->dev, "%s: cannot reconfigure as " \ + "allocated buffers are too small\n", + KBUILD_MODNAME); + return -EINVAL; + } + + /* check if all fields are same */ + if (stream->props.type == props->type && + stream->props.count == props->count && + stream->props.endpoint == props->endpoint) { + if (props->type == USB_BULK && + props->u.bulk.buffersize == + stream->props.u.bulk.buffersize) + return 0; + else if (props->type == USB_ISOC && + props->u.isoc.framesperurb == + stream->props.u.isoc.framesperurb && + props->u.isoc.framesize == + stream->props.u.isoc.framesize && + props->u.isoc.interval == + stream->props.u.isoc.interval) + return 0; + } + + dev_dbg(&stream->udev->dev, "%s: re-alloc urbs\n", __func__); + + usb_urb_free_urbs(stream); + memcpy(&stream->props, props, sizeof(*props)); + if (props->type == USB_BULK) + return usb_urb_alloc_bulk_urbs(stream); + else if (props->type == USB_ISOC) + return usb_urb_alloc_isoc_urbs(stream); + + return 0; +} + +int usb_urb_initv2(struct usb_data_stream *stream, + const struct usb_data_stream_properties *props) +{ + int ret; + + if (!stream || !props) + return -EINVAL; + + memcpy(&stream->props, props, sizeof(*props)); + + if (!stream->complete) { + dev_err(&stream->udev->dev, "%s: there is no data callback - " \ + "this doesn't make sense\n", KBUILD_MODNAME); + return -EINVAL; + } + + switch (stream->props.type) { + case USB_BULK: + ret = usb_alloc_stream_buffers(stream, stream->props.count, + stream->props.u.bulk.buffersize); + if (ret < 0) + return ret; + + return usb_urb_alloc_bulk_urbs(stream); + case USB_ISOC: + ret = usb_alloc_stream_buffers(stream, stream->props.count, + stream->props.u.isoc.framesize * + stream->props.u.isoc.framesperurb); + if (ret < 0) + return ret; + + return usb_urb_alloc_isoc_urbs(stream); + default: + dev_err(&stream->udev->dev, "%s: unknown urb-type for data " \ + "transfer\n", KBUILD_MODNAME); + return -EINVAL; + } +} + +int usb_urb_exitv2(struct usb_data_stream *stream) +{ + usb_urb_free_urbs(stream); + usb_free_stream_buffers(stream); + + return 0; +} |