--- a/drivers/media/usb/dvb-usb-v2/Kconfig +++ b/drivers/media/usb/dvb-usb-v2/Kconfig @@ -141,3 +141,10 @@ config DVB_USB_RTL28XXU help Say Y here to support the Realtek RTL28xxU DVB USB receiver. +config DVB_USB_DVBSKY + tristate "DVBSky USB support" + depends on DVB_USB_V2 + select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_M88TS2022 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to support the USB receivers from DVBSky. --- a/drivers/media/usb/dvb-usb-v2/Makefile +++ b/drivers/media/usb/dvb-usb-v2/Makefile @@ -37,6 +37,9 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o dvb-usb-rtl28xxu-objs := rtl28xxu.o obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o +dvb-usb-dvbsky-objs := dvbsky.o +obj-$(CONFIG_DVB_USB_DVBSKY) += dvb-usb-dvbsky.o + ccflags-y += -I$(srctree)/drivers/media/dvb-core ccflags-y += -I$(srctree)/drivers/media/dvb-frontends ccflags-y += -I$(srctree)/drivers/media/tuners --- a/drivers/media/usb/dvb-usb-v2/dvbsky.c +++ b/drivers/media/usb/dvb-usb-v2/dvbsky.c @@ -0,0 +1,864 @@ +/* + * Driver for DVBSky USB2.0 receiver + * + * Copyright (C) 2013 Max nibble + * + * 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 "dvb_usb.h" +#include "m88ds3103.h" +#include "ts2020.h" +#include "sp2.h" +#include "si2168.h" +#include "si2157.h" + +#define DVBSKY_MSG_DELAY 0/*2000*/ +#define DVBSKY_BUF_LEN 64 + +static int dvb_usb_dvbsky_disable_rc; +module_param_named(disable_rc, dvb_usb_dvbsky_disable_rc, int, 0644); +MODULE_PARM_DESC(disable_rc, "Disable inbuilt IR receiver."); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct dvbsky_state { + struct mutex stream_mutex; + u8 ibuf[DVBSKY_BUF_LEN]; + u8 obuf[DVBSKY_BUF_LEN]; + u8 last_lock; + struct i2c_client *i2c_client_demod; + struct i2c_client *i2c_client_tuner; + struct i2c_client *i2c_client_ci; + + /* fe hook functions*/ + int (*fe_set_voltage)(struct dvb_frontend *fe, + fe_sec_voltage_t voltage); + int (*fe_read_status)(struct dvb_frontend *fe, + fe_status_t *status); +}; + +static int dvbsky_usb_generic_rw(struct dvb_usb_device *d, + u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) +{ + int ret; + struct dvbsky_state *state = d_to_priv(d); + + mutex_lock(&d->usb_mutex); + if (wlen != 0) + memcpy(state->obuf, wbuf, wlen); + + ret = dvb_usbv2_generic_rw_locked(d, state->obuf, wlen, + state->ibuf, rlen); + + if (!ret && (rlen != 0)) + memcpy(rbuf, state->ibuf, rlen); + + mutex_unlock(&d->usb_mutex); + return ret; +} + +static int dvbsky_stream_ctrl(struct dvb_usb_device *d, u8 onoff) +{ + struct dvbsky_state *state = d_to_priv(d); + int ret; + u8 obuf_pre[3] = { 0x37, 0, 0 }; + u8 obuf_post[3] = { 0x36, 3, 0 }; + + mutex_lock(&state->stream_mutex); + ret = dvbsky_usb_generic_rw(d, obuf_pre, 3, NULL, 0); + if (!ret && onoff) { + msleep(20); + ret = dvbsky_usb_generic_rw(d, obuf_post, 3, NULL, 0); + } + mutex_unlock(&state->stream_mutex); + return ret; +} + +static int dvbsky_streaming_ctrl(struct dvb_frontend *fe, int onoff) +{ + struct dvb_usb_device *d = fe_to_d(fe); + + return dvbsky_stream_ctrl(d, (onoff == 0) ? 0 : 1); +} + +/* GPIO */ +static int dvbsky_gpio_ctrl(struct dvb_usb_device *d, u8 gport, u8 value) +{ + int ret; + u8 obuf[3], ibuf[2]; + + obuf[0] = 0x0e; + obuf[1] = gport; + obuf[2] = value; + ret = dvbsky_usb_generic_rw(d, obuf, 3, ibuf, 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + return ret; +} + +/* I2C */ +static int dvbsky_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret = 0; + u8 ibuf[64], obuf[64]; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + if (num > 2) { + dev_err(&d->udev->dev, + "too many i2c messages[%d], max 2.", num); + ret = -EOPNOTSUPP; + goto i2c_error; + } + + if (num == 1) { + if (msg[0].len > 60) { + dev_err(&d->udev->dev, + "too many i2c bytes[%d], max 60.", + msg[0].len); + ret = -EOPNOTSUPP; + goto i2c_error; + } + if (msg[0].flags & I2C_M_RD) { + /* single read */ + obuf[0] = 0x09; + obuf[1] = 0; + obuf[2] = msg[0].len; + obuf[3] = msg[0].addr; + ret = dvbsky_usb_generic_rw(d, obuf, 4, + ibuf, msg[0].len + 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + if (!ret) + memcpy(msg[0].buf, &ibuf[1], msg[0].len); + } else { + /* write */ + obuf[0] = 0x08; + obuf[1] = msg[0].addr; + obuf[2] = msg[0].len; + memcpy(&obuf[3], msg[0].buf, msg[0].len); + ret = dvbsky_usb_generic_rw(d, obuf, + msg[0].len + 3, ibuf, 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + } + } else { + if ((msg[0].len > 60) || (msg[1].len > 60)) { + dev_err(&d->udev->dev, + "too many i2c bytes[w-%d][r-%d], max 60.", + msg[0].len, msg[1].len); + ret = -EOPNOTSUPP; + goto i2c_error; + } + /* write then read */ + obuf[0] = 0x09; + obuf[1] = msg[0].len; + obuf[2] = msg[1].len; + obuf[3] = msg[0].addr; + memcpy(&obuf[4], msg[0].buf, msg[0].len); + ret = dvbsky_usb_generic_rw(d, obuf, + msg[0].len + 4, ibuf, msg[1].len + 1); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + + if (!ret) + memcpy(msg[1].buf, &ibuf[1], msg[1].len); + } +i2c_error: + mutex_unlock(&d->i2c_mutex); + return (ret) ? ret : num; +} + +static u32 dvbsky_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm dvbsky_i2c_algo = { + .master_xfer = dvbsky_i2c_xfer, + .functionality = dvbsky_i2c_func, +}; + +#if IS_ENABLED(CONFIG_RC_CORE) +static int dvbsky_rc_query(struct dvb_usb_device *d) +{ + u32 code = 0xffff, scancode; + u8 rc5_command, rc5_system; + u8 obuf[2], ibuf[2], toggle; + int ret; + + obuf[0] = 0x10; + ret = dvbsky_usb_generic_rw(d, obuf, 1, ibuf, 2); + if (ret) + dev_err(&d->udev->dev, "failed=%d\n", ret); + if (ret == 0) + code = (ibuf[0] << 8) | ibuf[1]; + if (code != 0xffff) { + dev_dbg(&d->udev->dev, "rc code: %x\n", code); + rc5_command = code & 0x3F; + rc5_system = (code & 0x7C0) >> 6; + toggle = (code & 0x800) ? 1 : 0; + scancode = rc5_system << 8 | rc5_command; + rc_keydown(d->rc_dev, scancode, toggle); + } + return 0; +} + +static int dvbsky_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) +{ + if (dvb_usb_dvbsky_disable_rc) { + rc->map_name = NULL; + return 0; + } + + rc->allowed_protos = RC_BIT_RC5; + rc->query = dvbsky_rc_query; + rc->interval = 300; + return 0; +} +#else + #define dvbsky_get_rc_config NULL +#endif + +static int dvbsky_usb_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct dvbsky_state *state = d_to_priv(d); + u8 value; + + if (voltage == SEC_VOLTAGE_OFF) + value = 0; + else + value = 1; + dvbsky_gpio_ctrl(d, 0x80, value); + + return state->fe_set_voltage(fe, voltage); +} + +static int dvbsky_read_mac_addr(struct dvb_usb_adapter *adap, u8 mac[6]) +{ + struct dvb_usb_device *d = adap_to_d(adap); + u8 obuf[] = { 0x1e, 0x00 }; + u8 ibuf[6] = { 0 }; + struct i2c_msg msg[] = { + { + .addr = 0x51, + .flags = 0, + .buf = obuf, + .len = 2, + }, { + .addr = 0x51, + .flags = I2C_M_RD, + .buf = ibuf, + .len = 6, + } + }; + + if (i2c_transfer(&d->i2c_adap, msg, 2) == 2) + memcpy(mac, ibuf, 6); + + return 0; +} + +static int dvbsky_usb_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct dvbsky_state *state = d_to_priv(d); + int ret; + + ret = state->fe_read_status(fe, status); + + /* it need resync slave fifo when signal change from unlock to lock.*/ + if ((*status & FE_HAS_LOCK) && (!state->last_lock)) + dvbsky_stream_ctrl(d, 1); + + state->last_lock = (*status & FE_HAS_LOCK) ? 1 : 0; + return ret; +} + +static const struct m88ds3103_config dvbsky_s960_m88ds3103_config = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_CI, + .ts_clk = 16000, + .ts_clk_pol = 0, + .agc = 0x99, + .lnb_hv_pol = 1, + .lnb_en_pol = 1, +}; + +static int dvbsky_s960_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + /* demod I2C adapter */ + struct i2c_adapter *i2c_adapter; + struct i2c_client *client; + struct i2c_board_info info; + struct ts2020_config ts2020_config = {}; + memset(&info, 0, sizeof(struct i2c_board_info)); + + /* attach demod */ + adap->fe[0] = dvb_attach(m88ds3103_attach, + &dvbsky_s960_m88ds3103_config, + &d->i2c_adap, + &i2c_adapter); + if (!adap->fe[0]) { + dev_err(&d->udev->dev, "dvbsky_s960_attach fail.\n"); + ret = -ENODEV; + goto fail_attach; + } + + /* attach tuner */ + ts2020_config.fe = adap->fe[0]; + strlcpy(info.type, "ts2020", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &ts2020_config; + request_module("ts2020"); + client = i2c_new_device(i2c_adapter, &info); + if (client == NULL || client->dev.driver == NULL) { + dvb_frontend_detach(adap->fe[0]); + ret = -ENODEV; + goto fail_attach; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + dvb_frontend_detach(adap->fe[0]); + ret = -ENODEV; + goto fail_attach; + } + + /* delegate signal strength measurement to tuner */ + adap->fe[0]->ops.read_signal_strength = + adap->fe[0]->ops.tuner_ops.get_rf_strength; + + /* hook fe: need to resync the slave fifo when signal locks. */ + state->fe_read_status = adap->fe[0]->ops.read_status; + adap->fe[0]->ops.read_status = dvbsky_usb_read_status; + + /* hook fe: LNB off/on is control by Cypress usb chip. */ + state->fe_set_voltage = adap->fe[0]->ops.set_voltage; + adap->fe[0]->ops.set_voltage = dvbsky_usb_set_voltage; + + state->i2c_client_tuner = client; + +fail_attach: + return ret; +} + +static int dvbsky_usb_ci_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct dvb_usb_device *d = fe_to_d(fe); + struct dvbsky_state *state = d_to_priv(d); + u8 value; + + if (voltage == SEC_VOLTAGE_OFF) + value = 0; + else + value = 1; + dvbsky_gpio_ctrl(d, 0x00, value); + + return state->fe_set_voltage(fe, voltage); +} + +static int dvbsky_ci_ctrl(void *priv, u8 read, int addr, + u8 data, int *mem) +{ + struct dvb_usb_device *d = priv; + int ret = 0; + u8 command[4], respond[2], command_size, respond_size; + + command[1] = (u8)((addr >> 8) & 0xff); /*high part of address*/ + command[2] = (u8)(addr & 0xff); /*low part of address*/ + if (read) { + command[0] = 0x71; + command_size = 3; + respond_size = 2; + } else { + command[0] = 0x70; + command[3] = data; + command_size = 4; + respond_size = 1; + } + ret = dvbsky_usb_generic_rw(d, command, command_size, + respond, respond_size); + if (ret) + goto err; + if (read) + *mem = respond[1]; + return ret; +err: + dev_err(&d->udev->dev, "ci control failed=%d\n", ret); + return ret; +} + +static const struct m88ds3103_config dvbsky_s960c_m88ds3103_config = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_CI, + .ts_clk = 10000, + .ts_clk_pol = 1, + .agc = 0x99, + .lnb_hv_pol = 0, + .lnb_en_pol = 1, +}; + +static int dvbsky_s960c_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + /* demod I2C adapter */ + struct i2c_adapter *i2c_adapter; + struct i2c_client *client_tuner, *client_ci; + struct i2c_board_info info; + struct sp2_config sp2_config; + struct ts2020_config ts2020_config = {}; + memset(&info, 0, sizeof(struct i2c_board_info)); + + /* attach demod */ + adap->fe[0] = dvb_attach(m88ds3103_attach, + &dvbsky_s960c_m88ds3103_config, + &d->i2c_adap, + &i2c_adapter); + if (!adap->fe[0]) { + dev_err(&d->udev->dev, "dvbsky_s960ci_attach fail.\n"); + ret = -ENODEV; + goto fail_attach; + } + + /* attach tuner */ + ts2020_config.fe = adap->fe[0]; + strlcpy(info.type, "ts2020", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &ts2020_config; + request_module("ts2020"); + client_tuner = i2c_new_device(i2c_adapter, &info); + if (client_tuner == NULL || client_tuner->dev.driver == NULL) { + ret = -ENODEV; + goto fail_tuner_device; + } + + if (!try_module_get(client_tuner->dev.driver->owner)) { + ret = -ENODEV; + goto fail_tuner_module; + } + + /* attach ci controller */ + memset(&sp2_config, 0, sizeof(sp2_config)); + sp2_config.dvb_adap = &adap->dvb_adap; + sp2_config.priv = d; + sp2_config.ci_control = dvbsky_ci_ctrl; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "sp2", I2C_NAME_SIZE); + info.addr = 0x40; + info.platform_data = &sp2_config; + request_module("sp2"); + client_ci = i2c_new_device(&d->i2c_adap, &info); + if (client_ci == NULL || client_ci->dev.driver == NULL) { + ret = -ENODEV; + goto fail_ci_device; + } + + if (!try_module_get(client_ci->dev.driver->owner)) { + ret = -ENODEV; + goto fail_ci_module; + } + + /* delegate signal strength measurement to tuner */ + adap->fe[0]->ops.read_signal_strength = + adap->fe[0]->ops.tuner_ops.get_rf_strength; + + /* hook fe: need to resync the slave fifo when signal locks. */ + state->fe_read_status = adap->fe[0]->ops.read_status; + adap->fe[0]->ops.read_status = dvbsky_usb_read_status; + + /* hook fe: LNB off/on is control by Cypress usb chip. */ + state->fe_set_voltage = adap->fe[0]->ops.set_voltage; + adap->fe[0]->ops.set_voltage = dvbsky_usb_ci_set_voltage; + + state->i2c_client_tuner = client_tuner; + state->i2c_client_ci = client_ci; + return ret; +fail_ci_module: + i2c_unregister_device(client_ci); +fail_ci_device: + module_put(client_tuner->dev.driver->owner); +fail_tuner_module: + i2c_unregister_device(client_tuner); +fail_tuner_device: + dvb_frontend_detach(adap->fe[0]); +fail_attach: + return ret; +} + +static int dvbsky_t680c_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + struct i2c_adapter *i2c_adapter; + struct i2c_client *client_demod, *client_tuner, *client_ci; + struct i2c_board_info info; + struct si2168_config si2168_config; + struct si2157_config si2157_config; + struct sp2_config sp2_config; + + /* attach demod */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &i2c_adapter; + si2168_config.fe = &adap->fe[0]; + si2168_config.ts_mode = SI2168_TS_PARALLEL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + + request_module(info.type); + client_demod = i2c_new_device(&d->i2c_adap, &info); + if (client_demod == NULL || + client_demod->dev.driver == NULL) + goto fail_demod_device; + if (!try_module_get(client_demod->dev.driver->owner)) + goto fail_demod_module; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = adap->fe[0]; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + + request_module(info.type); + client_tuner = i2c_new_device(i2c_adapter, &info); + if (client_tuner == NULL || + client_tuner->dev.driver == NULL) + goto fail_tuner_device; + if (!try_module_get(client_tuner->dev.driver->owner)) + goto fail_tuner_module; + + /* attach ci controller */ + memset(&sp2_config, 0, sizeof(sp2_config)); + sp2_config.dvb_adap = &adap->dvb_adap; + sp2_config.priv = d; + sp2_config.ci_control = dvbsky_ci_ctrl; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "sp2", I2C_NAME_SIZE); + info.addr = 0x40; + info.platform_data = &sp2_config; + + request_module(info.type); + client_ci = i2c_new_device(&d->i2c_adap, &info); + + if (client_ci == NULL || client_ci->dev.driver == NULL) + goto fail_ci_device; + + if (!try_module_get(client_ci->dev.driver->owner)) + goto fail_ci_module; + + state->i2c_client_demod = client_demod; + state->i2c_client_tuner = client_tuner; + state->i2c_client_ci = client_ci; + return ret; +fail_ci_module: + i2c_unregister_device(client_ci); +fail_ci_device: + module_put(client_tuner->dev.driver->owner); +fail_tuner_module: + i2c_unregister_device(client_tuner); +fail_tuner_device: + module_put(client_demod->dev.driver->owner); +fail_demod_module: + i2c_unregister_device(client_demod); +fail_demod_device: + ret = -ENODEV; + return ret; +} + +static int dvbsky_t330_attach(struct dvb_usb_adapter *adap) +{ + struct dvbsky_state *state = adap_to_priv(adap); + struct dvb_usb_device *d = adap_to_d(adap); + int ret = 0; + struct i2c_adapter *i2c_adapter; + struct i2c_client *client_demod, *client_tuner; + struct i2c_board_info info; + struct si2168_config si2168_config; + struct si2157_config si2157_config; + + /* attach demod */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &i2c_adapter; + si2168_config.fe = &adap->fe[0]; + si2168_config.ts_mode = SI2168_TS_PARALLEL | 0x40; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + + request_module(info.type); + client_demod = i2c_new_device(&d->i2c_adap, &info); + if (client_demod == NULL || + client_demod->dev.driver == NULL) + goto fail_demod_device; + if (!try_module_get(client_demod->dev.driver->owner)) + goto fail_demod_module; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = adap->fe[0]; + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + + request_module(info.type); + client_tuner = i2c_new_device(i2c_adapter, &info); + if (client_tuner == NULL || + client_tuner->dev.driver == NULL) + goto fail_tuner_device; + if (!try_module_get(client_tuner->dev.driver->owner)) + goto fail_tuner_module; + + state->i2c_client_demod = client_demod; + state->i2c_client_tuner = client_tuner; + return ret; +fail_tuner_module: + i2c_unregister_device(client_tuner); +fail_tuner_device: + module_put(client_demod->dev.driver->owner); +fail_demod_module: + i2c_unregister_device(client_demod); +fail_demod_device: + ret = -ENODEV; + return ret; +} + +static int dvbsky_identify_state(struct dvb_usb_device *d, const char **name) +{ + dvbsky_gpio_ctrl(d, 0x04, 1); + msleep(20); + dvbsky_gpio_ctrl(d, 0x83, 0); + dvbsky_gpio_ctrl(d, 0xc0, 1); + msleep(100); + dvbsky_gpio_ctrl(d, 0x83, 1); + dvbsky_gpio_ctrl(d, 0xc0, 0); + msleep(50); + + return WARM; +} + +static int dvbsky_init(struct dvb_usb_device *d) +{ + struct dvbsky_state *state = d_to_priv(d); + + /* use default interface */ + /* + ret = usb_set_interface(d->udev, 0, 0); + if (ret) + return ret; + */ + mutex_init(&state->stream_mutex); + + state->last_lock = 0; + + return 0; +} + +static void dvbsky_exit(struct dvb_usb_device *d) +{ + struct dvbsky_state *state = d_to_priv(d); + struct i2c_client *client; + + client = state->i2c_client_tuner; + /* remove I2C tuner */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + client = state->i2c_client_demod; + /* remove I2C demod */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + client = state->i2c_client_ci; + /* remove I2C ci */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } +} + +/* DVB USB Driver stuff */ +static struct dvb_usb_device_properties dvbsky_s960_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_s960_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static struct dvb_usb_device_properties dvbsky_s960c_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_s960c_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static struct dvb_usb_device_properties dvbsky_t680c_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_t680c_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static struct dvb_usb_device_properties dvbsky_t330_props = { + .driver_name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .adapter_nr = adapter_nr, + .size_of_priv = sizeof(struct dvbsky_state), + + .generic_bulk_ctrl_endpoint = 0x01, + .generic_bulk_ctrl_endpoint_response = 0x81, + .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY, + + .i2c_algo = &dvbsky_i2c_algo, + .frontend_attach = dvbsky_t330_attach, + .init = dvbsky_init, + .get_rc_config = dvbsky_get_rc_config, + .streaming_ctrl = dvbsky_streaming_ctrl, + .identify_state = dvbsky_identify_state, + .exit = dvbsky_exit, + .read_mac_address = dvbsky_read_mac_addr, + + .num_adapters = 1, + .adapter = { + { + .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096), + } + } +}; + +static const struct usb_device_id dvbsky_id_table[] = { + { DVB_USB_DEVICE(0x0572, 0x6831, + &dvbsky_s960_props, "DVBSky S960/S860", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(0x0572, 0x960c, + &dvbsky_s960c_props, "DVBSky S960CI", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(0x0572, 0x680c, + &dvbsky_t680c_props, "DVBSky T680CI", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(0x0572, 0x0320, + &dvbsky_t330_props, "DVBSky T330", RC_MAP_DVBSKY) }, + { DVB_USB_DEVICE(USB_VID_TECHNOTREND, + USB_PID_TECHNOTREND_TVSTICK_CT2_4400, + &dvbsky_t330_props, "TechnoTrend TVStick CT2-4400", + RC_MAP_TT_1500) }, + { DVB_USB_DEVICE(USB_VID_TECHNOTREND, + USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI, + &dvbsky_t680c_props, "TechnoTrend TT-connect CT2-4650 CI", + RC_MAP_TT_1500) }, + { } +}; +MODULE_DEVICE_TABLE(usb, dvbsky_id_table); + +static struct usb_driver dvbsky_usb_driver = { + .name = KBUILD_MODNAME, + .id_table = dvbsky_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(dvbsky_usb_driver); + +MODULE_AUTHOR("Max nibble "); +MODULE_DESCRIPTION("Driver for DVBSky USB"); +MODULE_LICENSE("GPL"); --- a/drivers/media/rc/keymaps/Makefile 2013-12-12 14:38:07.000000000 +0800 +++ b/drivers/media/rc/keymaps/Makefile 2013-12-18 20:02:46.202844414 +0800 @@ -28,6 +28,7 @@ rc-dm1105-nec.o \ rc-dntv-live-dvb-t.o \ rc-dntv-live-dvbt-pro.o \ + rc-dvbsky.o \ rc-em-terratec.o \ rc-encore-enltv2.o \ rc-encore-enltv.o \ diff -urN a/drivers/media/rc/keymaps/rc-dvbsky.c b/drivers/media/rc/keymaps/rc-dvbsky.c --- a/drivers/media/rc/keymaps/rc-dvbsky.c 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/media/rc/keymaps/rc-dvbsky.c 2013-12-18 20:02:49.870844372 +0800 @@ -0,0 +1,78 @@ +/* rc-dvbsky.c - Keytable for Dvbsky Remote Controllers + * + * keymap imported from ir-keymaps.c + * + * + * Copyright (c) 2010-2012 by Nibble Max + * + * 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. + */ + +#include +#include +/* + * This table contains the complete RC5 code, instead of just the data part + */ + +static struct rc_map_table rc5_dvbsky[] = { + { 0x0000, KEY_0 }, + { 0x0001, KEY_1 }, + { 0x0002, KEY_2 }, + { 0x0003, KEY_3 }, + { 0x0004, KEY_4 }, + { 0x0005, KEY_5 }, + { 0x0006, KEY_6 }, + { 0x0007, KEY_7 }, + { 0x0008, KEY_8 }, + { 0x0009, KEY_9 }, + { 0x000a, KEY_MUTE }, + { 0x000d, KEY_OK }, + { 0x000b, KEY_STOP }, + { 0x000c, KEY_EXIT }, + { 0x000e, KEY_CAMERA }, /*Snap shot*/ + { 0x000f, KEY_SUBTITLE }, /*PIP*/ + { 0x0010, KEY_VOLUMEUP }, + { 0x0011, KEY_VOLUMEDOWN }, + { 0x0012, KEY_FAVORITES }, + { 0x0013, KEY_LIST }, /*Info*/ + { 0x0016, KEY_PAUSE }, + { 0x0017, KEY_PLAY }, + { 0x001f, KEY_RECORD }, + { 0x0020, KEY_CHANNELDOWN }, + { 0x0021, KEY_CHANNELUP }, + { 0x0025, KEY_POWER2 }, + { 0x0026, KEY_REWIND }, + { 0x0027, KEY_FASTFORWARD }, + { 0x0029, KEY_LAST }, + { 0x002b, KEY_MENU }, + { 0x002c, KEY_EPG }, + { 0x002d, KEY_ZOOM }, +}; + +static struct rc_map_list rc5_dvbsky_map = { + .map = { + .scan = rc5_dvbsky, + .size = ARRAY_SIZE(rc5_dvbsky), + .rc_type = RC_TYPE_RC5, + .name = RC_MAP_DVBSKY, + } +}; + +static int __init init_rc_map_rc5_dvbsky(void) +{ + return rc_map_register(&rc5_dvbsky_map); +} + +static void __exit exit_rc_map_rc5_dvbsky(void) +{ + rc_map_unregister(&rc5_dvbsky_map); +} + +module_init(init_rc_map_rc5_dvbsky) +module_exit(exit_rc_map_rc5_dvbsky) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nibble Max "); --- a/include/media/rc-map.h 2013-12-12 14:38:07.000000000 +0800 +++ b/include/media/rc-map.h 2013-12-18 19:59:03.122846921 +0800 @@ -119,6 +119,7 @@ #define RC_MAP_DM1105_NEC "rc-dm1105-nec" #define RC_MAP_DNTV_LIVE_DVBT_PRO "rc-dntv-live-dvbt-pro" #define RC_MAP_DNTV_LIVE_DVB_T "rc-dntv-live-dvb-t" +#define RC_MAP_DVBSKY "rc-dvbsky" #define RC_MAP_EMPTY "rc-empty" #define RC_MAP_EM_TERRATEC "rc-em-terratec" #define RC_MAP_ENCORE_ENLTV2 "rc-encore-enltv2" --- a/drivers/media/dvb-frontends/ts2020.c +++ b/drivers/media/dvb-frontends/ts2020.c @@ -26,12 +26,23 @@ #define FREQ_OFFSET_LOW_SYM_RATE 3000 struct ts2020_priv { + struct dvb_frontend *fe; /* i2c details */ int i2c_address; struct i2c_adapter *i2c; - u8 clk_out_div; + u8 clk_out:2; + u8 clk_out_div:5; u32 frequency; u32 frequency_div; +#define TS2020_M88TS2020 0 +#define TS2020_M88TS2022 1 + u8 tuner; + u8 loop_through:1; +}; + +struct ts2020_reg_val { + u8 reg; + u8 val; }; static int ts2020_release(struct dvb_frontend *fe) @@ -112,40 +123,77 @@ static int ts2020_sleep(struct dvb_frontend *fe) { struct ts2020_priv *priv = fe->tuner_priv; - int ret; - u8 buf[] = { 10, 0 }; - struct i2c_msg msg = { - .addr = priv->i2c_address, - .flags = 0, - .buf = buf, - .len = 2 - }; + u8 u8tmp; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - ret = i2c_transfer(priv->i2c, &msg, 1); - if (ret != 1) - printk(KERN_ERR "%s: i2c error\n", __func__); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); + if (priv->tuner == TS2020_M88TS2020) + u8tmp = 0x0a; /* XXX: probably wrong */ + else + u8tmp = 0x00; - return (ret == 1) ? 0 : ret; + return ts2020_writereg(fe, u8tmp, 0x00); } static int ts2020_init(struct dvb_frontend *fe) { struct ts2020_priv *priv = fe->tuner_priv; + int i; + u8 u8tmp; + + if (priv->tuner == TS2020_M88TS2020) { + ts2020_writereg(fe, 0x42, 0x73); + ts2020_writereg(fe, 0x05, priv->clk_out_div); + ts2020_writereg(fe, 0x20, 0x27); + ts2020_writereg(fe, 0x07, 0x02); + ts2020_writereg(fe, 0x11, 0xff); + ts2020_writereg(fe, 0x60, 0xf9); + ts2020_writereg(fe, 0x08, 0x01); + ts2020_writereg(fe, 0x00, 0x41); + } else { + static const struct ts2020_reg_val reg_vals[] = { + {0x7d, 0x9d}, + {0x7c, 0x9a}, + {0x7a, 0x76}, + {0x3b, 0x01}, + {0x63, 0x88}, + {0x61, 0x85}, + {0x22, 0x30}, + {0x30, 0x40}, + {0x20, 0x23}, + {0x24, 0x02}, + {0x12, 0xa0}, + }; + + ts2020_writereg(fe, 0x00, 0x01); + ts2020_writereg(fe, 0x00, 0x03); + + switch (priv->clk_out) { + case TS2020_CLK_OUT_DISABLED: + u8tmp = 0x60; + break; + case TS2020_CLK_OUT_ENABLED: + u8tmp = 0x70; + ts2020_writereg(fe, 0x05, priv->clk_out_div); + break; + case TS2020_CLK_OUT_ENABLED_XTALOUT: + u8tmp = 0x6c; + break; + default: + u8tmp = 0x60; + break; + } + + ts2020_writereg(fe, 0x42, u8tmp); + + if (priv->loop_through) + u8tmp = 0xec; + else + u8tmp = 0x6c; - ts2020_writereg(fe, 0x42, 0x73); - ts2020_writereg(fe, 0x05, priv->clk_out_div); - ts2020_writereg(fe, 0x20, 0x27); - ts2020_writereg(fe, 0x07, 0x02); - ts2020_writereg(fe, 0x11, 0xff); - ts2020_writereg(fe, 0x60, 0xf9); - ts2020_writereg(fe, 0x08, 0x01); - ts2020_writereg(fe, 0x00, 0x41); + ts2020_writereg(fe, 0x62, u8tmp); + + for (i = 0; i < ARRAY_SIZE(reg_vals); i++) + ts2020_writereg(fe, reg_vals[i].reg, reg_vals[i].val); + } return 0; } @@ -203,7 +251,14 @@ ndiv = ndiv + ndiv % 2; ndiv = ndiv - 1024; - ret = ts2020_writereg(fe, 0x10, 0x80 | lo); + if (priv->tuner == TS2020_M88TS2020) { + lpf_coeff = 2766; + ret = ts2020_writereg(fe, 0x10, 0x80 | lo); + } else { + lpf_coeff = 3200; + ret = ts2020_writereg(fe, 0x10, 0x0b); + ret |= ts2020_writereg(fe, 0x11, 0x40); + } /* Set frequency divider */ ret |= ts2020_writereg(fe, 0x01, (ndiv >> 8) & 0xf); @@ -220,7 +275,8 @@ ret |= ts2020_tuner_gate_ctrl(fe, 0x08); /* Tuner RF */ - ret |= ts2020_set_tuner_rf(fe); + if (priv->tuner == TS2020_M88TS2020) + ret |= ts2020_set_tuner_rf(fe); gdiv28 = (TS2020_XTAL_FREQ / 1000 * 1694 + 500) / 1000; ret |= ts2020_writereg(fe, 0x04, gdiv28 & 0xff); @@ -228,6 +284,15 @@ if (ret < 0) return -ENODEV; + if (priv->tuner == TS2020_M88TS2022) { + ret = ts2020_writereg(fe, 0x25, 0x00); + ret |= ts2020_writereg(fe, 0x27, 0x70); + ret |= ts2020_writereg(fe, 0x41, 0x09); + ret |= ts2020_writereg(fe, 0x08, 0x0b); + if (ret < 0) + return -ENODEV; + } + value = ts2020_readreg(fe, 0x26); f3db = (symbol_rate * 135) / 200 + 2000; @@ -243,8 +308,6 @@ if (mlpf_max > 63) mlpf_max = 63; - lpf_coeff = 2766; - nlpf = (f3db * gdiv28 * 2 / lpf_coeff / (TS2020_XTAL_FREQ / 1000) + 1) / 2; if (nlpf > 23) @@ -285,6 +348,13 @@ { struct ts2020_priv *priv = fe->tuner_priv; *frequency = priv->frequency; + + return 0; +} + +static int ts2020_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + *frequency = 0; /* Zero-IF */ return 0; } @@ -324,6 +394,7 @@ .sleep = ts2020_sleep, .set_params = ts2020_set_params, .get_frequency = ts2020_get_frequency, + .get_if_frequency = ts2020_get_if_frequency, .get_rf_strength = ts2020_read_signal_strength, }; @@ -340,8 +411,10 @@ priv->i2c_address = config->tuner_address; priv->i2c = i2c; + priv->clk_out = config->clk_out; priv->clk_out_div = config->clk_out_div; priv->frequency_div = config->frequency_div; + priv->fe = fe; fe->tuner_priv = priv; if (!priv->frequency_div) @@ -358,9 +431,13 @@ /* Check the tuner version */ buf = ts2020_readreg(fe, 0x00); - if ((buf == 0x01) || (buf == 0x41) || (buf == 0x81)) + if ((buf == 0x01) || (buf == 0x41) || (buf == 0x81)) { printk(KERN_INFO "%s: Find tuner TS2020!\n", __func__); - else { + priv->tuner = TS2020_M88TS2020; + } else if ((buf == 0x83) || (buf == 0xc3)) { + printk(KERN_INFO "%s: Find tuner TS2022!\n", __func__); + priv->tuner = TS2020_M88TS2022; + } else { printk(KERN_ERR "%s: Read tuner reg[0] = %d\n", __func__, buf); kfree(priv); return NULL; @@ -373,6 +450,165 @@ } EXPORT_SYMBOL(ts2020_attach); +static int ts2020_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ts2020_config *pdata = client->dev.platform_data; + struct dvb_frontend *fe = pdata->fe; + struct ts2020_priv *dev; + int ret; + u8 u8tmp; + unsigned int utmp; + char *chip_str; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err; + } + + dev->i2c = client->adapter; + dev->i2c_address = client->addr; + dev->clk_out = pdata->clk_out; + dev->clk_out_div = pdata->clk_out_div; + dev->frequency_div = pdata->frequency_div; + dev->fe = fe; + fe->tuner_priv = dev; + + /* check if the tuner is there */ + ret = ts2020_readreg(fe, 0x00); + if (ret < 0) + goto err; + utmp = ret; + + if ((utmp & 0x03) == 0x00) { + ret = ts2020_writereg(fe, 0x00, 0x01); + if (ret) + goto err; + + usleep_range(2000, 50000); + } + + ret = ts2020_writereg(fe, 0x00, 0x03); + if (ret) + goto err; + + usleep_range(2000, 50000); + + ret = ts2020_readreg(fe, 0x00); + if (ret < 0) + goto err; + utmp = ret; + + dev_dbg(&client->dev, "chip_id=%02x\n", utmp); + + switch (utmp) { + case 0x01: + case 0x41: + case 0x81: + dev->tuner = TS2020_M88TS2020; + chip_str = "TS2020"; + if (!dev->frequency_div) + dev->frequency_div = 1060000; + break; + case 0xc3: + case 0x83: + dev->tuner = TS2020_M88TS2022; + chip_str = "TS2022"; + if (!dev->frequency_div) + dev->frequency_div = 1103000; + break; + default: + ret = -ENODEV; + goto err; + } + + if (dev->tuner == TS2020_M88TS2022) { + switch (dev->clk_out) { + case TS2020_CLK_OUT_DISABLED: + u8tmp = 0x60; + break; + case TS2020_CLK_OUT_ENABLED: + u8tmp = 0x70; + ret = ts2020_writereg(fe, 0x05, dev->clk_out_div); + if (ret) + goto err; + break; + case TS2020_CLK_OUT_ENABLED_XTALOUT: + u8tmp = 0x6c; + break; + default: + ret = -EINVAL; + goto err; + } + + ret = ts2020_writereg(fe, 0x42, u8tmp); + if (ret) + goto err; + + if (dev->loop_through) + u8tmp = 0xec; + else + u8tmp = 0x6c; + + ret = ts2020_writereg(fe, 0x62, u8tmp); + if (ret) + goto err; + } + + /* sleep */ + ret = ts2020_writereg(fe, 0x00, 0x00); + if (ret) + goto err; + + dev_info(&client->dev, + "Montage Technology %s successfully identified\n", chip_str); + + memcpy(&fe->ops.tuner_ops, &ts2020_tuner_ops, + sizeof(struct dvb_tuner_ops)); + fe->ops.tuner_ops.release = NULL; + + i2c_set_clientdata(client, dev); + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + kfree(dev); + return ret; +} + +static int ts2020_remove(struct i2c_client *client) +{ + struct ts2020_priv *dev = i2c_get_clientdata(client); + struct dvb_frontend *fe = dev->fe; + + dev_dbg(&client->dev, "\n"); + + memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops)); + fe->tuner_priv = NULL; + kfree(dev); + + return 0; +} + +static const struct i2c_device_id ts2020_id_table[] = { + {"ts2020", 0}, + {"ts2022", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ts2020_id_table); + +static struct i2c_driver ts2020_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ts2020", + }, + .probe = ts2020_probe, + .remove = ts2020_remove, + .id_table = ts2020_id_table, +}; + +module_i2c_driver(ts2020_driver); + MODULE_AUTHOR("Konstantin Dimitrov "); MODULE_DESCRIPTION("Montage Technology TS2020 - Silicon tuner driver module"); MODULE_LICENSE("GPL"); --- a/drivers/media/dvb-frontends/ts2020.h +++ b/drivers/media/dvb-frontends/ts2020.h @@ -27,11 +27,34 @@ struct ts2020_config { u8 tuner_address; - u8 clk_out_div; u32 frequency_div; + + /* + * RF loop-through + */ + u8 loop_through:1; + + /* + * clock output + */ +#define TS2020_CLK_OUT_DISABLED 0 +#define TS2020_CLK_OUT_ENABLED 1 +#define TS2020_CLK_OUT_ENABLED_XTALOUT 2 + u8 clk_out:2; + + /* + * clock output divider + * 1 - 31 + */ + u8 clk_out_div:5; + + /* + * pointer to DVB frontend + */ + struct dvb_frontend *fe; }; -#if IS_ENABLED(CONFIG_DVB_TS2020) +#if IS_ENABLED(CONFIG_DVB_TS2020) extern struct dvb_frontend *ts2020_attach( struct dvb_frontend *fe, --- a/drivers/media/dvb-frontends/m88ds3103.h +++ b/drivers/media/dvb-frontends/m88ds3103.h @@ -47,14 +47,23 @@ */ #define M88DS3103_TS_SERIAL 0 /* TS output pin D0, normal */ #define M88DS3103_TS_SERIAL_D7 1 /* TS output pin D7 */ -#define M88DS3103_TS_PARALLEL 2 /* 24 MHz, normal */ -#define M88DS3103_TS_PARALLEL_12 3 /* 12 MHz */ -#define M88DS3103_TS_PARALLEL_16 4 /* 16 MHz */ -#define M88DS3103_TS_PARALLEL_19_2 5 /* 19.2 MHz */ -#define M88DS3103_TS_CI 6 /* 6 MHz */ +#define M88DS3103_TS_PARALLEL 2 /* TS Parallel mode */ +#define M88DS3103_TS_CI 3 /* TS CI Mode */ u8 ts_mode; /* + * TS clk in KHz + * Default: 0. + */ + u32 ts_clk; + + /* + * TS clk polarity. + * Default: 0. 1-active at falling edge; 0-active at rising edge. + */ + u8 ts_clk_pol:1; + + /* * spectrum inversion * Default: 0 */ @@ -86,6 +95,22 @@ * Default: none, must set */ u8 agc; + + /* + * LNB H/V pin polarity + * Default: 0. + * 1: pin high set to VOLTAGE_13, pin low to set VOLTAGE_18. + * 0: pin high set to VOLTAGE_18, pin low to set VOLTAGE_13. + */ + u8 lnb_hv_pol:1; + + /* + * LNB enable pin polarity + * Default: 0. + * 1: pin high to enable, pin low to disable. + * 0: pin high to disable, pin low to enable. + */ + u8 lnb_en_pol:1; }; /* --- a/drivers/media/dvb-frontends/m88ds3103.c +++ b/drivers/media/dvb-frontends/m88ds3103.c @@ -1,5 +1,5 @@ /* - * Montage M88DS3103 demodulator driver + * Montage M88DS3103/M88RS6000 demodulator driver * * Copyright (C) 2013 Antti Palosaari * @@ -159,9 +159,10 @@ { int ret, i, j; u8 buf[83]; + dev_dbg(&priv->i2c->dev, "%s: tab_len=%d\n", __func__, tab_len); - if (tab_len > 83) { + if (tab_len > 86) { ret = -EINVAL; goto err; } @@ -244,11 +245,12 @@ struct dtv_frontend_properties *c = &fe->dtv_property_cache; int ret, len; const struct m88ds3103_reg_val *init; - u8 u8tmp, u8tmp1, u8tmp2; - u8 buf[2]; - u16 u16tmp, divide_ratio; - u32 tuner_frequency, target_mclk, ts_clk; + u8 u8tmp, u8tmp1 = 0, u8tmp2 = 0; /* silence compiler warning */ + u8 buf[3]; + u16 u16tmp, divide_ratio = 0; + u32 tuner_frequency, target_mclk; s32 s32tmp; + dev_dbg(&priv->i2c->dev, "%s: delivery_system=%d modulation=%d frequency=%d symbol_rate=%d inversion=%d pilot=%d rolloff=%d\n", __func__, c->delivery_system, @@ -260,6 +262,22 @@ goto err; } + /* reset */ + ret = m88ds3103_wr_reg(priv, 0x07, 0x80); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x07, 0x00); + if (ret) + goto err; + + /* Disable demod clock path */ + if (priv->chip_id == M88RS6000_CHIP_ID) { + ret = m88ds3103_wr_reg(priv, 0x06, 0xe0); + if (ret) + goto err; + } + /* program tuner */ if (fe->ops.tuner_ops.set_params) { ret = fe->ops.tuner_ops.set_params(fe); @@ -271,54 +289,53 @@ ret = fe->ops.tuner_ops.get_frequency(fe, &tuner_frequency); if (ret) goto err; + } else { + /* + * Use nominal target frequency as tuner driver does not provide + * actual frequency used. Carrier offset calculation is not + * valid. + */ + tuner_frequency = c->frequency; } - /* reset */ - ret = m88ds3103_wr_reg(priv, 0x07, 0x80); - if (ret) - goto err; - - ret = m88ds3103_wr_reg(priv, 0x07, 0x00); - if (ret) - goto err; - - ret = m88ds3103_wr_reg(priv, 0xb2, 0x01); - if (ret) - goto err; + /* select M88RS6000 demod main mclk and ts mclk from tuner die. */ + if (priv->chip_id == M88RS6000_CHIP_ID) { + if (c->symbol_rate > 45010000) + priv->mclk_khz = 110250; + else + priv->mclk_khz = 96000; - ret = m88ds3103_wr_reg(priv, 0x00, 0x01); - if (ret) - goto err; + if (c->delivery_system == SYS_DVBS) + target_mclk = 96000; + else + target_mclk = 144000; - switch (c->delivery_system) { - case SYS_DVBS: - len = ARRAY_SIZE(m88ds3103_dvbs_init_reg_vals); - init = m88ds3103_dvbs_init_reg_vals; - target_mclk = 96000; - break; - case SYS_DVBS2: - len = ARRAY_SIZE(m88ds3103_dvbs2_init_reg_vals); - init = m88ds3103_dvbs2_init_reg_vals; + /* Enable demod clock path */ + ret = m88ds3103_wr_reg(priv, 0x06, 0x00); + if (ret) + goto err; + usleep_range(10000, 20000); + } else { + /* set M88DS3103 mclk and ts mclk. */ + priv->mclk_khz = 96000; switch (priv->cfg->ts_mode) { case M88DS3103_TS_SERIAL: case M88DS3103_TS_SERIAL_D7: - if (c->symbol_rate < 18000000) - target_mclk = 96000; - else - target_mclk = 144000; + target_mclk = priv->cfg->ts_clk; break; case M88DS3103_TS_PARALLEL: - case M88DS3103_TS_PARALLEL_12: - case M88DS3103_TS_PARALLEL_16: - case M88DS3103_TS_PARALLEL_19_2: case M88DS3103_TS_CI: - if (c->symbol_rate < 18000000) + if (c->delivery_system == SYS_DVBS) target_mclk = 96000; - else if (c->symbol_rate < 28000000) - target_mclk = 144000; - else - target_mclk = 192000; + else { + if (c->symbol_rate < 18000000) + target_mclk = 96000; + else if (c->symbol_rate < 28000000) + target_mclk = 144000; + else + target_mclk = 192000; + } break; default: dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", @@ -326,6 +343,55 @@ ret = -EINVAL; goto err; } + + switch (target_mclk) { + case 96000: + u8tmp1 = 0x02; /* 0b10 */ + u8tmp2 = 0x01; /* 0b01 */ + break; + case 144000: + u8tmp1 = 0x00; /* 0b00 */ + u8tmp2 = 0x01; /* 0b01 */ + break; + case 192000: + u8tmp1 = 0x03; /* 0b11 */ + u8tmp2 = 0x00; /* 0b00 */ + break; + } + ret = m88ds3103_wr_reg_mask(priv, 0x22, u8tmp1 << 6, 0xc0); + if (ret) + goto err; + ret = m88ds3103_wr_reg_mask(priv, 0x24, u8tmp2 << 6, 0xc0); + if (ret) + goto err; + } + + ret = m88ds3103_wr_reg(priv, 0xb2, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x00, 0x01); + if (ret) + goto err; + + switch (c->delivery_system) { + case SYS_DVBS: + if (priv->chip_id == M88RS6000_CHIP_ID) { + len = ARRAY_SIZE(m88rs6000_dvbs_init_reg_vals); + init = m88rs6000_dvbs_init_reg_vals; + } else { + len = ARRAY_SIZE(m88ds3103_dvbs_init_reg_vals); + init = m88ds3103_dvbs_init_reg_vals; + } + break; + case SYS_DVBS2: + if (priv->chip_id == M88RS6000_CHIP_ID) { + len = ARRAY_SIZE(m88rs6000_dvbs2_init_reg_vals); + init = m88rs6000_dvbs2_init_reg_vals; + } else { + len = ARRAY_SIZE(m88ds3103_dvbs2_init_reg_vals); + init = m88ds3103_dvbs2_init_reg_vals; + } break; default: dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", @@ -341,37 +407,44 @@ goto err; } - u8tmp1 = 0; /* silence compiler warning */ + if (priv->chip_id == M88RS6000_CHIP_ID) { + if ((c->delivery_system == SYS_DVBS2) + && ((c->symbol_rate / 1000) <= 5000)) { + ret = m88ds3103_wr_reg(priv, 0xc0, 0x04); + if (ret) + goto err; + buf[0] = 0x09; + buf[1] = 0x22; + buf[2] = 0x88; + ret = m88ds3103_wr_regs(priv, 0x8a, buf, 3); + if (ret) + goto err; + } + ret = m88ds3103_wr_reg_mask(priv, 0x9d, 0x08, 0x08); + if (ret) + goto err; + ret = m88ds3103_wr_reg(priv, 0xf1, 0x01); + if (ret) + goto err; + ret = m88ds3103_wr_reg_mask(priv, 0x30, 0x80, 0x80); + if (ret) + goto err; + } + switch (priv->cfg->ts_mode) { case M88DS3103_TS_SERIAL: u8tmp1 = 0x00; - ts_clk = 0; - u8tmp = 0x46; + u8tmp = 0x06; break; case M88DS3103_TS_SERIAL_D7: u8tmp1 = 0x20; - ts_clk = 0; - u8tmp = 0x46; + u8tmp = 0x06; break; case M88DS3103_TS_PARALLEL: - ts_clk = 24000; - u8tmp = 0x42; - break; - case M88DS3103_TS_PARALLEL_12: - ts_clk = 12000; - u8tmp = 0x42; - break; - case M88DS3103_TS_PARALLEL_16: - ts_clk = 16000; - u8tmp = 0x42; - break; - case M88DS3103_TS_PARALLEL_19_2: - ts_clk = 19200; - u8tmp = 0x42; + u8tmp = 0x02; break; case M88DS3103_TS_CI: - ts_clk = 6000; - u8tmp = 0x43; + u8tmp = 0x03; break; default: dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", __func__); @@ -379,6 +452,9 @@ goto err; } + if (priv->cfg->ts_clk_pol) + u8tmp |= 0x40; + /* TS mode */ ret = m88ds3103_wr_reg(priv, 0xfd, u8tmp); if (ret) @@ -390,21 +466,20 @@ ret = m88ds3103_wr_reg_mask(priv, 0x29, u8tmp1, 0x20); if (ret) goto err; - } - - if (ts_clk) { - divide_ratio = DIV_ROUND_UP(target_mclk, ts_clk); - u8tmp1 = divide_ratio / 2; - u8tmp2 = DIV_ROUND_UP(divide_ratio, 2); - } else { - divide_ratio = 0; u8tmp1 = 0; u8tmp2 = 0; + break; + default: + if (priv->cfg->ts_clk) { + divide_ratio = DIV_ROUND_UP(target_mclk, priv->cfg->ts_clk); + u8tmp1 = divide_ratio / 2; + u8tmp2 = DIV_ROUND_UP(divide_ratio, 2); + } } dev_dbg(&priv->i2c->dev, "%s: target_mclk=%d ts_clk=%d divide_ratio=%d\n", - __func__, target_mclk, ts_clk, divide_ratio); + __func__, target_mclk, priv->cfg->ts_clk, divide_ratio); u8tmp1--; u8tmp2--; @@ -427,41 +502,6 @@ if (ret) goto err; - switch (target_mclk) { - case 72000: - u8tmp1 = 0x00; /* 0b00 */ - u8tmp2 = 0x03; /* 0b11 */ - break; - case 96000: - u8tmp1 = 0x02; /* 0b10 */ - u8tmp2 = 0x01; /* 0b01 */ - break; - case 115200: - u8tmp1 = 0x01; /* 0b01 */ - u8tmp2 = 0x01; /* 0b01 */ - break; - case 144000: - u8tmp1 = 0x00; /* 0b00 */ - u8tmp2 = 0x01; /* 0b01 */ - break; - case 192000: - u8tmp1 = 0x03; /* 0b11 */ - u8tmp2 = 0x00; /* 0b00 */ - break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid target_mclk\n", __func__); - ret = -EINVAL; - goto err; - } - - ret = m88ds3103_wr_reg_mask(priv, 0x22, u8tmp1 << 6, 0xc0); - if (ret) - goto err; - - ret = m88ds3103_wr_reg_mask(priv, 0x24, u8tmp2 << 6, 0xc0); - if (ret) - goto err; - if (c->symbol_rate <= 3000000) u8tmp = 0x20; else if (c->symbol_rate <= 10000000) @@ -485,7 +525,7 @@ if (ret) goto err; - u16tmp = DIV_ROUND_CLOSEST((c->symbol_rate / 1000) << 15, M88DS3103_MCLK_KHZ / 2); + u16tmp = DIV_ROUND_CLOSEST((c->symbol_rate / 1000) << 15, priv->mclk_khz / 2); buf[0] = (u16tmp >> 0) & 0xff; buf[1] = (u16tmp >> 8) & 0xff; ret = m88ds3103_wr_regs(priv, 0x61, buf, 2); @@ -508,7 +548,7 @@ (tuner_frequency - c->frequency)); s32tmp = 0x10000 * (tuner_frequency - c->frequency); - s32tmp = DIV_ROUND_CLOSEST(s32tmp, M88DS3103_MCLK_KHZ); + s32tmp = DIV_ROUND_CLOSEST(s32tmp, priv->mclk_khz); if (s32tmp < 0) s32tmp += 0x10000; @@ -539,8 +579,9 @@ struct m88ds3103_priv *priv = fe->demodulator_priv; int ret, len, remaining; const struct firmware *fw = NULL; - u8 *fw_file = M88DS3103_FIRMWARE; + u8 *fw_file; u8 u8tmp; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); /* set cold state by default */ @@ -559,15 +600,6 @@ if (ret) goto err; - /* reset */ - ret = m88ds3103_wr_reg(priv, 0x07, 0x60); - if (ret) - goto err; - - ret = m88ds3103_wr_reg(priv, 0x07, 0x00); - if (ret) - goto err; - /* firmware status */ ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp); if (ret) @@ -578,14 +610,27 @@ if (u8tmp) goto skip_fw_download; + /* global reset, global diseqc reset, golbal fec reset */ + ret = m88ds3103_wr_reg(priv, 0x07, 0xe0); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0x07, 0x00); + if (ret) + goto err; + /* cold state - try to download firmware */ dev_info(&priv->i2c->dev, "%s: found a '%s' in cold state\n", KBUILD_MODNAME, m88ds3103_ops.info.name); + if (priv->chip_id == M88RS6000_CHIP_ID) + fw_file = M88RS6000_FIRMWARE; + else + fw_file = M88DS3103_FIRMWARE; /* request the firmware, this will block and timeout */ ret = request_firmware(&fw, fw_file, priv->i2c->dev.parent); if (ret) { - dev_err(&priv->i2c->dev, "%s: firmare file '%s' not found\n", + dev_err(&priv->i2c->dev, "%s: firmware file '%s' not found\n", KBUILD_MODNAME, fw_file); goto err; } @@ -595,7 +640,7 @@ ret = m88ds3103_wr_reg(priv, 0xb2, 0x01); if (ret) - goto err; + goto error_fw_release; for (remaining = fw->size; remaining > 0; remaining -= (priv->cfg->i2c_wr_max - 1)) { @@ -609,13 +654,13 @@ dev_err(&priv->i2c->dev, "%s: firmware download failed=%d\n", KBUILD_MODNAME, ret); - goto err; + goto error_fw_release; } } ret = m88ds3103_wr_reg(priv, 0xb2, 0x00); if (ret) - goto err; + goto error_fw_release; release_firmware(fw); fw = NULL; @@ -641,10 +686,10 @@ priv->warm = true; return 0; -err: - if (fw) - release_firmware(fw); +error_fw_release: + release_firmware(fw); +err: dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); return ret; } @@ -653,12 +698,18 @@ { struct m88ds3103_priv *priv = fe->demodulator_priv; int ret; + u8 u8tmp; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); priv->delivery_system = SYS_UNDEFINED; /* TS Hi-Z */ - ret = m88ds3103_wr_reg_mask(priv, 0x27, 0x00, 0x01); + if (priv->chip_id == M88RS6000_CHIP_ID) + u8tmp = 0x29; + else + u8tmp = 0x27; + ret = m88ds3103_wr_reg_mask(priv, u8tmp, 0x00, 0x01); if (ret) goto err; @@ -687,6 +738,7 @@ struct dtv_frontend_properties *c = &fe->dtv_property_cache; int ret; u8 buf[3]; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { @@ -711,9 +763,6 @@ case 1: c->inversion = INVERSION_ON; break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n", - __func__); } switch ((buf[1] >> 5) & 0x07) { @@ -793,9 +842,6 @@ case 1: c->pilot = PILOT_ON; break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid pilot\n", - __func__); } switch ((buf[0] >> 6) & 0x07) { @@ -823,9 +869,6 @@ case 1: c->inversion = INVERSION_ON; break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n", - __func__); } switch ((buf[2] >> 0) & 0x03) { @@ -855,7 +898,7 @@ goto err; c->symbol_rate = 1ull * ((buf[1] << 8) | (buf[0] << 0)) * - M88DS3103_MCLK_KHZ * 1000 / 0x10000; + priv->mclk_khz * 1000 / 0x10000; return 0; err: @@ -871,6 +914,7 @@ u8 buf[3]; u16 noise, signal; u32 noise_tot, signal_tot; + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); /* reports SNR in resolution of 0.1 dB */ @@ -893,7 +937,7 @@ /* SNR(X) dB = 10 * ln(X) / ln(10) dB */ tmp = DIV_ROUND_CLOSEST(tmp, 8 * M88DS3103_SNR_ITERATIONS); if (tmp) - *snr = 100ul * intlog2(tmp) / intlog2(10); + *snr = div_u64((u64) 100 * intlog2(tmp), intlog2(10)); else *snr = 0; break; @@ -922,7 +966,7 @@ /* SNR(X) dB = 10 * log10(X) dB */ if (signal > noise) { tmp = signal / noise; - *snr = 100ul * intlog10(tmp) / (1 << 24); + *snr = div_u64((u64) 100 * intlog10(tmp), (1 << 24)); } else { *snr = 0; } @@ -940,6 +984,87 @@ return ret; } +static int m88ds3103_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + unsigned int utmp; + u8 buf[3], u8tmp; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + switch (c->delivery_system) { + case SYS_DVBS: + ret = m88ds3103_wr_reg(priv, 0xf9, 0x04); + if (ret) + goto err; + + ret = m88ds3103_rd_reg(priv, 0xf8, &u8tmp); + if (ret) + goto err; + + if (!(u8tmp & 0x10)) { + u8tmp |= 0x10; + + ret = m88ds3103_rd_regs(priv, 0xf6, buf, 2); + if (ret) + goto err; + + priv->ber = (buf[1] << 8) | (buf[0] << 0); + + /* restart counters */ + ret = m88ds3103_wr_reg(priv, 0xf8, u8tmp); + if (ret) + goto err; + } + break; + case SYS_DVBS2: + ret = m88ds3103_rd_regs(priv, 0xd5, buf, 3); + if (ret) + goto err; + + utmp = (buf[2] << 16) | (buf[1] << 8) | (buf[0] << 0); + + if (utmp > 3000) { + ret = m88ds3103_rd_regs(priv, 0xf7, buf, 2); + if (ret) + goto err; + + priv->ber = (buf[1] << 8) | (buf[0] << 0); + + /* restart counters */ + ret = m88ds3103_wr_reg(priv, 0xd1, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xf9, 0x01); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xf9, 0x00); + if (ret) + goto err; + + ret = m88ds3103_wr_reg(priv, 0xd1, 0x00); + if (ret) + goto err; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto err; + } + + *ber = priv->ber; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} static int m88ds3103_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t fe_sec_tone_mode) @@ -947,6 +1072,7 @@ struct m88ds3103_priv *priv = fe->demodulator_priv; int ret; u8 u8tmp, tone, reg_a1_mask; + dev_dbg(&priv->i2c->dev, "%s: fe_sec_tone_mode=%d\n", __func__, fe_sec_tone_mode); @@ -958,7 +1084,7 @@ switch (fe_sec_tone_mode) { case SEC_TONE_ON: tone = 0; - reg_a1_mask = 0x87; + reg_a1_mask = 0x47; break; case SEC_TONE_OFF: tone = 1; @@ -987,12 +1113,64 @@ return ret; } +static int m88ds3103_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t fe_sec_voltage) +{ + struct m88ds3103_priv *priv = fe->demodulator_priv; + int ret; + u8 u8tmp; + bool voltage_sel, voltage_dis; + + dev_dbg(&priv->i2c->dev, "%s: fe_sec_voltage=%d\n", __func__, + fe_sec_voltage); + + if (!priv->warm) { + ret = -EAGAIN; + goto err; + } + + switch (fe_sec_voltage) { + case SEC_VOLTAGE_18: + voltage_sel = true; + voltage_dis = false; + break; + case SEC_VOLTAGE_13: + voltage_sel = false; + voltage_dis = false; + break; + case SEC_VOLTAGE_OFF: + voltage_sel = false; + voltage_dis = true; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_voltage\n", + __func__); + ret = -EINVAL; + goto err; + } + + /* output pin polarity */ + voltage_sel ^= priv->cfg->lnb_hv_pol; + voltage_dis ^= priv->cfg->lnb_en_pol; + + u8tmp = voltage_dis << 1 | voltage_sel << 0; + ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0x03); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + static int m88ds3103_diseqc_send_master_cmd(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *diseqc_cmd) { struct m88ds3103_priv *priv = fe->demodulator_priv; int ret, i; u8 u8tmp; + dev_dbg(&priv->i2c->dev, "%s: msg=%*ph\n", __func__, diseqc_cmd->msg_len, diseqc_cmd->msg); @@ -1064,6 +1242,7 @@ struct m88ds3103_priv *priv = fe->demodulator_priv; int ret, i; u8 u8tmp, burst; + dev_dbg(&priv->i2c->dev, "%s: fe_sec_mini_cmd=%d\n", __func__, fe_sec_mini_cmd); @@ -1136,6 +1315,7 @@ static void m88ds3103_release(struct dvb_frontend *fe) { struct m88ds3103_priv *priv = fe->demodulator_priv; + i2c_del_mux_adapter(priv->i2c_adapter); kfree(priv); } @@ -1198,18 +1378,22 @@ priv->i2c = i2c; mutex_init(&priv->i2c_mutex); - ret = m88ds3103_rd_reg(priv, 0x01, &chip_id); + /* 0x00: chip id[6:0], 0x01: chip ver[7:0], 0x02: chip ver[15:8] */ + ret = m88ds3103_rd_reg(priv, 0x00, &chip_id); if (ret) goto err; - dev_dbg(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id); + chip_id >>= 1; + dev_info(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id); switch (chip_id) { - case 0xd0: + case M88RS6000_CHIP_ID: + case M88DS3103_CHIP_ID: break; default: goto err; } + priv->chip_id = chip_id; switch (priv->cfg->clock_out) { case M88DS3103_CLOCK_OUT_DISABLED: @@ -1225,6 +1409,11 @@ goto err; } + /* 0x29 register is defined differently for m88rs6000. */ + /* set internal tuner address to 0x21 */ + if (chip_id == M88RS6000_CHIP_ID) + u8tmp = 0x00; + ret = m88ds3103_wr_reg(priv, 0x29, u8tmp); if (ret) goto err; @@ -1252,6 +1441,9 @@ /* create dvb_frontend */ memcpy(&priv->fe.ops, &m88ds3103_ops, sizeof(struct dvb_frontend_ops)); + if (priv->chip_id == M88RS6000_CHIP_ID) + strncpy(priv->fe.ops.info.name, + "Montage M88RS6000", sizeof(priv->fe.ops.info.name)); priv->fe.demodulator_priv = priv; return &priv->fe; @@ -1298,14 +1490,17 @@ .read_status = m88ds3103_read_status, .read_snr = m88ds3103_read_snr, + .read_ber = m88ds3103_read_ber, .diseqc_send_master_cmd = m88ds3103_diseqc_send_master_cmd, .diseqc_send_burst = m88ds3103_diseqc_send_burst, .set_tone = m88ds3103_set_tone, + .set_voltage = m88ds3103_set_voltage, }; MODULE_AUTHOR("Antti Palosaari "); MODULE_DESCRIPTION("Montage M88DS3103 DVB-S/S2 demodulator driver"); MODULE_LICENSE("GPL"); MODULE_FIRMWARE(M88DS3103_FIRMWARE); +MODULE_FIRMWARE(M88RS6000_FIRMWARE); --- a/drivers/media/dvb-frontends//m88ds3103_priv.h +++ b/drivers/media/dvb-frontends//m88ds3103_priv.h @@ -22,9 +22,13 @@ #include "dvb_math.h" #include #include +#include #define M88DS3103_FIRMWARE "dvb-demod-m88ds3103.fw" +#define M88RS6000_FIRMWARE "dvb-demod-m88rs6000.fw" #define M88DS3103_MCLK_KHZ 96000 +#define M88RS6000_CHIP_ID 0x74 +#define M88DS3103_CHIP_ID 0x70 struct m88ds3103_priv { struct i2c_adapter *i2c; @@ -34,8 +38,13 @@ struct dvb_frontend fe; fe_delivery_system_t delivery_system; fe_status_t fe_status; + u32 ber; bool warm; /* FW running */ struct i2c_adapter *i2c_adapter; + /* auto detect chip id to do different config */ + u8 chip_id; + /* main mclk is calculated for M88RS6000 dynamically */ + u32 mclk_khz; }; struct m88ds3103_reg_val { @@ -212,4 +221,178 @@ {0xb8, 0x00}, }; +static const struct m88ds3103_reg_val m88rs6000_dvbs_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x03}, + {0x0c, 0x02}, + {0x20, 0x00}, + {0x21, 0x54}, + {0x25, 0x82}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x31, 0x40}, + {0x32, 0x32}, + {0x33, 0x35}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x63, 0x0f}, + {0x64, 0x30}, + {0x65, 0x40}, + {0x68, 0x26}, + {0x69, 0x4c}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x7c, 0x00}, + {0xae, 0x82}, + {0x80, 0x64}, + {0x81, 0x66}, + {0x82, 0x44}, + {0x85, 0x04}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0xa0, 0x44}, + {0xbe, 0x00}, + {0xc0, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xe0, 0xf8}, + {0xe6, 0x8b}, + {0xd0, 0x40}, + {0xf8, 0x20}, + {0xfa, 0x0f}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, + {0x29, 0x11}, +}; + +static const struct m88ds3103_reg_val m88rs6000_dvbs2_init_reg_vals[] = { + {0x23, 0x07}, + {0x08, 0x07}, + {0x0c, 0x02}, + {0x20, 0x00}, + {0x21, 0x54}, + {0x25, 0x82}, + {0x27, 0x31}, + {0x30, 0x08}, + {0x32, 0x32}, + {0x33, 0x35}, + {0x35, 0xff}, + {0x3a, 0x00}, + {0x37, 0x10}, + {0x38, 0x10}, + {0x39, 0x02}, + {0x42, 0x60}, + {0x4a, 0x80}, + {0x4b, 0x04}, + {0x4d, 0x91}, + {0x5d, 0xc8}, + {0x50, 0x36}, + {0x51, 0x36}, + {0x52, 0x36}, + {0x53, 0x36}, + {0x63, 0x0f}, + {0x64, 0x10}, + {0x65, 0x20}, + {0x68, 0x46}, + {0x69, 0xcd}, + {0x70, 0x20}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x40}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x60}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x80}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0xa0}, + {0x71, 0x70}, + {0x72, 0x04}, + {0x73, 0x00}, + {0x70, 0x1f}, + {0x76, 0x38}, + {0x77, 0xa6}, + {0x78, 0x0c}, + {0x79, 0x80}, + {0x7f, 0x14}, + {0x85, 0x08}, + {0xcd, 0xf4}, + {0x90, 0x33}, + {0x86, 0x00}, + {0x87, 0x0f}, + {0x89, 0x00}, + {0x8b, 0x44}, + {0x8c, 0x66}, + {0x9d, 0xc1}, + {0x8a, 0x10}, + {0xad, 0x40}, + {0xa0, 0x44}, + {0xbe, 0x00}, + {0xc0, 0x08}, + {0xc1, 0x10}, + {0xc2, 0x08}, + {0xc3, 0x10}, + {0xc4, 0x08}, + {0xc5, 0xf0}, + {0xc6, 0xff}, + {0xc7, 0x00}, + {0xc8, 0x1a}, + {0xc9, 0x80}, + {0xca, 0x23}, + {0xcb, 0x24}, + {0xcc, 0xf4}, + {0xce, 0x74}, + {0x00, 0x00}, + {0xbd, 0x01}, + {0xb8, 0x00}, + {0x29, 0x01}, +}; #endif --- a/drivers/media/dvb-core/dvb-usb-ids.h +++ b/drivers/media/dvb-core/dvb-usb-ids.h @@ -244,6 +245,9 @@ #define USB_PID_TECHNOTREND_CONNECT_S2400 0x3006 #define USB_PID_TECHNOTREND_CONNECT_S2400_8KEEPROM 0x3009 #define USB_PID_TECHNOTREND_CONNECT_CT3650 0x300d +#define USB_PID_TECHNOTREND_CONNECT_S2_4600 0x3011 +#define USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI 0x3012 +#define USB_PID_TECHNOTREND_TVSTICK_CT2_4400 0x3014 #define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY 0x005a #define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY_2 0x0081 #define USB_PID_TERRATEC_CINERGY_HT_USB_XE 0x0058 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c @@ -814,7 +814,7 @@ .clock = 27000000, .i2c_wr_max = 33, .clock_out = 0, - .ts_mode = M88DS3103_TS_PARALLEL_16, + .ts_mode = M88DS3103_TS_PARALLEL, .agc = 0x99, };