Skip to content

Instantly share code, notes, and snippets.

@Daniel-Abrecht
Created September 10, 2024 19:10
Show Gist options
  • Save Daniel-Abrecht/0c0c91ce6e7486af59af47bc0c793aae to your computer and use it in GitHub Desktop.
Save Daniel-Abrecht/0c0c91ce6e7486af59af47bc0c793aae to your computer and use it in GitHub Desktop.
TV
diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index 2ef2ff2a38ff..8d04e082b50f 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -943,6 +943,13 @@ config DVB_SP2
help
CIMaX SP2/SP2HF Common Interface module.
+config DVB_SI2183
+ tristate "Silicon Labs Si2183 DVB-T/T2/C/C2/S/S2/S2X/ISDB-T demodulator"
+ depends on DVB_CORE && I2C
+ default m if !MEDIA_SUBDRV_AUTOSELECT
+ help
+ Say Y when you want to support this frontend.
+
endmenu # Customise DVB Frontends
endif # MEDIA_DIGITAL_TV_SUPPORT
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index a93146cb428c..c9065ccacb86 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_DVB_S5H1432) += s5h1432.o
obj-$(CONFIG_DVB_S921) += s921.o
obj-$(CONFIG_DVB_SI2165) += si2165.o
obj-$(CONFIG_DVB_SI2168) += si2168.o
+obj-$(CONFIG_DVB_SI2183) += si2183.o
obj-$(CONFIG_DVB_SI21XX) += si21xx.o
obj-$(CONFIG_DVB_SP2) += sp2.o
obj-$(CONFIG_DVB_SP887X) += sp887x.o
diff --git a/drivers/media/dvb-frontends/si2183.c b/drivers/media/dvb-frontends/si2183.c
new file mode 100644
index 000000000000..d4e19f94d10c
--- /dev/null
+++ b/drivers/media/dvb-frontends/si2183.c
@@ -0,0 +1,1830 @@
+/*
+ * Silicon Labs Si2183(2) DVB-T/T2/C/C2/S/S2 demodulator driver
+ *
+ * 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.
+ */
+
+#include "si2183.h"
+#include <media/dvb_frontend.h>
+#include <linux/firmware.h>
+#include <linux/i2c-mux.h>
+
+#define SI2183_USE_I2C_MUX
+
+#define SI2183_B60_FIRMWARE "dvb-demod-si2183-b60-01.fw"
+
+#define SI2183_PROP_MODE 0x100a
+#define SI2183_PROP_DVBC_CONST 0x1101
+#define SI2183_PROP_DVBC_SR 0x1102
+#define SI2183_PROP_DVBT_HIER 0x1201
+#define SI2183_PROP_DVBT2_MODE 0x1304
+#define SI2183_PROP_DVBS2_SR 0x1401
+#define SI2183_PROP_DVBS_SR 0x1501
+#define SI2183_PROP_MCNS_CONST 0x1601
+#define SI2183_PROP_MCNS_SR 0x1602
+
+#define SI2183_ARGLEN 30
+struct si2183_cmd {
+ u8 args[SI2183_ARGLEN];
+ unsigned wlen;
+ unsigned rlen;
+};
+
+static const struct dvb_frontend_ops si2183_ops;
+
+LIST_HEAD(silist);
+
+struct si_base {
+ struct mutex i2c_mutex;
+#if 1
+ struct i2c_mux_core *muxc;
+#endif
+ struct list_head silist;
+
+ u8 adr;
+ struct i2c_adapter *i2c;
+ u32 count;
+
+ struct i2c_adapter *tuner_adapter;
+
+#ifndef SI2183_USE_I2C_MUX
+ struct i2c_client *i2c_gate_client;
+#endif
+};
+
+/* state struct */
+struct si2183_dev {
+ struct dvb_frontend fe;
+ enum fe_delivery_system delivery_system;
+ enum fe_status fe_status;
+ u8 stat_resp;
+ u16 snr;
+ bool fw_loaded;
+ u8 ts_mode;
+ bool ts_clock_inv;
+ bool ts_clock_gapped;
+ int start_clk_mode;
+ u8 agc_mode;
+ struct si_base *base;
+ void (*RF_switch)(struct i2c_adapter * i2c,u8 rf_in,u8 flag);
+ u8 rf_in;
+ u8 active_fe;
+ void (*TS_switch)(struct i2c_adapter * i2c,u8 flag);
+ void (*LED_switch)(struct i2c_adapter * i2c,u8 flag);
+
+ void (*write_properties) (struct i2c_adapter *i2c,u8 reg, u32 buf);
+ void (*read_properties) (struct i2c_adapter *i2c,u8 reg, u32 *buf);
+
+ void (*write_eeprom) (struct i2c_adapter *i2c,u8 reg, u8 buf);
+ void (*read_eeprom) (struct i2c_adapter *i2c,u8 reg, u8 *buf);
+};
+
+/* Own I2C adapter locking is needed because of I2C gate logic. */
+static int si2183_i2c_master_send_unlocked(const struct i2c_client *client,
+ const char *buf, int count)
+{
+ int ret;
+ struct i2c_msg msg = {
+ .addr = client->addr,
+ .flags = 0,
+ .len = count,
+ .buf = (char *)buf,
+ };
+
+ ret = __i2c_transfer(client->adapter, &msg, 1);
+ return (ret == 1) ? count : ret;
+}
+
+static int si2183_i2c_master_recv_unlocked(const struct i2c_client *client,
+ char *buf, int count)
+{
+ int ret;
+ struct i2c_msg msg = {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = count,
+ .buf = buf,
+ };
+
+ ret = __i2c_transfer(client->adapter, &msg, 1);
+ return (ret == 1) ? count : ret;
+}
+
+/* execute firmware command */
+static int si2183_cmd_execute_unlocked(struct i2c_client *client,
+ struct si2183_cmd *cmd)
+{
+ int ret;
+ unsigned long timeout;
+
+ if (cmd->wlen) {
+ /* write cmd and args for firmware */
+ ret = si2183_i2c_master_send_unlocked(client, cmd->args,
+ cmd->wlen);
+ if (ret < 0) {
+ goto err;
+ } else if (ret != cmd->wlen) {
+ ret = -EREMOTEIO;
+ goto err;
+ }
+ }
+
+ if (cmd->rlen) {
+ /* wait cmd execution terminate */
+ #define TIMEOUT 500
+ timeout = jiffies + msecs_to_jiffies(TIMEOUT);
+ while (!time_after(jiffies, timeout)) {
+ ret = si2183_i2c_master_recv_unlocked(client, cmd->args,
+ cmd->rlen);
+ if (ret < 0) {
+ goto err;
+ } else if (ret != cmd->rlen) {
+ ret = -EREMOTEIO;
+ goto err;
+ }
+
+ /* firmware ready? */
+ if ((cmd->args[0] >> 7) & 0x01)
+ break;
+ }
+
+ dev_dbg(&client->dev, "cmd execution took %d ms\n",
+ jiffies_to_msecs(jiffies) -
+ (jiffies_to_msecs(timeout) - TIMEOUT));
+
+ /* error bit set? */
+ if ((cmd->args[0] >> 6) & 0x01) {
+ ret = -EREMOTEIO;
+ goto err;
+ }
+
+ if (!((cmd->args[0] >> 7) & 0x01)) {
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ dev_dbg(&client->dev, "failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_cmd_execute(struct i2c_client *client, struct si2183_cmd *cmd)
+{
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ int ret;
+
+
+ mutex_lock(&dev->base->i2c_mutex);
+ ret = si2183_cmd_execute_unlocked(client, cmd);
+ mutex_unlock(&dev->base->i2c_mutex);
+
+ return ret;
+}
+
+static int si2183_set_prop(struct i2c_client *client, u16 prop, u16 *val)
+{
+ struct si2183_cmd cmd;
+ int ret;
+
+ cmd.args[0] = 0x14;
+ cmd.args[1] = 0x00;
+ cmd.args[2] = (u8) prop;
+ cmd.args[3] = (u8) (prop >> 8);
+ cmd.args[4] = (u8) (*val);
+ cmd.args[5] = (u8) (*val >> 8);
+ cmd.wlen = 6;
+ cmd.rlen = 4;
+ ret = si2183_cmd_execute(client, &cmd);
+ *val = (cmd.args[2] | (cmd.args[3] << 8));
+ return ret;
+}
+#if 0
+static int si2183_get_prop(struct i2c_client *client, u16 prop, u16 *val)
+{
+ struct si2183_cmd cmd;
+ int ret;
+
+ cmd.args[0] = 0x15;
+ cmd.args[1] = 0x00;
+ cmd.args[2] = (u8) prop;
+ cmd.args[3] = (u8) (prop >> 8);
+ cmd.wlen = 4;
+ cmd.rlen = 4;
+ ret = si2183_cmd_execute(client, &cmd);
+ *val = (cmd.args[2] | (cmd.args[3] << 8));
+ return ret;
+}
+#endif
+
+static int si2183_read_status(struct dvb_frontend *fe, enum fe_status *status)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ int ret;
+ struct si2183_cmd cmd;
+ u16 agc;
+
+ *status = 0;
+
+ if (!dev->active_fe) {
+ ret = -EAGAIN;
+ goto err;
+ }
+
+ if ((dev->delivery_system != c->delivery_system) || (dev->delivery_system == 0))
+ return 0;
+
+ switch (c->delivery_system) {
+ case SYS_DVBT:
+ memcpy(cmd.args, "\xa0\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 13;
+ dev->snr = 2;
+ break;
+ case SYS_DVBC_ANNEX_A:
+ memcpy(cmd.args, "\x90\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 9;
+ dev->snr = 2;
+ break;
+ case SYS_DVBC_ANNEX_B:
+ memcpy(cmd.args, "\x98\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 10;
+ dev->snr = 2;
+ break;
+ case SYS_DVBT2:
+ memcpy(cmd.args, "\x50\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 14;
+ dev->snr = 2;
+ break;
+ case SYS_DVBS:
+ memcpy(cmd.args, "\x60\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 10;
+ dev->snr = 5;
+ break;
+ case SYS_DVBS2:
+ memcpy(cmd.args, "\x70\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 13;
+ dev->snr = 5;
+ break;
+ case SYS_ISDBT:
+ memcpy(cmd.args, "\xa4\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 14;
+ dev->snr = 2;
+ break;
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret) {
+ dev_err(&client->dev, "read_status fe%d cmd_exec failed=%d\n", fe->id, ret);
+ goto err;
+ }
+
+ dev->stat_resp = cmd.args[2];
+ switch ((dev->stat_resp >> 1) & 0x03) {
+ case 0x01:
+ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+ c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+ break;
+ case 0x03:
+ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI |
+ FE_HAS_SYNC | FE_HAS_LOCK;
+ dev->snr *= cmd.args[3] * 164;
+ c->cnr.len = 2;
+ c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+ c->cnr.stat[0].svalue = (s64) cmd.args[3] * 250;
+ c->cnr.stat[1].scale = FE_SCALE_RELATIVE;
+ c->cnr.stat[1].svalue = dev->snr;
+
+ // writing missing properties
+ // CONSTELLATION or modulation
+ switch (cmd.args[8] & 0x3f){
+ case 0x03:
+ c->modulation = QPSK;
+ break;
+ case 0x07:
+ c->modulation = QAM_16;
+ break;
+ case 0x08:
+ c->modulation = QAM_32;
+ break;
+ case 0x09:
+ c->modulation = QAM_64;
+ break;
+ case 0x0a:
+ c->modulation = QAM_128;
+ break;
+ case 0x0b:
+ c->modulation = QAM_256;
+ break;
+ case 0x0e:
+ c->modulation = PSK_8;
+ break;
+ case 0x14:
+ c->modulation = APSK_16;
+ break;
+ case 0x17:
+ c->modulation = APSK_8_L;
+ break;
+ case 0x18:
+ c->modulation = APSK_16_L;
+ break;
+ case 0x15:
+ c->modulation = APSK_32;
+ break;
+ case 0x19:
+ c->modulation = APSK_32_L;
+ break;
+ case 0x1a:
+ c->modulation = APSK_32;
+ break;
+ default:
+ c->modulation = QPSK;
+ break;
+ }
+ // fec_inner
+ switch (c->delivery_system) {
+ case SYS_DVBT2:
+ switch (cmd.args[12] & 0x0f){
+ case 0x01:
+ c->fec_inner = FEC_1_2;
+ break;
+ case 0x02:
+ c->fec_inner = FEC_2_3;
+ break;
+ case 0x03:
+ c->fec_inner = FEC_3_4;
+ break;
+ case 0x04:
+ c->fec_inner = FEC_4_5;
+ break;
+ case 0x05:
+ c->fec_inner = FEC_5_6;
+ break;
+ case 0x0a:
+ c->fec_inner = FEC_1_3;
+ break;
+ case 0x0c:
+ c->fec_inner = FEC_2_5;
+ break;
+ case 0x0d:
+ c->fec_inner = FEC_3_5;
+ break;
+ default:
+ c->fec_inner = FEC_AUTO;
+ break;
+ }
+ break;
+ case SYS_DVBS:
+ switch (cmd.args[9] & 0x0f){
+ case 0x01:
+ c->fec_inner = FEC_1_2;
+ break;
+ case 0x02:
+ c->fec_inner = FEC_2_3;
+ break;
+ case 0x03:
+ c->fec_inner = FEC_3_4;
+ break;
+ case 0x04:
+ c->fec_inner = FEC_4_5;
+ break;
+ case 0x05:
+ c->fec_inner = FEC_5_6;
+ break;
+ case 0x06:
+ c->fec_inner = FEC_6_7;
+ break;
+ case 0x07:
+ c->fec_inner = FEC_7_8;
+ break;
+ default:
+ c->fec_inner = FEC_AUTO;
+ break;
+ }
+ break;
+ case SYS_DVBS2:
+ switch (cmd.args[9] & 0x1f){
+ case 0x01:
+ c->fec_inner = FEC_1_2;
+ break;
+ case 0x02:
+ c->fec_inner = FEC_2_3;
+ break;
+ case 0x03:
+ c->fec_inner = FEC_3_4;
+ break;
+ case 0x04:
+ c->fec_inner = FEC_4_5;
+ break;
+ case 0x05:
+ c->fec_inner = FEC_5_6;
+ break;
+ case 0x08:
+ c->fec_inner = FEC_8_9;
+ break;
+ case 0x09:
+ c->fec_inner = FEC_9_10;
+ break;
+ case 0x0a:
+ c->fec_inner = FEC_1_3;
+ break;
+ case 0x0b:
+ c->fec_inner = FEC_1_4;
+ break;
+ case 0x0c:
+ c->fec_inner = FEC_2_5;
+ break;
+ case 0x0d:
+ c->fec_inner = FEC_3_5;
+ break;
+ default:
+ c->fec_inner = FEC_AUTO;
+ break;
+ }
+ break;
+ default:
+ c->fec_inner = FEC_AUTO;
+ break;
+ }
+ // rolloff and pilot (only available for dvb s2)
+ switch (c->delivery_system) {
+ case SYS_DVBS2:
+ // rolloff
+ switch (cmd.args[10] & 0x07){
+ case 0x00:
+ c->rolloff = ROLLOFF_35;
+ break;
+ case 0x01:
+ c->rolloff = ROLLOFF_25;
+ break;
+ case 0x02:
+ c->rolloff = ROLLOFF_20;
+ break;
+ case 0x04:
+ c->rolloff = ROLLOFF_15;
+ break;
+ case 0x05:
+ c->rolloff = ROLLOFF_10;
+ break;
+ case 0x06:
+ c->rolloff = ROLLOFF_5;
+ break;
+ default:
+ c->rolloff = ROLLOFF_AUTO;
+ break;
+ }
+ // pilot
+ switch ((cmd.args[8] >> 7) & 0x01){
+ case 0x01:
+ c->pilot = PILOT_ON;
+ break;
+ case 0x00:
+ c->pilot = PILOT_OFF;
+ break;
+ default:
+ c->pilot = PILOT_AUTO;
+ break;
+ }
+ break;
+ default:
+ c->rolloff = ROLLOFF_AUTO;
+ c->pilot = PILOT_AUTO;
+ break;
+ }
+ break;
+ default:
+ c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+ break;
+ }
+
+ dev->fe_status = *status;
+
+ dev_dbg(&client->dev, "status=%02x args=%*ph\n",
+ *status, cmd.rlen, cmd.args);
+
+ if (fe->ops.tuner_ops.get_rf_strength)
+ {
+ memcpy(cmd.args, "\x8a\x00\x00\x00\x00\x00", 6);
+ cmd.wlen = 6;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret) {
+ dev_err(&client->dev, "read_status fe%d cmd_exec failed=%d\n", fe->id, ret);
+ goto err;
+ }
+ dev_dbg(&client->dev, "status=%02x args=%*ph\n",
+ *status, cmd.rlen, cmd.args);
+
+ agc = cmd.args[1];
+ fe->ops.tuner_ops.get_rf_strength(fe, &agc);
+ }
+
+ if ( c->strength.len == 1 && c->strength.stat[0].scale == FE_SCALE_DECIBEL) {
+ c->strength.len ++;
+ c->strength.stat[1].scale = FE_SCALE_RELATIVE;
+ c->strength.stat[1].svalue = ((100000 + (s32)c->strength.stat[0].svalue) / 1000) * 656;
+ }
+
+ return 0;
+err:
+ dev_err(&client->dev, "read_status failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+
+ *snr = (dev->fe_status & FE_HAS_LOCK) ? dev->snr : 0;
+
+ return 0;
+}
+
+static int si2183_read_signal_strength(struct dvb_frontend *fe, u16 *strength)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ int i;
+
+ *strength = 0;
+ for (i=0; i < c->strength.len; i++)
+ {
+ if (c->strength.stat[i].scale == FE_SCALE_RELATIVE)
+ *strength = (u16)c->strength.stat[i].uvalue;
+ else if (c->strength.stat[i].scale == FE_SCALE_DECIBEL)
+ *strength = ((100000 + (s32)c->strength.stat[i].svalue)/1000) * 656;
+ }
+
+ return 0;
+}
+
+static int si2183_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+ int ret;
+
+ if (dev->fe_status & FE_HAS_LOCK) {
+ memcpy(cmd.args, "\x82\x00", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret) {
+ dev_err(&client->dev, "read_ber fe%d cmd_exec failed=%d\n", fe->id, ret);
+ goto err;
+ }
+ *ber = (u32)cmd.args[2] * cmd.args[1] & 0xf;
+ } else *ber = 1;
+
+ return 0;
+err:
+ dev_err(&client->dev, "read_ber failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+ int ret;
+
+ if (dev->stat_resp & 0x10) {
+ memcpy(cmd.args, "\x84\x00", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret) {
+ dev_err(&client->dev, "read_ucblocks fe%d cmd_exec failed=%d\n", fe->id, ret);
+ goto err;
+ }
+
+ *ucblocks = (u16)cmd.args[2] << 8 | cmd.args[1];
+ } else *ucblocks = 0;
+
+ return 0;
+err:
+ dev_err(&client->dev, "read_ucblocks failed=%d\n", ret);
+ return ret;
+}
+
+
+static int si2183_set_dvbc(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+ int ret;
+ u16 prop;
+
+ if(dev->LED_switch)
+ dev->LED_switch(dev->base->i2c,6);
+
+ memcpy(cmd.args, "\x89\x41\x06\x12\x0\x0", 6);
+ cmd.args[1]= (dev->agc_mode &0x07)<<4 |0x1;
+ cmd.wlen = 6;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if(ret){
+ dev_err(&client->dev, "err set agc mode\n");
+ }
+ /* dvb-c mode */
+ prop = 0x38;
+ ret = si2183_set_prop(client, SI2183_PROP_MODE, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set dvb-c mode\n");
+ return ret;
+ }
+
+ switch (c->modulation) {
+ default:
+ case QAM_AUTO:
+ prop = 0;
+ break;
+ case QAM_16:
+ prop = 7;
+ break;
+ case QAM_32:
+ prop = 8;
+ break;
+ case QAM_64:
+ prop = 9;
+ break;
+ case QAM_128:
+ prop = 10;
+ break;
+ case QAM_256:
+ prop = 11;
+ break;
+ }
+ ret = si2183_set_prop(client, SI2183_PROP_DVBC_CONST, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set dvb-c constelation\n");
+ return ret;
+ }
+
+ /* symbol rate */
+ prop = c->symbol_rate / 1000;
+ ret = si2183_set_prop(client, SI2183_PROP_DVBC_SR, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set dvb-c symbol rate\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int si2183_set_mcns(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+ int ret;
+ u16 prop,symb;
+
+ if(dev->LED_switch)
+ dev->LED_switch(dev->base->i2c,6);
+
+ memcpy(cmd.args, "\x89\x41\x06\x12\x0\x0", 6);
+ cmd.args[1]= (dev->agc_mode &0x07)<<4 |0x1;
+ cmd.wlen = 6;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if(ret){
+ dev_err(&client->dev, "err set agc mode\n");
+ }
+ /* mcns mode */
+ prop = 0x18;
+ ret = si2183_set_prop(client, SI2183_PROP_MODE, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set mcns mode\n");
+ return ret;
+ }
+
+ switch (c->modulation) {
+ default:
+ case QAM_64:
+ prop = 9;
+ symb = 5057;
+ break;
+ case QAM_256:
+ prop = 11;
+ symb = 5361;
+ break;
+ }
+ ret = si2183_set_prop(client, SI2183_PROP_MCNS_CONST, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set mcns constelation\n");
+ return ret;
+ }
+
+ /* symbol rate */
+ ret = si2183_set_prop(client, SI2183_PROP_MCNS_SR, &symb);
+ if (ret) {
+ dev_err(&client->dev, "err set mcns symbol rate\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int gold_code_index (int gold_sequence_index)
+{
+ unsigned int i, k , x_init;
+ u8 GOLD_PRBS[19] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ for (k=0; k<gold_sequence_index; k++) {
+ GOLD_PRBS[18] = (GOLD_PRBS[0] + GOLD_PRBS[7])%2;
+ /* Shifting 18 first values */
+ for (i=0; i<18; i++)
+ GOLD_PRBS[i] = GOLD_PRBS[i+1];
+ }
+ x_init = 0;
+ for (i=0; i<18; i++) { x_init = x_init + GOLD_PRBS[i]*(1<<i); }
+
+ return x_init;
+}
+
+static int si2183_set_dvbs(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+ int ret;
+ u16 prop;
+ u32 pls_mode, pls_code;
+
+ if(dev->LED_switch)
+ dev->LED_switch(dev->base->i2c,2);
+
+ /*set SAT agc*/
+ memcpy(cmd.args, "\x8a\x1d\x12\x0\x0\x0", 6);
+ cmd.args[1]= dev->agc_mode|0x18;
+ cmd.wlen = 6;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if(ret){
+ dev_err(&client->dev, "err set agc mode\n");
+ }
+
+ /* set mode */
+ prop = 0x8;
+ switch (c->delivery_system) {
+ default:
+ case SYS_DVBS:
+ prop |= 0x80;
+ break;
+ case SYS_DVBS2:
+ prop |= 0x90;
+ break;
+ case SYS_DSS:
+ prop |= 0xa0;
+ break;
+ }
+ if (c->inversion)
+ prop |= 0x100;
+ ret = si2183_set_prop(client, SI2183_PROP_MODE, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set dvb-s/s2 mode\n");
+ return ret;
+ }
+
+ /* symbol rate */
+ prop = c->symbol_rate / 1000;
+ switch (c->delivery_system) {
+ default:
+ case SYS_DSS:
+ case SYS_DVBS:
+ ret = si2183_set_prop(client, SI2183_PROP_DVBS_SR, &prop);
+ break;
+ case SYS_DVBS2:
+ ret = si2183_set_prop(client, SI2183_PROP_DVBS2_SR, &prop);
+ /* stream_id selection */
+ cmd.args[0] = 0x71;
+ cmd.args[1] = (u8) c->stream_id;
+ cmd.args[2] = c->stream_id == NO_STREAM_ID_FILTER ? 0 : 1;
+ cmd.wlen = 3;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ dev_warn(&client->dev, "dvb-s2: err selecting stream_id\n");
+
+ /* pls selection */
+ pls_mode = c->stream_id == NO_STREAM_ID_FILTER ? 0 : (c->stream_id >> 26) & 3;
+ pls_code = c->stream_id == NO_STREAM_ID_FILTER ? 0 : (c->stream_id >> 8) & 0x3FFFF;
+ if (pls_mode)
+ pls_code = gold_code_index(pls_code);
+ cmd.args[0] = 0x73;
+ cmd.args[1] = pls_code > 0;
+ cmd.args[2] = cmd.args[3] = 0;
+ cmd.args[4] = (u8) pls_code;
+ cmd.args[5] = (u8) (pls_code >> 8);
+ cmd.args[6] = (u8) (pls_code >> 16);
+ cmd.args[7] = (u8) (pls_code >> 24);
+ cmd.wlen = 8;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ dev_warn(&client->dev, "dvb-s2: err set pls\n");
+ }
+
+ return 0;
+}
+
+static int si2183_set_dvbt(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+ int ret;
+ u16 prop;
+
+ if(dev->LED_switch)
+ dev->LED_switch(dev->base->i2c,1);
+
+ memcpy(cmd.args, "\x89\x41\x06\x12\x0\x0", 6);
+ cmd.args[1]= (dev->agc_mode &0x07)<<4 |0x1;
+ cmd.wlen = 6;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if(ret){
+ dev_err(&client->dev, "err set agc mode\n");
+ }
+
+ if (c->bandwidth_hz == 0) {
+ return -EINVAL;
+ } else if (c->bandwidth_hz <= 2000000)
+ prop = 0x02;
+ else if (c->bandwidth_hz <= 5000000)
+ prop = 0x05;
+ else if (c->bandwidth_hz <= 6000000)
+ prop = 0x06;
+ else if (c->bandwidth_hz <= 7000000)
+ prop = 0x07;
+ else if (c->bandwidth_hz <= 8000000)
+ prop = 0x08;
+ else if (c->bandwidth_hz <= 9000000)
+ prop = 0x09;
+ else if (c->bandwidth_hz <= 10000000)
+ prop = 0x0a;
+ else
+ prop = 0x0f;
+
+ switch (c->delivery_system) {
+ default:
+ case SYS_DVBT:
+ prop |= 0x20;
+ break;
+ case SYS_DVBT2:
+ prop |= 0x70;
+ break;
+ }
+ ret = si2183_set_prop(client, SI2183_PROP_MODE, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set dvb-t mode\n");
+ return ret;
+ }
+
+ /* hierarchy - HP = 0 / LP = 1 */
+ prop = c->hierarchy == HIERARCHY_1 ? 1 : 0;
+ ret = si2183_set_prop(client, SI2183_PROP_DVBT_HIER, &prop);
+ if (ret)
+ dev_warn(&client->dev, "dvb-t: err set hierarchy\n");
+
+ if (c->delivery_system == SYS_DVBT2) {
+ /* stream_id selection */
+ cmd.args[0] = 0x52;
+ cmd.args[1] = (u8) c->stream_id;
+ cmd.args[2] = c->stream_id == NO_STREAM_ID_FILTER ? 0 : 1;
+ cmd.wlen = 3;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ dev_warn(&client->dev, "dvb-t2: err selecting stream_id\n");
+
+ /* dvb-t2 mode - any=0 / base=1 / lite=2 */
+ prop = 0;
+ ret = si2183_set_prop(client, SI2183_PROP_DVBT2_MODE, &prop);
+ if (ret)
+ dev_warn(&client->dev, "dvb-t2: err set mode\n");
+ }
+
+ return 0;
+}
+
+static int si2183_set_isdbt(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+ int ret;
+ u16 prop;
+
+ if(dev->LED_switch)
+ dev->LED_switch(dev->base->i2c,5);
+
+
+ memcpy(cmd.args, "\x89\x41\x06\x12\x0\x0", 6);
+ cmd.args[1]= (dev->agc_mode &0x07)<<4 |0x1;
+ cmd.wlen = 6;
+ cmd.rlen = 3;
+ ret = si2183_cmd_execute(client, &cmd);
+ if(ret){
+ dev_err(&client->dev, "err set agc mode\n");
+ }
+ if (c->bandwidth_hz == 0) {
+ return -EINVAL;
+ } else if (c->bandwidth_hz <= 2000000)
+ prop = 0x02;
+ else if (c->bandwidth_hz <= 5000000)
+ prop = 0x05;
+ else if (c->bandwidth_hz <= 6000000)
+ prop = 0x06;
+ else if (c->bandwidth_hz <= 7000000)
+ prop = 0x07;
+ else if (c->bandwidth_hz <= 8000000)
+ prop = 0x08;
+ else if (c->bandwidth_hz <= 9000000)
+ prop = 0x09;
+ else if (c->bandwidth_hz <= 10000000)
+ prop = 0x0a;
+ else
+ prop = 0x0f;
+
+ /* ISDB-T mode */
+ prop |= 0x40;
+ ret = si2183_set_prop(client, SI2183_PROP_MODE, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set dvb-t mode\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int si2183_set_frontend(struct dvb_frontend *fe)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ int ret;
+ struct si2183_cmd cmd;
+
+ dev_dbg(&client->dev,
+ "delivery_system=%u modulation=%u frequency=%u bandwidth_hz=%u symbol_rate=%u inversion=%u stream_id=%d\n",
+ c->delivery_system, c->modulation, c->frequency,
+ c->bandwidth_hz, c->symbol_rate, c->inversion,
+ c->stream_id);
+
+ if (!dev->active_fe) {
+ ret = -EAGAIN;
+ goto err;
+ }
+ if(dev->RF_switch)
+ {
+ switch (c->delivery_system) {
+ case SYS_DVBT:
+ case SYS_DVBT2:
+ case SYS_DVBC_ANNEX_A:
+ case SYS_DVBC_ANNEX_B:
+ case SYS_ISDBT:
+ dev->RF_switch(dev->base->i2c,dev->rf_in,1);
+
+ break;
+
+ case SYS_DVBS:
+ case SYS_DVBS2:
+ case SYS_DSS:
+ default:
+ dev->RF_switch(dev->base->i2c,dev->rf_in,0);
+ break;
+
+ }
+ }
+
+ if(dev->TS_switch)
+ dev->TS_switch(dev->base->i2c,1);
+
+ if (fe->ops.tuner_ops.set_params) {
+#ifndef SI2183_USE_I2C_MUX
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 1);
+#endif
+ ret = fe->ops.tuner_ops.set_params(fe);
+#ifndef SI2183_USE_I2C_MUX
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 0);
+#endif
+ if (ret) {
+ dev_err(&client->dev, "err setting tuner params\n");
+ goto err;
+ }
+ }
+
+ switch (c->delivery_system) {
+ case SYS_DVBT:
+ case SYS_DVBT2:
+ ret = si2183_set_dvbt(fe);
+ break;
+ case SYS_DVBC_ANNEX_A:
+ ret = si2183_set_dvbc(fe);
+ break;
+ case SYS_DVBC_ANNEX_B:
+ ret= si2183_set_mcns(fe);
+ break;
+ case SYS_ISDBT:
+ ret = si2183_set_isdbt(fe);
+ break;
+ case SYS_DVBS:
+ case SYS_DVBS2:
+ case SYS_DSS:
+ ret = si2183_set_dvbs(fe);
+ break;
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* dsp restart */
+ memcpy(cmd.args, "\x85", 1);
+ cmd.wlen = 1;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret) {
+ dev_err(&client->dev, "err restarting dsp\n");
+ return ret;
+ }
+
+ dev->delivery_system = c->delivery_system;
+ return 0;
+err:
+ dev_err(&client->dev, "set_params failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_init(struct dvb_frontend *fe)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ int ret = 0, len, remaining;
+ const struct firmware *fw;
+ const char *fw_name;
+ struct si2183_cmd cmd;
+ unsigned int chip_id;
+ u16 prop;
+
+ dev_dbg(&client->dev, "\n");
+
+ if (dev->active_fe) {
+ dev->active_fe |= (1 << fe->id);
+ return 0;
+ }
+
+ c->cnr.len = 1;
+ c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+
+ /* initialize */
+ memcpy(cmd.args, "\xc0\x12\x00\x0c\x00\x0d\x16\x00\x00\x00\x00\x00\x00", 13);
+ if(dev->start_clk_mode == 1){
+ cmd.args[3] =0;
+ cmd.args[5] = 0x6;
+ }
+
+ cmd.wlen = 13;
+ cmd.rlen = 0;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ if (dev->fw_loaded) {
+ /* resume */
+ memcpy(cmd.args, "\xc0\x06\x08\x0f\x00\x20\x21\x01", 8);
+
+ if(dev->start_clk_mode==1)
+ cmd.args[6]=0x31;
+ cmd.wlen = 8;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ memcpy(cmd.args, "\x85", 1);
+ cmd.wlen = 1;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ goto warm;
+ }
+
+ /* power up */
+ memcpy(cmd.args, "\xc0\x06\x01\x0f\x00\x20\x20\x01", 8);
+ if(dev->start_clk_mode ==1 ){
+ cmd.args[6] = 0x30;
+ }
+ cmd.wlen = 8;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ /* query chip revision */
+ memcpy(cmd.args, "\x02", 1);
+ cmd.wlen = 1;
+ cmd.rlen = 13;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ chip_id = cmd.args[1] << 24 | cmd.args[2] << 16 | cmd.args[3] << 8 |
+ cmd.args[4] << 0;
+
+ #define SI2183_B60 ('B' << 24 | 83 << 16 | '6' << 8 | '0' << 0)
+
+ switch (chip_id) {
+ case SI2183_B60:
+ fw_name = SI2183_B60_FIRMWARE;
+ break;
+ default:
+ dev_err(&client->dev, "unknown chip version Si21%d-%c%c%c\n",
+ cmd.args[2], cmd.args[1],
+ cmd.args[3], cmd.args[4]);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ dev_info(&client->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n",
+ cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]);
+
+ ret = request_firmware(&fw, fw_name, &client->dev);
+ if (ret) {
+ dev_err(&client->dev,
+ "firmware file '%s' not found\n",
+ fw_name);
+ goto err;
+ }
+
+ dev_info(&client->dev, "downloading firmware from file '%s'\n",
+ fw_name);
+
+ for (remaining = fw->size; remaining > 0; remaining -= 17) {
+ len = fw->data[fw->size - remaining];
+ if (len > SI2183_ARGLEN) {
+ ret = -EINVAL;
+ break;
+ }
+ memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len);
+ cmd.wlen = len;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ break;
+ }
+ release_firmware(fw);
+
+ if (ret) {
+ dev_err(&client->dev, "firmware download failed %d\n", ret);
+ goto err;
+ }
+
+ memcpy(cmd.args, "\x01\x01", 2);
+ cmd.wlen = 2;
+ cmd.rlen = 1;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ /* query firmware version */
+ memcpy(cmd.args, "\x11", 1);
+ cmd.wlen = 1;
+ cmd.rlen = 10;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ dev_info(&client->dev, "firmware version: %c.%c.%d\n",
+ cmd.args[6], cmd.args[7], cmd.args[8]);
+
+ /* set ts mode */
+ prop = 0x10 | dev->ts_mode | (dev->ts_clock_gapped ? 0x40 : 0);
+ ret = si2183_set_prop(client, 0x1001, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set ts mode\n");
+ }
+
+ /* FER resol */
+ prop = 0x12;
+ ret = si2183_set_prop(client, 0x100c, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set FER resol\n");
+ return ret;
+ }
+
+ /* DD IEN */
+ prop = 0x00;
+ ret = si2183_set_prop(client, 0x1006, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set dd ien\n");
+ return ret;
+ }
+
+ /* int sense */
+ prop = 0x2000;
+ ret = si2183_set_prop(client, 0x1007, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set int sense\n");
+ return ret;
+ }
+
+ /* Control of SQI computation */
+ prop = 0x1e;
+ ret = si2183_set_prop(client, 0x100f, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set sqi comp\n");
+ return ret;
+ }
+
+ /* Transport Stream setting for parallel mode */
+ prop = 0x0104 | (dev->ts_clock_inv ? 0x0000 : 0x1000);
+ ret = si2183_set_prop(client, 0x1009, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set par_ts\n");
+ return ret;
+ }
+
+ /* Transport Stream setting for serial mode */
+ prop = 0x230C | (dev->ts_clock_inv ? 0x0000 : 0x1000);
+ ret = si2183_set_prop(client, 0x1008, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set ser_ts\n");
+ return ret;
+ }
+
+ /* Transport Stream setting for parallel mode - secondary*/
+ prop = 0x08e3;
+ ret = si2183_set_prop(client, 0x1015, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set int par_ts_sec\n");
+ return ret;
+ }
+
+ /* Transport Stream setting for serial mode - secondary*/
+ prop = 0x01c7;
+ ret = si2183_set_prop(client, 0x1016, &prop);
+ if (ret) {
+ dev_err(&client->dev, "err set int ser_ts_sec\n");
+ return ret;
+ }
+
+ dev->fw_loaded = true;
+warm:
+ dev->active_fe |= (1 << fe->id);
+ return 0;
+
+err:
+ dev_dbg(&client->dev, "init failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_sleep(struct dvb_frontend *fe)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ int ret;
+ struct si2183_cmd cmd;
+
+ dev_dbg(&client->dev, "\n");
+
+ dev->active_fe &= ~(1 << fe->id);
+ if (dev->active_fe)
+ return 0;
+
+ memcpy(cmd.args, "\x13", 1);
+ cmd.wlen = 1;
+ cmd.rlen = 0;
+ ret = si2183_cmd_execute(client, &cmd);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_dbg(&client->dev, "failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_get_tune_settings(struct dvb_frontend *fe,
+ struct dvb_frontend_tune_settings *s)
+{
+ s->min_delay_ms = 900;
+
+ return 0;
+}
+
+#ifdef SI2183_USE_I2C_MUX
+static int si2183_select(struct i2c_mux_core *muxc, u32 chan)
+{
+ struct i2c_client *client = i2c_mux_priv(muxc);
+ int ret;
+ struct si2183_cmd cmd;
+
+ /* open I2C gate */
+ memcpy(cmd.args, "\xc0\x0d\x01", 3);
+ cmd.wlen = 3;
+ cmd.rlen = 0;
+ ret = si2183_cmd_execute_unlocked(client, &cmd);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_dbg(&client->dev, "failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_deselect(struct i2c_mux_core *muxc, u32 chan)
+{
+ struct i2c_client *client = i2c_mux_priv(muxc);
+ int ret;
+ struct si2183_cmd cmd;
+
+ /* close I2C gate */
+ memcpy(cmd.args, "\xc0\x0d\x00", 3);
+ cmd.wlen = 3;
+ cmd.rlen = 0;
+ ret = si2183_cmd_execute_unlocked(client, &cmd);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_dbg(&client->dev, "failed=%d\n", ret);
+ return ret;
+}
+#else
+static int i2c_gate_ctrl(struct dvb_frontend* fe, int enable)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+ struct si2183_cmd cmd;
+
+ memcpy(cmd.args, "\xc0\x0d\x00", 3);
+ if (enable)
+ cmd.args[2] = 1;
+ cmd.wlen = 3;
+ cmd.rlen = 0;
+ return si2183_cmd_execute(dev->base->i2c_gate_client, &cmd);
+}
+#endif
+
+static int si2183_tune(struct dvb_frontend *fe, bool re_tune,
+ unsigned int mode_flags, unsigned int *delay, enum fe_status *status)
+{
+ *delay = HZ / 5;
+ if (re_tune) {
+ int ret = si2183_set_frontend(fe);
+ if (ret)
+ return ret;
+ }
+ return si2183_read_status(fe, status);
+}
+
+static enum dvbfe_algo si2183_get_algo(struct dvb_frontend *fe)
+{
+ return DVBFE_ALGO_HW;
+}
+/*
+static int si2183_set_property(struct dvb_frontend *fe,
+ u32 cmd, u32 data)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case DTV_DELIVERY_SYSTEM:
+ switch (data) {
+ case SYS_DVBS:
+ case SYS_DVBS2:
+ case SYS_DSS:
+ fe->ops.info.frequency_min_hz = 950 * MHz;
+ fe->ops.info.frequency_max_hz = 2150 * MHz;
+ fe->ops.info.frequency_stepsize_hz = 0;
+ break;
+ case SYS_ISDBT:
+ fe->ops.info.frequency_min_hz = 42 * MHz;
+ fe->ops.info.frequency_max_hz = 1002 * MHz;
+ fe->ops.info.frequency_stepsize_hz = 0;
+ break;
+ case SYS_DVBC_ANNEX_A:
+ case SYS_DVBC_ANNEX_B:
+ fe->ops.info.frequency_min_hz = 47 * MHz;
+ fe->ops.info.frequency_max_hz = 862 * MHz;
+ fe->ops.info.frequency_stepsize_hz = 62500;
+ break;
+ case SYS_DVBT:
+ case SYS_DVBT2:
+ default:
+ fe->ops.info.frequency_min_hz = 174 * MHz;
+ fe->ops.info.frequency_max_hz = 862 * MHz;
+ fe->ops.info.frequency_stepsize_hz = 250000;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+*/
+
+static int send_diseqc_cmd(struct dvb_frontend *fe,
+ u8 cont_tone, u8 tone_burst, u8 burst_sel,
+ u8 end_seq, u8 msg_len, u8 *msg)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_cmd cmd;
+ u8 enable = 1;
+
+ cmd.args[0] = 0x8c;
+ cmd.args[1] = enable | (cont_tone << 1)
+ | (tone_burst << 2) | (burst_sel << 3)
+ | (end_seq << 4) | (msg_len << 5);
+
+ if (msg_len > 0)
+ memcpy(&cmd.args[2], msg, msg_len);
+
+ cmd.wlen = 8;
+ cmd.rlen = 1;
+ return si2183_cmd_execute(client, &cmd);
+}
+
+static int si2183_set_tone(struct dvb_frontend *fe, enum fe_sec_tone_mode tone)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ int ret;
+ u8 cont_tone;
+
+ switch (tone) {
+ case SEC_TONE_ON:
+ cont_tone = 1;
+ break;
+ case SEC_TONE_OFF:
+ cont_tone = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = send_diseqc_cmd(fe, cont_tone, 0, 0, 1, 0, NULL);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_err(&client->dev, "set_tone failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_diseqc_send_burst(struct dvb_frontend *fe,
+ enum fe_sec_mini_cmd burst)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ int ret;
+ u8 burst_sel;
+
+ switch (burst) {
+ case SEC_MINI_A:
+ burst_sel = 0;
+ break;
+ case SEC_MINI_B:
+ burst_sel = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = send_diseqc_cmd(fe, 0, 1, burst_sel, 1, 0, NULL);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_err(&client->dev, "set_tone failed=%d\n", ret);
+ return ret;
+}
+
+static int si2183_diseqc_send_msg(struct dvb_frontend *fe,
+ struct dvb_diseqc_master_cmd *d)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ int ret;
+ u8 remaining = d->msg_len;
+ u8 *p = d->msg;
+ u8 len = 0;
+
+ while (remaining > 0) {
+ p += len;
+ len = (remaining > 6) ? 6 : remaining;
+ remaining -= len;
+ ret = send_diseqc_cmd(fe, 0, 0, 0,
+ (remaining == 0) ? 1 : 0, len, p);
+ if (ret)
+ goto err;
+ msleep(50);
+ }
+
+ return 0;
+err:
+ dev_err(&client->dev, "set_tone failed=%d\n", ret);
+ return ret;
+}
+
+/*
+static void spi_read(struct dvb_frontend *fe, struct ecp3_info *ecp3inf)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+
+
+ if (dev->read_properties)
+ dev->read_properties(client->adapter,ecp3inf->reg, &(ecp3inf->data));
+
+ return ;
+}
+
+static void spi_write(struct dvb_frontend *fe,struct ecp3_info *ecp3inf)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+
+ if (dev->write_properties)
+ dev->write_properties(client->adapter,ecp3inf->reg, ecp3inf->data);
+ return ;
+}
+
+struct eeprom_info {
+ __u8 reg;
+ __u8 data;
+};
+
+static void eeprom_read(struct dvb_frontend *fe, struct eeprom_info *eepinf)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+
+ if (dev->read_eeprom)
+ dev->read_eeprom(client->adapter,eepinf->reg, &(eepinf->data));
+ return ;
+}
+
+static void eeprom_write(struct dvb_frontend *fe,struct eeprom_info *eepinf)
+{
+ struct i2c_client *client = fe->demodulator_priv;
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+
+ if (dev->write_eeprom)
+ dev->write_eeprom(client->adapter,eepinf->reg, eepinf->data);
+ return ;
+}
+*/
+static const struct dvb_frontend_ops si2183_ops = {
+ .delsys = {SYS_DVBT, SYS_DVBT2,
+ SYS_ISDBT,
+ SYS_DVBC_ANNEX_A,SYS_DVBC_ANNEX_B,
+ SYS_DVBS, SYS_DVBS2, SYS_DSS},
+ .info = {
+ .name = "Silicon Labs Si2183",
+ .symbol_rate_min = 1000000,
+ .symbol_rate_max = 45000000,
+ .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_32 |
+ FE_CAN_QAM_64 |
+ FE_CAN_QAM_128 |
+ FE_CAN_QAM_256 |
+ FE_CAN_QAM_AUTO |
+ FE_CAN_TRANSMISSION_MODE_AUTO |
+ FE_CAN_GUARD_INTERVAL_AUTO |
+ FE_CAN_HIERARCHY_AUTO |
+ FE_CAN_MUTE_TS |
+ FE_CAN_2G_MODULATION |
+ FE_CAN_MULTISTREAM
+ },
+
+ .get_tune_settings = si2183_get_tune_settings,
+
+ .init = si2183_init,
+ .sleep = si2183_sleep,
+
+ .set_frontend = si2183_set_frontend,
+
+ .read_status = si2183_read_status,
+ .read_signal_strength = si2183_read_signal_strength,
+ .read_snr = si2183_read_snr,
+ .read_ber = si2183_read_ber,
+ .read_ucblocks = si2183_read_ucblocks,
+
+ .get_frontend_algo = si2183_get_algo,
+ .tune = si2183_tune,
+
+ // .set_property = si2183_set_property,
+
+ .set_tone = si2183_set_tone,
+ .diseqc_send_burst = si2183_diseqc_send_burst,
+ .diseqc_send_master_cmd = si2183_diseqc_send_msg,
+#ifndef SI2183_USE_I2C_MUX
+ .i2c_gate_ctrl = i2c_gate_ctrl,
+#endif
+
+ // .spi_read = spi_read,
+ // .spi_write = spi_write,
+ // .eeprom_read = eeprom_read,
+ // .eeprom_write = eeprom_write,
+};
+
+
+static struct si_base *match_base(struct i2c_adapter *i2c, u8 adr)
+{
+ struct si_base *p;
+
+ list_for_each_entry(p, &silist, silist)
+ if (p->i2c == i2c)// && p->adr == adr) lja: TO FIX
+ return p;
+ return NULL;
+}
+
+static int si2183_probe(struct i2c_client *client)
+{
+ struct si2183_config *config = client->dev.platform_data;
+ struct si2183_dev *dev;
+ struct si_base *base;
+ int ret;
+
+ dev_dbg(&client->dev, "\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ ret = -ENOMEM;
+ dev_err(&client->dev, "kzalloc() failed\n");
+ goto err;
+ }
+
+ base = match_base(client->adapter, client->addr);
+ if (base) {
+ base->count++;
+ dev->base = base;
+ } else {
+ base = kzalloc(sizeof(struct si_base), GFP_KERNEL);
+ if (!base)
+ goto err_kfree;
+ base->i2c = client->adapter;
+ base->adr = client->addr;
+ base->count = 1;
+ dev->base = base;
+ list_add(&base->silist, &silist);
+
+ mutex_init(&base->i2c_mutex);
+#ifdef SI2183_USE_I2C_MUX
+#if 1
+ /* create mux i2c adapter for tuner */
+ base->muxc = i2c_mux_alloc(client->adapter, &client->adapter->dev,
+ 1, 0, I2C_MUX_LOCKED,
+ si2183_select, si2183_deselect);
+ if (!base->muxc) {
+ ret = -ENOMEM;
+ goto err_base_kfree;
+ }
+ base->muxc->priv = client;
+ ret = i2c_mux_add_adapter(base->muxc, 0, 0);
+ if (ret)
+ goto err_base_kfree;
+ base->tuner_adapter = base->muxc->adapter[0];
+#else // ???
+ /* create mux i2c adapter for tuners */
+ base->tuner_adapter = i2c_add_mux_adapter(client->adapter, &client->adapter->dev,
+ client, 0, 0, 0, si2183_select, si2183_deselect);
+ if (base->tuner_adapter == NULL) {
+ ret = -ENODEV;
+ goto err_base_kfree;
+ }
+#endif
+#else
+ base->tuner_adapter = client->adapter;
+ base->i2c_gate_client = client;
+#endif
+ }
+
+ /* create dvb_frontend */
+ memcpy(&dev->fe.ops, &si2183_ops, sizeof(struct dvb_frontend_ops));
+ dev->fe.demodulator_priv = client;
+ *config->i2c_adapter = base->tuner_adapter;
+ *config->fe = &dev->fe;
+ dev->ts_mode = config->ts_mode;
+ dev->ts_clock_inv = config->ts_clock_inv;
+ dev->ts_clock_gapped = config->ts_clock_gapped;
+ dev->agc_mode = config->agc_mode;
+ dev->RF_switch = config->RF_switch;
+ dev->rf_in = config->rf_in;
+ dev->fw_loaded = false;
+ dev->delivery_system = 0;
+ dev->snr = 0;
+ dev->stat_resp = 0;
+ dev->active_fe = 0;
+ dev->start_clk_mode = config->start_clk_mode;
+ dev->TS_switch = config->TS_switch;
+ dev->LED_switch = config->LED_switch;
+
+ dev->write_properties = config->write_properties;
+ dev->read_properties = config->read_properties;
+ dev->write_eeprom = config->write_eeprom;
+ dev->read_eeprom = config->read_eeprom;
+
+ i2c_set_clientdata(client, dev);
+
+#ifndef SI2183_USE_I2C_MUX
+ /* leave gate open for tuner to init */
+ i2c_gate_ctrl(&dev->fe, 1);
+#endif
+ dev_info(&client->dev, "Silicon Labs Si2183 successfully attached\n");
+ return 0;
+err_base_kfree:
+ kfree(base);
+err_kfree:
+ kfree(dev);
+err:
+ dev_dbg(&client->dev, "probe failed=%d\n", ret);
+ return ret;
+}
+
+static void si2183_remove(struct i2c_client *client)
+{
+ struct si2183_dev *dev = i2c_get_clientdata(client);
+
+ dev_dbg(&client->dev, "\n");
+
+ dev->base->count--;
+ if (dev->base->count == 0) {
+#ifdef SI2183_USE_I2C_MUX
+ i2c_mux_del_adapters(dev->base->muxc);
+#endif
+ list_del(&dev->base->silist);
+ kfree(dev->base);
+ }
+
+ dev->fe.ops.release = NULL;
+ dev->fe.demodulator_priv = NULL;
+
+ kfree(dev);
+}
+
+static const struct i2c_device_id si2183_id_table[] = {
+ {"si2183", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, si2183_id_table);
+
+static struct i2c_driver si2183_driver = {
+ .driver = {
+ .name = "si2183",
+ },
+ .probe = si2183_probe,
+ .remove = si2183_remove,
+ .id_table = si2183_id_table,
+};
+
+module_i2c_driver(si2183_driver);
+
+MODULE_AUTHOR("Luis Alves <ljalvs@gmail.com>");
+MODULE_DESCRIPTION("Silicon Labs Si2183 DVB-T/T2/C/C2/S/S2 demodulator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/dvb-frontends/si2183.h b/drivers/media/dvb-frontends/si2183.h
new file mode 100644
index 000000000000..6c654bba3b2c
--- /dev/null
+++ b/drivers/media/dvb-frontends/si2183.h
@@ -0,0 +1,66 @@
+/*
+ * Silicon Labs Si2183(2) DVB-T/T2/C/C2/S/S2 demodulator driver
+ *
+ * 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.
+ */
+
+#ifndef SI2183_H
+#define SI2183_H
+
+#include <linux/dvb/frontend.h>
+/*
+ * I2C address
+ * 0x64
+ */
+struct si2183_config {
+ /*
+ * frontend
+ * returned by driver
+ */
+ struct dvb_frontend **fe;
+
+ /*
+ * tuner I2C adapter
+ * returned by driver
+ */
+ struct i2c_adapter **i2c_adapter;
+
+ /* TS mode */
+#define SI2183_TS_PARALLEL 0x06
+#define SI2183_TS_SERIAL 0x03
+ u8 ts_mode;
+
+ /* TS clock inverted */
+ bool ts_clock_inv;
+
+ int start_clk_mode; //0 terrestrial mode 1: satellite mode
+
+ /* TS clock gapped */
+ bool ts_clock_gapped;
+ /*agc*/
+ u8 agc_mode;
+
+ /*rf switch*/
+ void (*RF_switch)(struct i2c_adapter * i2c,u8 rf_in,u8 flag);
+ /*rf no.*/
+ u8 rf_in;
+
+ void (*TS_switch)(struct i2c_adapter * i2c,u8 flag);
+ void (*LED_switch)(struct i2c_adapter * i2c,u8 flag);
+ //update the FW.
+ void (*write_properties) (struct i2c_adapter *i2c,u8 reg, u32 buf);
+ void (*read_properties) (struct i2c_adapter *i2c,u8 reg, u32 *buf);
+ // EEPROM access
+ void (*write_eeprom) (struct i2c_adapter *i2c,u8 reg, u8 buf);
+ void (*read_eeprom) (struct i2c_adapter *i2c,u8 reg, u8 *buf);
+};
+
+#endif
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index 0c01b0298099..a2902b3ea4df 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -14,6 +14,7 @@ config MEDIA_TUNER
select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_TDA9887 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_MC44S803 if MEDIA_SUBDRV_AUTOSELECT
+ select MEDIA_TUNER_AV201X if MEDIA_SUBDRV_AUTOSELECT
comment "Tuner drivers auto-selected by 'Autoselect ancillary drivers'"
depends on MEDIA_HIDE_ANCILLARY_SUBDRV
@@ -297,4 +298,11 @@ config MEDIA_TUNER_XC5000
This device is only used inside a SiP called together with a
demodulator for now.
+config MEDIA_TUNER_AV201X
+ tristate "Airoha Technology AV201x silicon tuner"
+ depends on MEDIA_SUPPORT && I2C
+ default m if !MEDIA_SUBDRV_AUTOSELECT
+ help
+ Airoha Technology AV201x silicon tuner driver.
+
endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index bd350a285aad..115bd5e7b9bd 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -8,6 +8,7 @@ tda18271-objs := tda18271-maps.o tda18271-common.o tda18271-fe.o
# Please keep it alphabetically sorted by Kconfig name
# (e. g. LC_ALL=C sort Makefile)
+obj-$(CONFIG_MEDIA_TUNER_AV201X) += av201x.o
obj-$(CONFIG_MEDIA_TUNER_E4000) += e4000.o
obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o
obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
diff --git a/drivers/media/tuners/av201x.c b/drivers/media/tuners/av201x.c
new file mode 100644
index 000000000000..605065381bec
--- /dev/null
+++ b/drivers/media/tuners/av201x.c
@@ -0,0 +1,302 @@
+/*
+ * AV201x Airoha Technology silicon tuner driver
+ *
+ * Copyright (C) 2014 Luis Alves <ljalvs@gmail.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 "av201x.h"
+#include "av201x_priv.h"
+
+/* write multiple (continuous) registers */
+static int av201x_wrm(struct av201x_priv *priv, u8 *buf, int len)
+{
+ int ret;
+ struct i2c_msg msg = {
+ .addr = priv->cfg->i2c_address,
+ .flags = 0, .buf = buf, .len = len };
+
+ dev_dbg(&priv->i2c->dev, "%s() i2c wrm @0x%02x (len=%d) ",
+ __func__, buf[0], len);
+
+ ret = i2c_transfer(priv->i2c, &msg, 1);
+ if (ret < 0) {
+ dev_warn(&priv->i2c->dev,
+ "%s: i2c wrm err(%i) @0x%02x (len=%d)\n",
+ KBUILD_MODNAME, ret, buf[0], len);
+ return ret;
+ }
+ return 0;
+}
+
+/* write one register */
+static int av201x_wr(struct av201x_priv *priv, u8 addr, u8 data)
+{
+ u8 buf[] = { addr, data };
+ return av201x_wrm(priv, buf, 2);
+}
+
+/* read multiple (continuous) registers starting at addr */
+static int av201x_rdm(struct av201x_priv *priv, u8 addr, u8 *buf, int len)
+{
+ int ret;
+ struct i2c_msg msg[] = {
+ { .addr = priv->cfg->i2c_address, .flags = 0,
+ .buf = &addr, .len = 1 },
+ { .addr = priv->cfg->i2c_address, .flags = I2C_M_RD,
+ .buf = buf, .len = len }
+ };
+
+ dev_dbg(&priv->i2c->dev, "%s() i2c rdm @0x%02x (len=%d)\n",
+ __func__, addr, len);
+
+ ret = i2c_transfer(priv->i2c, msg, 2);
+ if (ret < 0) {
+ dev_warn(&priv->i2c->dev,
+ "%s: i2c rdm err(%i) @0x%02x (len=%d)\n",
+ KBUILD_MODNAME, ret, addr, len);
+ return ret;
+ }
+ return 0;
+}
+
+/* read one register */
+static int av201x_rd(struct av201x_priv *priv, u8 addr, u8 *data)
+{
+ return av201x_rdm(priv, addr, data, 1);
+}
+
+/* read register, apply masks, write back */
+static int av201x_regmask(struct av201x_priv *priv,
+ u8 reg, u8 setmask, u8 clrmask)
+{
+ int ret;
+ u8 b = 0;
+ if (clrmask != 0xff) {
+ ret = av201x_rd(priv, reg, &b);
+ if (ret)
+ return ret;
+ b &= ~clrmask;
+ }
+ return av201x_wr(priv, reg, b | setmask);
+}
+
+static int av201x_wrtable(struct av201x_priv *priv,
+ struct av201x_regtable *regtable, int len)
+{
+ int ret, i;
+
+ for (i = 0; i < len; i++) {
+ ret = av201x_regmask(priv, regtable[i].addr,
+ regtable[i].setmask, regtable[i].clrmask);
+ if (ret)
+ return ret;
+ if (regtable[i].sleep)
+ msleep(regtable[i].sleep);
+ }
+ return 0;
+}
+
+static void av201x_release(struct dvb_frontend *fe)
+{
+ struct av201x_priv *priv = fe->tuner_priv;
+ dev_dbg(&priv->i2c->dev, "%s()\n", __func__);
+
+ kfree(fe->tuner_priv);
+ fe->tuner_priv = NULL;
+}
+
+static int av201x_init(struct dvb_frontend *fe)
+{
+ struct av201x_priv *priv = fe->tuner_priv;
+ int ret;
+ dev_dbg(&priv->i2c->dev, "%s()\n", __func__);
+
+ ret = av201x_wrtable(priv, av201x_inittuner0,
+ ARRAY_SIZE(av201x_inittuner0));
+
+ switch (priv->cfg->id) {
+ case ID_AV2011:
+ ret |= av201x_wrtable(priv, av201x_inittuner1a,
+ ARRAY_SIZE(av201x_inittuner1a));
+ break;
+ case ID_AV2012:
+ default:
+ ret |= av201x_wrtable(priv, av201x_inittuner1b,
+ ARRAY_SIZE(av201x_inittuner1b));
+ break;
+ }
+
+ ret |= av201x_wrtable(priv, av201x_inittuner2,
+ ARRAY_SIZE(av201x_inittuner2));
+
+ ret |= av201x_wr(priv, REG_TUNER_CTRL, 0x96);
+
+ msleep(120);
+
+ if (ret)
+ dev_dbg(&priv->i2c->dev, "%s() failed\n", __func__);
+ return ret;
+}
+
+static int av201x_sleep(struct dvb_frontend *fe)
+{
+ struct av201x_priv *priv = fe->tuner_priv;
+ int ret;
+ dev_dbg(&priv->i2c->dev, "%s()\n", __func__);
+
+ ret = av201x_regmask(priv, REG_TUNER_CTRL, AV201X_SLEEP, 0);
+ if (ret)
+ dev_dbg(&priv->i2c->dev, "%s() failed\n", __func__);
+ return ret;
+}
+
+static int av201x_set_params(struct dvb_frontend *fe)
+{
+ struct av201x_priv *priv = fe->tuner_priv;
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ u32 n, bw, bf;
+ u8 buf[5];
+ int ret;
+
+ dev_dbg(&priv->i2c->dev, "%s() delivery_system=%d frequency=%d " \
+ "symbol_rate=%d\n", __func__,
+ c->delivery_system, c->frequency, c->symbol_rate);
+
+ /*
+ ** PLL setup **
+ RF = (pll_N * ref_freq) / pll_M
+ pll_M = fixed 0x10000
+ PLL output is divided by 2
+ REG_FN = pll_M<24:0>
+ */
+ buf[0] = REG_FN;
+ n = DIV_ROUND_CLOSEST(c->frequency, priv->cfg->xtal_freq);
+ buf[1] = (n > 0xff) ? 0xff : (u8) n;
+ n = DIV_ROUND_CLOSEST((c->frequency / 1000) << 17, priv->cfg->xtal_freq / 1000);
+ buf[2] = (u8) (n >> 9);
+ buf[3] = (u8) (n >> 1);
+ buf[4] = (u8) (((n << 7) & 0x80) | 0x50);
+ ret = av201x_wrm(priv, buf, 5);
+ if (ret)
+ goto exit;
+
+ msleep(20);
+
+ /* set bandwidth */
+ bw = (c->symbol_rate / 1000) * 135/200;
+ if (c->symbol_rate < 6500000)
+ bw += 6000;
+ bw += 2000;
+ bw *= 108/100;
+
+ /* check limits (4MHz < bw < 40MHz) */
+ if (bw > 40000)
+ bw = 40000;
+ else if (bw < 4000)
+ bw = 4000;
+
+ /* bandwidth step = 211kHz */
+ bf = DIV_ROUND_CLOSEST(bw * 127, 21100);
+ ret = av201x_wr(priv, REG_BWFILTER, (u8) bf);
+
+ /* enable fine tune agc */
+ ret |= av201x_wr(priv, REG_FT_CTRL, AV201X_FT_EN | AV201X_FT_BLK);
+
+ ret |= av201x_wr(priv, REG_TUNER_CTRL, 0x96);
+ msleep(20);
+exit:
+ if (ret)
+ dev_dbg(&priv->i2c->dev, "%s() failed\n", __func__);
+ return ret;
+}
+
+static int AV201x_agc [] = { 0, 82, 100, 116, 140, 162, 173, 187, 210, 223, 254, 255};
+static int AV201x_level_dBm_10[] = { 90, -50, -263, -361, -463, -563, -661, -761, -861, -891, -904, -910};
+
+static int av201x_get_rf_strength(struct dvb_frontend *fe, u16 *st)
+{
+ struct av201x_priv *priv = fe->tuner_priv;
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ int if_agc, index, table_length, slope, *x, *y;
+
+ if_agc = *st;
+ x = AV201x_agc;
+ y = AV201x_level_dBm_10;
+ table_length = sizeof(AV201x_agc)/sizeof(int);
+
+
+ /* Finding in which segment the if_agc value is */
+ for (index = 0; index < table_length; index ++)
+ if (x[index] > if_agc ) break;
+
+ /* Computing segment slope */
+ slope = ((y[index]-y[index-1])*1000)/(x[index]-x[index-1]);
+ /* Linear approximation of rssi value in segment (rssi values will be in 0.1dBm unit: '-523' means -52.3 dBm) */
+ *st = 1000 + ((y[index-1] + ((if_agc - x[index-1])*slope + 500)/1000))/10;
+
+ c->strength.len = 1;
+ c->strength.stat[0].scale = FE_SCALE_DECIBEL;
+ c->strength.stat[0].svalue = ((y[index-1] + ((if_agc - x[index-1])*slope + 500)/1000)) * 100;
+
+ return 0;
+}
+
+
+static const struct dvb_tuner_ops av201x_tuner_ops = {
+ .info = {
+ .name = "Airoha Technology AV201x",
+ .frequency_min_hz = 850 * MHz,
+ .frequency_max_hz = 2300 * MHz,
+ },
+
+ .release = av201x_release,
+
+ .init = av201x_init,
+ .sleep = av201x_sleep,
+ .set_params = av201x_set_params,
+ .get_rf_strength = av201x_get_rf_strength,
+};
+
+struct dvb_frontend *av201x_attach(struct dvb_frontend *fe,
+ struct av201x_config *cfg, struct i2c_adapter *i2c)
+{
+ struct av201x_priv *priv = NULL;
+
+ priv = kzalloc(sizeof(struct av201x_priv), GFP_KERNEL);
+ if (priv == NULL) {
+ dev_dbg(&i2c->dev, "%s() attach failed\n", __func__);
+ return NULL;
+ }
+
+ priv->cfg = cfg;
+ priv->i2c = i2c;
+
+ dev_info(&priv->i2c->dev,
+ "%s: Airoha Technology AV201x successfully attached\n",
+ KBUILD_MODNAME);
+
+ memcpy(&fe->ops.tuner_ops, &av201x_tuner_ops,
+ sizeof(struct dvb_tuner_ops));
+
+ fe->tuner_priv = priv;
+ return fe;
+}
+EXPORT_SYMBOL_GPL(av201x_attach);
+
+MODULE_DESCRIPTION("Airoha Technology AV201x silicon tuner driver");
+MODULE_AUTHOR("Luis Alves <ljalvs@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/tuners/av201x.h b/drivers/media/tuners/av201x.h
new file mode 100644
index 000000000000..1fdf70a77242
--- /dev/null
+++ b/drivers/media/tuners/av201x.h
@@ -0,0 +1,55 @@
+/*
+ * AV201x Airoha Technology silicon tuner driver
+ *
+ * Copyright (C) 2014 Luis Alves <ljalvs@gmail.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.
+ */
+
+#ifndef AV201X_H
+#define AV201X_H
+
+#include <linux/kconfig.h>
+#include <media/dvb_frontend.h>
+
+typedef enum av201x_id {
+ ID_AV2011,
+ ID_AV2012,
+ ID_AV2018,
+} av201x_id_t;
+
+struct av201x_config {
+ /* tuner i2c address */
+ u8 i2c_address;
+ /* tuner type */
+ av201x_id_t id;
+
+ /* crystal freq in kHz */
+ u32 xtal_freq;
+};
+
+#if 1 // IS_REACHABLE(CONFIG_MEDIA_TUNER_AV201X)
+extern struct dvb_frontend *av201x_attach(struct dvb_frontend *fe,
+ struct av201x_config *cfg, struct i2c_adapter *i2c);
+#else
+static inline struct dvb_frontend *av201x_attach(struct dvb_frontend *fe,
+ struct av201x_config *cfg, struct i2c_adapter *i2c)
+{
+ printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+ return NULL;
+}
+#endif
+
+#endif /* AV201X_H */
diff --git a/drivers/media/tuners/av201x_priv.h b/drivers/media/tuners/av201x_priv.h
new file mode 100644
index 000000000000..c56ba203d39c
--- /dev/null
+++ b/drivers/media/tuners/av201x_priv.h
@@ -0,0 +1,110 @@
+/*
+ * AV201x Airoha Technology silicon tuner driver
+ *
+ * Copyright (C) 2014 Luis Alves <ljalvs@gmail.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.
+ */
+
+#ifndef AV201X_PRIV_H
+#define AV201X_PRIV_H
+
+struct av201x_priv {
+ struct av201x_config *cfg;
+ struct i2c_adapter *i2c;
+};
+
+enum av201x_regs_addr {
+ REG_FN = 0x00,
+ REG_BWFILTER = 0x05,
+ REG_TUNER_STAT = 0x0b,
+ REG_TUNER_CTRL = 0x0c,
+ REG_FT_CTRL = 0x25,
+};
+
+/* REG_TUNER_STAT */
+#define AV201X_PLLLOCK (1<<0)
+
+/* REG_TUNER_CTRL */
+#define AV201X_SLEEP (1<<5)
+#define AV201X_RFLP (1<<6)
+
+/* REG_FT_CTRL */
+#define AV201X_FT_EN (1<<1)
+#define AV201X_FT_BLK (1<<2)
+
+struct av201x_regtable {
+ u8 addr;
+ u8 setmask;
+ u8 clrmask;
+ int sleep;
+};
+
+static struct av201x_regtable av201x_inittuner0[] = {
+ {0x00, 0x38, 0xff, 0},
+ {0x01, 0x00, 0xff, 0},
+ {0x02, 0x00, 0xff, 0},
+ {0x03, 0x50, 0xff, 0},
+ {0x04, 0x1f, 0xff, 0},
+ {0x05, 0xa3, 0xff, 0},
+ {0x06, 0xfd, 0xff, 0},
+ {0x07, 0x58, 0xff, 0},
+ {0x08, 0x36, 0xff, 0},
+ {0x09, 0xc2, 0xff, 0},
+ {0x0a, 0x88, 0xff, 0},
+ {0x0b, 0xb4, 0xff, 20},
+ {0x0d, 0x40, 0xff, 0},
+};
+
+static struct av201x_regtable av201x_inittuner1a[] = {
+ {0x0e, 0x94, 0xff, 0},
+ {0x0f, 0x9a, 0xff, 0},
+};
+
+static struct av201x_regtable av201x_inittuner1b[] = {
+ {0x0e, 0x5b, 0xff, 0},
+ {0x0f, 0x6a, 0xff, 0},
+};
+
+static struct av201x_regtable av201x_inittuner2[] = {
+ {0x10, 0x66, 0xff, 0},
+ {0x11, 0x40, 0xff, 0},
+ {0x12, 0x80, 0xff, 0},
+ {0x13, 0x2b, 0xff, 0},
+ {0x14, 0x6a, 0xff, 0},
+ {0x15, 0x50, 0xff, 0},
+ {0x16, 0x91, 0xff, 0},
+ {0x17, 0x27, 0xff, 0},
+ {0x18, 0x8f, 0xff, 0},
+ {0x19, 0xcc, 0xff, 0},
+ {0x1a, 0x21, 0xff, 0},
+ {0x1b, 0x10, 0xff, 0},
+ {0x1c, 0x80, 0xff, 0},
+ {0x1d, 0x02, 0xff, 0},
+ {0x1e, 0xf5, 0xff, 0},
+ {0x1f, 0x7f, 0xff, 0},
+ {0x20, 0x4a, 0xff, 0},
+ {0x21, 0x9b, 0xff, 0},
+ {0x22, 0xe0, 0xff, 0},
+ {0x23, 0xe0, 0xff, 0},
+ {0x24, 0x36, 0xff, 0},
+ {0x25, 0x00, 0xff, 0},
+ {0x26, 0xab, 0xff, 0},
+ {0x27, 0x97, 0xff, 0},
+ {0x28, 0xc5, 0xff, 0},
+ {0x29, 0xa8, 0xff, 20},
+};
+
+#endif /* AV201X_PRIV_H */
diff --git a/drivers/media/usb/dvb-usb/Kconfig b/drivers/media/usb/dvb-usb/Kconfig
index f10fe27e2a4d..bd7e43e139f7 100644
--- a/drivers/media/usb/dvb-usb/Kconfig
+++ b/drivers/media/usb/dvb-usb/Kconfig
@@ -346,4 +346,13 @@ config DVB_USB_VP7045
DVB-T USB2.0 receivers.
+config DVB_USB_TBS5580
+ tristate "Turbosight TBS5580 CI support"
+ depends on DVB_USB
+ select DVB_SI2183 if MEDIA_SUBDRV_AUTOSELECT
+ select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
+ select MEDIA_TUNER_AV201X if MEDIA_SUBDRV_AUTOSELECT
+ help
+ Say Y here to support the Turbosight TBS5580 CI USB2 DVB-T/T2/C/C2/S/S2/S2x device
+
endif
diff --git a/drivers/media/usb/dvb-usb/Makefile b/drivers/media/usb/dvb-usb/Makefile
index c22514948db2..879bc065ad35 100644
--- a/drivers/media/usb/dvb-usb/Makefile
+++ b/drivers/media/usb/dvb-usb/Makefile
@@ -80,6 +80,9 @@ obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-az6027.o
dvb-usb-technisat-usb2-objs := technisat-usb2.o
obj-$(CONFIG_DVB_USB_TECHNISAT_USB2) += dvb-usb-technisat-usb2.o
+dvb-usb-tbs5580-objs = tbs5580.o
+obj-$(CONFIG_DVB_USB_TBS5580) += dvb-usb-tbs5580.o
+
ccflags-y += -I$(srctree)/drivers/media/dvb-frontends/
# due to tuner-xc3028
ccflags-y += -I$(srctree)/drivers/media/tuners
diff --git a/drivers/media/usb/dvb-usb/tbs5580.c b/drivers/media/usb/dvb-usb/tbs5580.c
new file mode 100644
index 000000000000..3edcd412d5f9
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/tbs5580.c
@@ -0,0 +1,761 @@
+/*
+ * TurboSight TBS 5580se driver
+ *
+ * Copyright (c) 2017 Davin zhang <smiledavin@gmail.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.
+ *
+ */
+
+#include <linux/version.h>
+#include "tbs5580.h"
+#include "si2183.h"
+#include "si2157.h"
+#include "av201x.h"
+#include <media/dvb_ca_en50221.h>
+
+#define TBS5580_READ_MSG 0
+#define TBS5580_WRITE_MSG 1
+
+#define TBS5580_VOLTAGE_CTRL (0x1800)
+
+
+struct tbs5580_state {
+ struct i2c_client *i2c_client_demod;
+ struct i2c_client *i2c_client_tuner;
+ struct dvb_ca_en50221 ca;
+ struct mutex ca_mutex;
+};
+
+static struct av201x_config tbs5580_av201x_cfg = {
+ .i2c_address = 0x62,
+ .id = ID_AV2018,
+ .xtal_freq = 27000,
+};
+
+/* debug */
+static int dvb_usb_tbs5580_debug;
+module_param_named(debug, dvb_usb_tbs5580_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info 2=xfer (or-able))."
+ DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int tbs5580_op_rw(struct usb_device *dev, u8 request, u16 value,
+ u16 index, u8 * data, u16 len, int flags)
+{
+ int ret;
+ void *u8buf;
+
+ unsigned int pipe = (flags == TBS5580_READ_MSG) ?
+ usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0);
+ u8 request_type = (flags == TBS5580_READ_MSG) ? USB_DIR_IN :
+ USB_DIR_OUT;
+ u8buf = kmalloc(len, GFP_KERNEL);
+ if (!u8buf)
+ return -ENOMEM;
+
+ if (flags == TBS5580_WRITE_MSG)
+ memcpy(u8buf, data, len);
+ ret = usb_control_msg(dev, pipe, request, request_type |
+ USB_TYPE_VENDOR, value, index , u8buf, len, 2000);
+
+ if (flags == TBS5580_READ_MSG)
+ memcpy(data, u8buf, len);
+ kfree(u8buf);
+ return ret;
+}
+
+static int tbs5580_read_attribute_mem(struct dvb_ca_en50221 *ca,
+ int slot, int address)
+{
+ struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+ struct tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ u8 buf[4], rbuf[3];
+ int ret;
+
+ if (0 != slot)
+ return -EINVAL;
+
+ buf[0] = 1;
+ buf[1] = 0;
+ buf[2] = (address >> 8) & 0x0f;
+ buf[3] = address;
+
+ //msleep(10);
+
+ mutex_lock(&state->ca_mutex);
+
+ ret = tbs5580_op_rw(d->udev, 0xa4, 0, 0, buf, 4, TBS5580_WRITE_MSG);
+
+ //msleep(1);
+
+ ret = tbs5580_op_rw(d->udev, 0xa5, 0, 0, rbuf, 1, TBS5580_READ_MSG);
+
+ mutex_unlock(&state->ca_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return rbuf[0];
+}
+
+static int tbs5580_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 tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ u8 buf[5];//, rbuf[1];
+ int ret;
+
+ if (0 != slot)
+ return -EINVAL;
+
+ buf[0] = 1;
+ buf[1] = 0;
+ buf[2] = (address >> 8) & 0x0f;
+ buf[3] = address;
+ buf[4] = value;
+
+ mutex_lock(&state->ca_mutex);
+
+ ret = tbs5580_op_rw(d->udev, 0xa2, 0, 0, buf, 5, TBS5580_WRITE_MSG);
+
+ //msleep(1);
+
+ //ret = tbs5580_op_rw(d->udev, 0xa5, 0, 0, rbuf, 1, TBS5580_READ_MSG);
+
+ mutex_unlock(&state->ca_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int tbs5580_read_cam_control(struct dvb_ca_en50221 *ca, int slot,
+ u8 address)
+{
+ struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+ struct tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ u8 buf[4], rbuf[1];
+ int ret;
+
+ if (0 != slot)
+ return -EINVAL;
+
+ buf[0] = 1;
+ buf[1] = 1;
+ buf[2] = (address >> 8) & 0x0f;
+ buf[3] = address;
+
+ mutex_lock(&state->ca_mutex);
+
+ ret = tbs5580_op_rw(d->udev, 0xa4, 0, 0, buf, 4, TBS5580_WRITE_MSG);
+
+ //msleep(10);
+
+ ret = tbs5580_op_rw(d->udev, 0xa5, 0, 0, rbuf, 1, TBS5580_READ_MSG);
+
+ mutex_unlock(&state->ca_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return rbuf[0];
+}
+
+static int tbs5580_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 tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ u8 buf[5];//, rbuf[1];
+ int ret;
+
+ if (0 != slot)
+ return -EINVAL;
+
+ buf[0] = 1;
+ buf[1] = 1;
+ buf[2] = (address >> 8) & 0x0f;
+ buf[3] = address;
+ buf[4] = value;
+
+ mutex_lock(&state->ca_mutex);
+
+ ret = tbs5580_op_rw(d->udev, 0xa2, 0, 0, buf, 5, TBS5580_WRITE_MSG);
+
+ //msleep(1);
+
+ //ret = tbs5580_op_rw(d->udev, 0xa5, 0, 0,
+ // rbuf, 1, TBS5580_READ_MSG);
+
+ mutex_unlock(&state->ca_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int tbs5580_set_video_port(struct dvb_ca_en50221 *ca,
+ int slot, int enable)
+{
+ struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+ struct tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ u8 buf[2];
+ int ret;
+
+ if (0 != slot)
+ return -EINVAL;
+
+ buf[0] = 2;
+ buf[1] = enable;
+
+ mutex_lock(&state->ca_mutex);
+
+ ret = tbs5580_op_rw(d->udev, 0xa6, 0, 0, buf, 2, TBS5580_WRITE_MSG);
+
+ mutex_unlock(&state->ca_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ if (enable != buf[1]) {
+ err("CI not %sabled.", enable ? "en" : "dis");
+ return -EIO;
+ }
+
+ info("CI %sabled.", enable ? "en" : "dis");
+ return 0;
+}
+
+static int tbs5580_slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
+{
+ return tbs5580_set_video_port(ca, slot, /* enable */ 0);
+}
+
+static int tbs5580_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
+{
+ return tbs5580_set_video_port(ca, slot, /* enable */ 1);
+}
+
+static int tbs5580_slot_reset(struct dvb_ca_en50221 *ca, int slot)
+{
+ struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+ struct tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ u8 buf[2];
+ int ret;
+
+ if (0 != slot) {
+ return -EINVAL;
+ }
+
+ buf[0] = 1;
+ buf[1] = 0;
+
+ mutex_lock (&state->ca_mutex);
+
+ ret = tbs5580_op_rw(d->udev, 0xa6, 0, 0, buf, 2, TBS5580_WRITE_MSG);
+
+ msleep (5);
+
+ buf[1] = 1;
+
+ ret = tbs5580_op_rw(d->udev, 0xa6, 0, 0, buf, 2, TBS5580_WRITE_MSG);
+
+ msleep (1400);
+
+ mutex_unlock (&state->ca_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int tbs5580_poll_slot_status(struct dvb_ca_en50221 *ca,
+ int slot, int open)
+{
+ struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+ struct tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ u8 buf[3];
+
+ if (0 != slot)
+ return -EINVAL;
+
+ mutex_lock(&state->ca_mutex);
+
+ tbs5580_op_rw(d->udev, 0xa8, 0, 0, buf, 3, TBS5580_READ_MSG);
+
+ mutex_unlock(&state->ca_mutex);
+
+ if ((1 == buf[2]) && (1 == buf[1]) && (0xa9 == buf[0])) {
+ return (DVB_CA_EN50221_POLL_CAM_PRESENT |
+ DVB_CA_EN50221_POLL_CAM_READY);
+ } else {
+ return 0;
+ }
+}
+
+static void tbs5580_uninit(struct dvb_usb_device *d)
+{
+ struct tbs5580_state *state;
+
+ if (NULL == d)
+ return;
+
+ state = (struct tbs5580_state *)d->priv;
+ if (NULL == state)
+ return;
+
+ if (NULL == state->ca.data)
+ return;
+
+ /* Error ignored. */
+ tbs5580_set_video_port(&state->ca, /* slot */ 0, /* enable */ 0);
+
+ dvb_ca_en50221_release(&state->ca);
+
+ memset(&state->ca, 0, sizeof(state->ca));
+}
+
+static int tbs5580_init(struct dvb_usb_adapter *a)
+{
+
+ struct dvb_usb_device *d = a->dev;
+ struct tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ int ret;
+
+ state->ca.owner = THIS_MODULE;
+ state->ca.read_attribute_mem = tbs5580_read_attribute_mem;
+ state->ca.write_attribute_mem = tbs5580_write_attribute_mem;
+ state->ca.read_cam_control = tbs5580_read_cam_control;
+ state->ca.write_cam_control = tbs5580_write_cam_control;
+ state->ca.slot_reset = tbs5580_slot_reset;
+ state->ca.slot_shutdown = tbs5580_slot_shutdown;
+ state->ca.slot_ts_enable = tbs5580_slot_ts_enable;
+ state->ca.poll_slot_status = tbs5580_poll_slot_status;
+ state->ca.data = d;
+
+ ret = dvb_ca_en50221_init (&a->dvb_adap, &state->ca,
+ /* flags */ 0, /* n_slots */ 1);
+
+ if (0 != ret) {
+ err ("Cannot initialize CI: Error %d.", ret);
+ memset (&state->ca, 0, sizeof (state->ca));
+ return ret;
+ }
+
+ info ("CI initialized.");
+
+ ret = tbs5580_poll_slot_status(&state->ca, 0, 0);
+ if (0 == ret)
+ tbs5580_set_video_port(&state->ca, /* slot */ 0, /* enable */ 0);
+
+ return 0;
+}
+
+/* I2C */
+static int tbs5580_i2c_transfer(struct i2c_adapter *adap,
+ struct i2c_msg msg[], int num)
+{
+ struct dvb_usb_device *d = i2c_get_adapdata(adap);
+ struct tbs5580_state *state = (struct tbs5580_state *)d->priv;
+ int i = 0;
+ u8 buf6[20];
+ u8 inbuf[20];
+
+ if (!d)
+ return -ENODEV;
+ mutex_lock(&state->ca_mutex);
+ if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+ return -EAGAIN;
+
+ switch (num) {
+ case 2:
+ buf6[0]=msg[1].len;//lenth
+ buf6[1]=msg[0].addr<<1;//demod addr
+ //register
+ buf6[2] = msg[0].buf[0];
+
+ tbs5580_op_rw(d->udev, 0x90, 0, 0, buf6, 3, TBS5580_WRITE_MSG);
+ //msleep(5);
+ tbs5580_op_rw(d->udev, 0x91, 0, 0, inbuf, buf6[0], TBS5580_READ_MSG);
+ memcpy(msg[1].buf, inbuf, msg[1].len);
+ break;
+ case 1:
+ switch (msg[0].addr) {
+ case 0x67:
+ case 0x62:
+ case 0x61:
+ if (msg[0].flags == 0) {
+ buf6[0] = msg[0].len+1;//lenth
+ buf6[1] = msg[0].addr<<1;//addr
+ for(i=0;i<msg[0].len;i++) {
+ buf6[2+i] = msg[0].buf[i];//register
+ }
+ tbs5580_op_rw(d->udev, 0x80, 0, 0, buf6, msg[0].len+2, TBS5580_WRITE_MSG);
+ } else {
+ buf6[0] = msg[0].len;//length
+ buf6[1] = (msg[0].addr<<1) | 0x01;//addr
+ tbs5580_op_rw(d->udev, 0x93, 0, 0,
+ buf6, 2, TBS5580_WRITE_MSG);
+ //msleep(5);
+ tbs5580_op_rw(d->udev, 0x91, 0, 0, inbuf, buf6[0], TBS5580_READ_MSG);
+ memcpy(msg[0].buf, inbuf, msg[0].len);
+ }
+ //msleep(3);
+ break;
+ case (TBS5580_VOLTAGE_CTRL):
+ buf6[0] = 3;
+ buf6[1] = msg[0].buf[0];
+ tbs5580_op_rw(d->udev, 0x8a, 0, 0, buf6, 2, TBS5580_WRITE_MSG);
+ break;
+ }
+
+ break;
+ }
+
+ mutex_unlock(&d->i2c_mutex);
+ mutex_unlock(&state->ca_mutex);
+ return num;
+}
+
+static u32 tbs5580_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm tbs5580_i2c_algo = {
+ .master_xfer = tbs5580_i2c_transfer,
+ .functionality = tbs5580_i2c_func,
+};
+
+/* DOes this only apply to the SAT Tuner? */
+/*
+static int tbs5580_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ static u8 command_13v[1] = {0x00};
+ static u8 command_18v[1] = {0x01};
+ struct i2c_msg msg[] = {
+ {.addr = TBS5580_VOLTAGE_CTRL, .flags = 0,
+ .buf = command_13v, .len = 1},
+ };
+
+ struct dvb_usb_adapter *udev_adap =
+ (struct dvb_usb_adapter *)(fe->dvb->priv);
+ if (voltage == SEC_VOLTAGE_18)
+ msg[0].buf = command_18v;
+
+
+ i2c_transfer(&udev_adap->dev->i2c_adap, msg, 1);
+
+ return 0;
+}
+*/
+
+static int tbs5580_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
+{
+ int i,ret;
+ u8 ibuf[3] = {0, 0,0};
+ u8 eeprom[256], eepromline[16];
+
+ for (i = 0; i < 256; i++) {
+ ibuf[0]=1;//lenth
+ ibuf[1]=0xa0;//eeprom addr
+ ibuf[2]=i;//register
+ ret = tbs5580_op_rw(d->udev, 0x90, 0, 0, ibuf, 3, TBS5580_WRITE_MSG);
+ ret = tbs5580_op_rw(d->udev, 0x91, 0, 0, ibuf, 1, TBS5580_READ_MSG);
+ if (ret < 0) {
+ err("read eeprom failed.");
+ return -1;
+ } else {
+ eepromline[i%16] = ibuf[0];
+ eeprom[i] = ibuf[0];
+ }
+
+ if ((i % 16) == 15) {
+ deb_xfer("%02x: ", i - 15);
+ debug_dump(eepromline, 16, deb_xfer);
+ }
+ }
+ memcpy(mac, eeprom + 16, 6);
+ return 0;
+};
+
+static struct dvb_usb_device_properties tbs5580_properties;
+
+static int tbs5580_frontend_attach(struct dvb_usb_adapter *adap)
+{
+ struct dvb_usb_device *d = adap->dev;
+ struct tbs5580_state *st = d->priv;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client_demod;
+ struct i2c_client *client_tuner;
+ struct i2c_board_info info;
+ struct si2183_config si2183_config;
+ struct si2157_config si2157_config;
+
+ mutex_init(&st->ca_mutex);
+ /* attach frontend */
+ memset(&si2183_config,0,sizeof(si2183_config));
+ si2183_config.i2c_adapter = &adapter;
+ si2183_config.fe = &adap->fe_adap[0].fe;
+ si2183_config.ts_mode = SI2183_TS_PARALLEL;
+ si2183_config.ts_clock_gapped = true;
+ si2183_config.rf_in = 0;
+ si2183_config.RF_switch = NULL;
+ si2183_config.agc_mode = 0x5 ;
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ strscpy(info.type, "si2183", I2C_NAME_SIZE);
+ info.addr = 0x67;
+ info.platform_data = &si2183_config;
+ request_module(info.type);
+ client_demod = i2c_new_client_device(&d->i2c_adap, &info);
+ if (!i2c_client_has_driver(client_demod))
+ return -ENODEV;
+
+ if (!try_module_get(client_demod->dev.driver->owner)) {
+ i2c_unregister_device(client_demod);
+ return -ENODEV;
+ }
+ st->i2c_client_demod = client_demod;
+
+ /* dvb core doesn't support 2 tuners for 1 demod so
+ we split the adapter in 2 frontends */
+
+ // memcpy(adap->fe_adap[1].fe, adap->fe_adap[0].fe, sizeof(struct dvb_frontend));
+
+ /* terrestrial tuner */
+ memset(adap->fe_adap[0].fe->ops.delsys, 0, MAX_DELSYS);
+ adap->fe_adap[0].fe->ops.delsys[0] = SYS_DVBT;
+ adap->fe_adap[0].fe->ops.delsys[1] = SYS_DVBT2;
+ adap->fe_adap[0].fe->ops.delsys[2] = SYS_DVBC_ANNEX_A;
+ adap->fe_adap[0].fe->ops.delsys[3] = SYS_ISDBT;
+ adap->fe_adap[0].fe->ops.delsys[4] = SYS_DVBC_ANNEX_B;
+
+ /* attach ter tuner */
+ memset(&si2157_config, 0, sizeof(si2157_config));
+ si2157_config.fe = adap->fe_adap[0].fe;
+ si2157_config.if_port = 1;
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ strscpy(info.type, "si2157", I2C_NAME_SIZE);
+ info.addr = 0x61;
+ info.platform_data = &si2157_config;
+ request_module(info.type);
+ client_tuner = i2c_new_client_device(adapter, &info);
+ if (!i2c_client_has_driver(client_tuner)) {
+ module_put(client_demod->dev.driver->owner);
+ i2c_unregister_device(client_demod);
+ return -ENODEV;
+ }
+ if (!try_module_get(client_tuner->dev.driver->owner)) {
+ i2c_unregister_device(client_tuner);
+ module_put(client_demod->dev.driver->owner);
+ i2c_unregister_device(client_demod);
+ return -ENODEV;
+ }
+
+ st->i2c_client_tuner = client_tuner;
+
+ /*attach SAT tuner*/
+ /*
+ memset(adap->fe_adap[1].fe->ops.delsys, 0, MAX_DELSYS);
+ adap->fe_adap[1].fe->ops.delsys[0] = SYS_DVBS;
+ adap->fe_adap[1].fe->ops.delsys[1] = SYS_DVBS2;
+ adap->fe_adap[1].fe->ops.delsys[2] = SYS_DSS;
+ adap->fe_adap[1].fe->id = 1;
+
+ if(dvb_attach(av201x_attach, adap->fe_adap[1].fe, &tbs5580_av201x_cfg, adapter) == NULL){
+ return -ENODEV;
+ }else{
+ tbs5580_op_rw(d->udev, 0x8a, 0, 0, (u8){1,0}, 2, TBS5580_WRITE_MSG);
+ adap->fe_adap[1].fe->ops.set_voltage = tbs5580_set_voltage;
+ }*/
+
+ tbs5580_op_rw(d->udev, 0xb7, 0, 0, (u8[]){0,0}, 2, TBS5580_WRITE_MSG);
+ tbs5580_op_rw(d->udev, 0x8a, 0, 0, (u8[]){8,1}, 2, TBS5580_WRITE_MSG);
+
+ tbs5580_init(adap);
+
+ strscpy(adap->fe_adap[0].fe->ops.info.name,d->props.devices[0].name,52);
+ strcat(adap->fe_adap[0].fe->ops.info.name," DVB-T/T2/C/C2/ISDB-T");
+ // strscpy(adap->fe_adap[1].fe->ops.info.name,d->props.devices[0].name,52);
+ // strcat(adap->fe_adap[1].fe->ops.info.name," DVB-S/S2/S2X");
+
+ return 0;
+}
+
+static struct usb_device_id tbs5580_table[] = {
+ {USB_DEVICE(0x734c, 0x5580)},
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, tbs5580_table);
+
+static int tbs5580_load_firmware(struct usb_device *dev,
+ const struct firmware *frmwr)
+{
+ u8 *b, *p;
+ int ret = 0, i;
+ u8 reset;
+ const struct firmware *fw;
+ switch (dev->descriptor.idProduct) {
+ case 0x5580:
+ ret = request_firmware(&fw, tbs5580_properties.firmware, &dev->dev);
+ if (ret != 0) {
+ err("did not find the firmware file. (%s) "
+ "Please see linux/Documentation/dvb/ for more details "
+ "on firmware-problems.", tbs5580_properties.firmware);
+ return ret;
+ }
+ break;
+ default:
+ fw = frmwr;
+ break;
+ }
+ info("start downloading TBS5580 firmware");
+ p = kmalloc(fw->size, GFP_KERNEL);
+ reset = 1;
+ /*stop the CPU*/
+ tbs5580_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1, TBS5580_WRITE_MSG);
+ tbs5580_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1, TBS5580_WRITE_MSG);
+
+ if (p != NULL) {
+ memcpy(p, fw->data, fw->size);
+ for (i = 0; i < fw->size; i += 0x40) {
+ b = (u8 *) p + i;
+ if (tbs5580_op_rw(dev, 0xa0, i, 0, b , 0x40, TBS5580_WRITE_MSG) != 0x40) {
+ err("error while transferring firmware");
+ ret = -EINVAL;
+ break;
+ }
+ }
+ /* restart the CPU */
+ reset = 0;
+ if (ret || tbs5580_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1, TBS5580_WRITE_MSG) != 1) {
+ err("could not restart the USB controller CPU.");
+ ret = -EINVAL;
+ }
+ if (ret || tbs5580_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1, TBS5580_WRITE_MSG) != 1) {
+ err("could not restart the USB controller CPU.");
+ ret = -EINVAL;
+ }
+
+ msleep(100);
+ kfree(p);
+ }
+ return ret;
+}
+
+static struct dvb_usb_device_properties tbs5580_properties = {
+ .caps = DVB_USB_IS_AN_I2C_ADAPTER,
+ .usb_ctrl = DEVICE_SPECIFIC,
+ .firmware = "dvb-usb-id5580.fw",
+ .size_of_priv = sizeof(struct tbs5580_state),
+ .no_reconnect = 1,
+
+ .i2c_algo = &tbs5580_i2c_algo,
+
+ .generic_bulk_ctrl_endpoint = 0x81,
+ /* parameter for the MPEG2-data transfer */
+ .num_adapters = 1,
+ .download_firmware = tbs5580_load_firmware,
+ .read_mac_address = tbs5580_read_mac_address,
+ .adapter = {{
+ .num_frontends = 1,
+ .fe = {{
+ .frontend_attach = tbs5580_frontend_attach,
+ .streaming_ctrl = NULL,
+ .tuner_attach = NULL,
+ .stream = {
+ .type = USB_BULK,
+ .count = 8,
+ .endpoint = 0x82,
+ .u = {
+ .bulk = {
+ .buffersize = 4096,
+ }
+ }
+ },
+ }},
+ }},
+
+ .num_device_descs = 1,
+ .devices = {
+ {"TBS 5580 CI USB2.0",
+ {&tbs5580_table[0], NULL},
+ {NULL},
+ }
+ }
+};
+
+static int tbs5580_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ if (0 == dvb_usb_device_init(intf, &tbs5580_properties,
+ THIS_MODULE, NULL, adapter_nr)) {
+ return 0;
+ }
+ return -ENODEV;
+}
+
+static void tbs5580_disconnect(struct usb_interface *intf)
+{
+
+ struct dvb_usb_device *d = usb_get_intfdata(intf);
+#if 0
+ struct tbs5580_state *st = d->priv;
+ struct i2c_client *client;
+
+ /* remove I2C client for tuner */
+ client = st->i2c_client_tuner;
+ if (client) {
+ module_put(client->dev.driver->owner);
+ i2c_unregister_device(client);
+ }
+
+ /* remove I2C client for demodulator */
+ client = st->i2c_client_demod;
+ if (client) {
+ module_put(client->dev.driver->owner);
+ i2c_unregister_device(client);
+ }
+#endif
+ tbs5580_uninit(d);
+ dvb_usb_device_exit(intf);
+}
+
+static struct usb_driver tbs5580_driver = {
+ .name = "tbs5580",
+ .probe = tbs5580_probe,
+ .disconnect = tbs5580_disconnect,
+ .id_table = tbs5580_table,
+};
+
+static int __init tbs5580_module_init(void)
+{
+ int ret = usb_register(&tbs5580_driver);
+ if (ret)
+ err("usb_register failed. Error number %d", ret);
+
+ return ret;
+}
+
+static void __exit tbs5580_module_exit(void)
+{
+ usb_deregister(&tbs5580_driver);
+}
+
+module_init(tbs5580_module_init);
+module_exit(tbs5580_module_exit);
+
+MODULE_AUTHOR("Davin zhang <smiledavin@gmail.com>");
+MODULE_DESCRIPTION("TurboSight TBS 5580 driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/tbs5580.h b/drivers/media/usb/dvb-usb/tbs5580.h
new file mode 100644
index 000000000000..e83b34c42941
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/tbs5580.h
@@ -0,0 +1,8 @@
+#ifndef _TBS5580_H_
+#define _TBS5580_H_
+
+#define DVB_USB_LOG_PREFIX "tbs5580"
+#include "dvb-usb.h"
+
+#define deb_xfer(args...) dprintk(dvb_usb_tbs5580_debug, 0x02, args)
+#endif
#!/lib/init/init-d-script
### BEGIN INIT INFO
# Provides: tailon
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Daemon for online web viewing
# Description: Daemon for online web viewing
### END INIT INFO
DAEMON=/usr/local/bin/muzap.py
PIDFILE=/var/run/muzap.pid
START_ARGS="--background -m -c _mumudvb"
#!/usr/bin/env python3
import os, sys
import re
import html
import threading
import subprocess
from difflib import get_close_matches
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import quote, unquote
base_port = 4300
channel_list = []
channel_dict = dict()
tmp_configfile = None
tmp_configfile_fd = None
current_frequency = None
current_channels = dict()
mumudvb = None
def stop_mumudvb():
global current_frequency
global mumudvb
if mumudvb:
print("stopping mumudvb")
mumudvb.terminate()
mumudvb.wait()
mumudvb = None
current_frequency = None
re_autoconf_channel_count = re.compile(b'^Info: Autoconf: Diffusion ([0-9]+) channels?$')
re_autoconf_channel_info = re.compile(b'^Info: Autoconf: Channel number : *([0-9]+), name : "(.*)" service id ([0-9]+) *$')
def start_mumudvb(program):
global current_frequency
global mumudvb
global current_channels
program.update_config()
print("starting mumudvb")
mumudvb = subprocess.Popen(
['mumudvb','-d','-c','/proc/self/fd/'+str(tmp_configfile_fd)],
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
pass_fds=(tmp_configfile_fd,)
)
autoconfig_done = False
channel_list = dict()
channel_count = 0
for line in mumudvb.stdout:
sys.stdout.buffer.write(b'mumudvb> '+line)
sys.stdout.buffer.flush()
if autoconfig_done:
res = re_autoconf_channel_info.match(line)
else:
res = re_autoconf_channel_count.match(line)
if res:
if not autoconfig_done:
autoconfig_done = True
channel_count = int(res[1])
else:
channel_list[res[2]] = (int(res[1]),int(res[3]))
if autoconfig_done and channel_count == len(channel_list):
break
mumudvb.stdout.close()
current_channels = channel_list
current_frequency = program.freq
print("started")
class ChannelListEntry:
def __init__(self, p):
(
self.program,
self.freq,
self.inv,
self.srate,
self.dec,
self.modulation
) = p[0:6]
self.freq = int(self.freq)
self.srate = int(self.srate)
self.modulation = self.modulation.replace('_','')
channel_list.append(self)
channel_dict[self.program] = self
def update_config(c):
tmp_configfile.truncate(0)
tmp_configfile.write(f'''
autoconfiguration=full
autoconf_unicast_start_port={base_port}
freq={c.freq//1000}
srate={c.srate//1000}
modulation={c.modulation}
delivery_system=DVBC_ANNEX_AC
unicast=1
multicast_ipv4=0
multicast_ipv6=0
''')
tmp_configfile.flush()
os.lseek(tmp_configfile_fd, os.SEEK_SET, 0)
def init():
global tmp_configfile
global tmp_configfile_fd
with open("/etc/channels.conf") as reader:
for line in reader:
p = ChannelListEntry([x.strip() for x in line.split(':')])
tmp_configfile_fd = os.open('/tmp/', os.O_RDWR|os.O_TMPFILE, 0o400)
tmp_configfile = os.fdopen(tmp_configfile_fd, 'w+')
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
global current_frequency
host = self.headers['Host']
if '/' in host:
self.send_response(400)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(b'<h1>404 Not Found</h1>')
spath = self.path.split('/')[1:]
if self.command == 'GET':
if self.path == '/':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b'''
<h1>TV Server</h1>
<ul>
<li><a href="/playlist.m3u">playlist.m3u</a></li>
<li><a href="/vlc/">vlc</a></li>
</ul>
''')
return
if self.path == '/playlist.m3u':
self.send_response(200)
self.send_header('Content-Type', 'audio/x-mpegurl')
self.end_headers()
self.wfile.write(b'#EXTM3U')
for entry in channel_list:
message = '#EXTINF:0,' + entry.program + '\n'
message += '/program/' + quote(entry.program) + '\n'
self.wfile.write(message.encode('utf-8'))
return
if self.path == '/vlc/':
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=UTF-8')
self.end_headers()
self.wfile.write(b'<ul>\n')
for entry in channel_list:
message = ' <li><a href="vlc://'+host+'/program/' + quote(entry.program) + '">' + html.escape(entry.program) + '</a></li>\n'
self.wfile.write(message.encode('utf-8'))
self.wfile.write(b'</ul>\n')
return
if len(spath) == 2 and spath[0] == 'program':
program = unquote(spath[1])
if program in channel_dict:
program = channel_dict[program]
if current_frequency != program.freq:
stop_mumudvb()
start_mumudvb(program)
match = get_close_matches(program.program.encode('UTF-8'), current_channels.keys(), 1)
if len(match):
match = match[0]
index, spid = current_channels[match]
location = f'http://{host.split(":")[0]}:{index+base_port}'
self.send_response(302)
self.send_header('Content-Type', 'video/mpeg')
self.send_header('Location', location)
self.end_headers()
return
self.send_response(404)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(b'<h1>404 Not Found</h1>')
if __name__ == '__main__':
init()
server = HTTPServer(('', 8080), Handler)
server.serve_forever()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment