Created
September 10, 2024 19:10
-
-
Save Daniel-Abrecht/0c0c91ce6e7486af59af47bc0c793aae to your computer and use it in GitHub Desktop.
TV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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