summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenning Garbers <henning.garbers@mail.de>2016-07-02 22:35:52 +0200
committerSungtak Lee <taklee@google.com>2016-11-29 11:08:19 -0800
commit03f40945dc8c0bab71d80d785a63ed6846142e78 (patch)
treec2fd46d40c5fc7d8de6bf5a6bf0f2a9f8871ad75
parent256f97fde6af4ae2d9be55af0e4283d3e10b6709 (diff)
downloadx86_64-03f40945dc8c0bab71d80d785a63ed6846142e78.tar.gz
media: dvb-frontends: demod lgdt3306a: add i2c gate logic and i2c device handling calls for WinTV-DualHD
Bug: 30095176 Change-Id: Iae614a6e03babd010e94b47da905dca4b3b597d5 Signed-off-by: Sungtak Lee <taklee@google.com>
-rw-r--r--drivers/media/dvb-frontends/lgdt3306a.c304
-rw-r--r--drivers/media/dvb-frontends/lgdt3306a.h17
2 files changed, 312 insertions, 9 deletions
diff --git a/drivers/media/dvb-frontends/lgdt3306a.c b/drivers/media/dvb-frontends/lgdt3306a.c
index 73ee1cc37a8d..9c7e412b793a 100644
--- a/drivers/media/dvb-frontends/lgdt3306a.c
+++ b/drivers/media/dvb-frontends/lgdt3306a.c
@@ -16,7 +16,6 @@
* GNU General Public License for more details.
*/
-#undef pr_fmt
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/div64.h>
@@ -59,11 +58,12 @@ MODULE_PARM_DESC(debug, "set debug level (info=1, reg=2 (or-able))");
struct lgdt3306a_state {
struct i2c_adapter *i2c_adap;
+ struct lgdt3306a_config config;
const struct lgdt3306a_config *cfg;
-
+ struct i2c_client *client;
struct dvb_frontend frontend;
- fe_modulation_t current_modulation;
+ enum fe_modulation current_modulation;
u32 current_frequency;
u32 snr;
};
@@ -118,6 +118,7 @@ static void lgdt3306a_DumpRegs(struct lgdt3306a_state *state);
static int lgdt3306a_write_reg(struct lgdt3306a_state *state, u16 reg, u8 val)
{
int ret;
+ struct i2c_adapter *i2c_adapter;
u8 buf[] = { reg >> 8, reg & 0xff, val };
struct i2c_msg msg = {
.addr = state->cfg->i2c_addr, .flags = 0,
@@ -126,8 +127,11 @@ static int lgdt3306a_write_reg(struct lgdt3306a_state *state, u16 reg, u8 val)
dbg_reg("reg: 0x%04x, val: 0x%02x\n", reg, val);
- ret = i2c_transfer(state->i2c_adap, &msg, 1);
-
+ if (state->cfg->has_tuner_i2c_adapter)
+ i2c_adapter = state->client->adapter;
+ else
+ i2c_adapter = state->i2c_adap;
+ ret = i2c_transfer(i2c_adapter, &msg, 1);
if (ret != 1) {
pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
msg.buf[0], msg.buf[1], msg.buf[2], ret);
@@ -142,6 +146,7 @@ static int lgdt3306a_write_reg(struct lgdt3306a_state *state, u16 reg, u8 val)
static int lgdt3306a_read_reg(struct lgdt3306a_state *state, u16 reg, u8 *val)
{
int ret;
+ struct i2c_adapter *i2c_adapter;
u8 reg_buf[] = { reg >> 8, reg & 0xff };
struct i2c_msg msg[] = {
{ .addr = state->cfg->i2c_addr,
@@ -150,8 +155,12 @@ static int lgdt3306a_read_reg(struct lgdt3306a_state *state, u16 reg, u8 *val)
.flags = I2C_M_RD, .buf = val, .len = 1 },
};
- ret = i2c_transfer(state->i2c_adap, msg, 2);
+ if (state->cfg->has_tuner_i2c_adapter)
+ i2c_adapter = state->client->adapter;
+ else
+ i2c_adapter = state->i2c_adap;
+ ret = i2c_transfer(i2c_adapter, msg, 2);
if (ret != 2) {
pr_err("error (addr %02x reg %04x error (ret == %i)\n",
state->cfg->i2c_addr, reg, ret);
@@ -622,6 +631,9 @@ static int lgdt3306a_set_modulation(struct lgdt3306a_state *state,
case QAM_256:
ret = lgdt3306a_set_qam(state, QAM_256);
break;
+ case QAM_AUTO:
+ ret = lgdt3306a_set_qam(state, QAM_64);
+ break;
default:
return -EINVAL;
}
@@ -647,6 +659,7 @@ static int lgdt3306a_agc_setup(struct lgdt3306a_state *state,
break;
case QAM_64:
case QAM_256:
+ case QAM_AUTO:
break;
default:
return -EINVAL;
@@ -701,6 +714,7 @@ static int lgdt3306a_spectral_inversion(struct lgdt3306a_state *state,
break;
case QAM_64:
case QAM_256:
+ case QAM_AUTO:
/* Auto ok for QAM */
ret = lgdt3306a_set_inversion_auto(state, 1);
break;
@@ -724,6 +738,7 @@ static int lgdt3306a_set_if(struct lgdt3306a_state *state,
break;
case QAM_64:
case QAM_256:
+ case QAM_AUTO:
if_freq_khz = state->cfg->qam_if_khz;
break;
default:
@@ -1559,7 +1574,8 @@ lgdt3306a_qam_lock_poll(struct lgdt3306a_state *state)
return LG3306_UNLOCK;
}
-static int lgdt3306a_read_status(struct dvb_frontend *fe, fe_status_t *status)
+static int lgdt3306a_read_status(struct dvb_frontend *fe,
+ enum fe_status *status)
{
struct lgdt3306a_state *state = fe->demodulator_priv;
u16 strength = 0;
@@ -1581,6 +1597,7 @@ static int lgdt3306a_read_status(struct dvb_frontend *fe, fe_status_t *status)
switch (state->current_modulation) {
case QAM_256:
case QAM_64:
+ case QAM_AUTO:
if (lgdt3306a_qam_lock_poll(state) == LG3306_LOCK) {
*status |= FE_HAS_VITERBI;
*status |= FE_HAS_SYNC;
@@ -1641,6 +1658,9 @@ static int lgdt3306a_read_signal_strength(struct dvb_frontend *fe,
case QAM_256:
ref_snr = 2800; /* 28dB */
break;
+ case QAM_AUTO:
+ ref_snr = 2200; /* 22dB */
+ break;
default:
return -EINVAL;
}
@@ -1706,7 +1726,7 @@ static int lgdt3306a_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
static int lgdt3306a_tune(struct dvb_frontend *fe, bool re_tune,
unsigned int mode_flags, unsigned int *delay,
- fe_status_t *status)
+ enum fe_status *status)
{
int ret = 0;
struct lgdt3306a_state *state = fe->demodulator_priv;
@@ -1736,7 +1756,7 @@ static int lgdt3306a_get_tune_settings(struct dvb_frontend *fe,
static int lgdt3306a_search(struct dvb_frontend *fe)
{
- fe_status_t status = 0;
+ enum fe_status status = 0;
int i = 0, ret;
unsigned long timeout;
@@ -2111,7 +2131,144 @@ static void lgdt3306a_DumpRegs(struct lgdt3306a_state *state)
}
#endif /* DBG_DUMP */
+/*
+ * I2C gate logic
+ * We must use unlocked I2C I/O because I2C adapter lock is already taken
+ * by the caller (usually tuner driver).
+ */
+static int lgdt3306a_select(struct i2c_adapter *adap, void *mux_priv, u32 chan)
+{
+ struct lgdt3306a_state *state = mux_priv;
+ struct i2c_client *client = state->client;
+
+ int ret;
+ u8 val;
+ u8 buf[3];
+
+ struct i2c_msg read_msg_1 = {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = "\x00\x02",
+ .len = 2,
+ };
+ struct i2c_msg read_msg_2 = {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .buf = &val,
+ .len = 1,
+ };
+
+ struct i2c_msg write_msg = {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = buf,
+ .len = 3,
+ };
+
+ ret = __i2c_transfer(client->adapter, &read_msg_1, 1);
+ if (ret != 1) {
+ pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+ client->addr, ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EREMOTEIO;
+ }
+
+ ret = __i2c_transfer(client->adapter, &read_msg_2, 1);
+ if (ret != 1) {
+ pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+ client->addr, ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EREMOTEIO;
+ }
+
+ buf[0] = 0x00;
+ buf[1] = 0x02;
+ val &= 0x7F;
+ val |= LG3306_TUNERI2C_ON;
+ buf[2] = val;
+ ret = __i2c_transfer(client->adapter, &write_msg, 1);
+ if (ret != 1) {
+ pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
+ write_msg.buf[0], write_msg.buf[1], write_msg.buf[2],
+ ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EREMOTEIO;
+ }
+ return 0;
+}
+
+static int lgdt3306a_deselect(struct i2c_adapter *adap, void *mux_priv,
+ u32 chan)
+{
+ struct lgdt3306a_state *state = mux_priv;
+ struct i2c_client *client = state->client;
+ int ret;
+ u8 val;
+ u8 buf[3];
+
+ struct i2c_msg read_msg_1 = {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = "\x00\x02",
+ .len = 2,
+ };
+ struct i2c_msg read_msg_2 = {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .buf = &val,
+ .len = 1,
+ };
+
+ struct i2c_msg write_msg = {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 3,
+ .buf = buf,
+ };
+
+ ret = __i2c_transfer(client->adapter, &read_msg_1, 1);
+ if (ret != 1) {
+ pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+ client->addr, ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EREMOTEIO;
+ }
+
+ ret = __i2c_transfer(client->adapter, &read_msg_2, 1);
+ if (ret != 1) {
+ pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+ client->addr, ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EREMOTEIO;
+ }
+ buf[0] = 0x00;
+ buf[1] = 0x02;
+ val &= 0x7F;
+ val |= LG3306_TUNERI2C_OFF;
+ buf[2] = val;
+ ret = __i2c_transfer(client->adapter, &write_msg, 1);
+ if (ret != 1) {
+ pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
+ write_msg.buf[0], write_msg.buf[1], write_msg.buf[2],
+ ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EREMOTEIO;
+ }
+ return 0;
+}
static struct dvb_frontend_ops lgdt3306a_ops = {
.delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B },
@@ -2141,6 +2298,135 @@ static struct dvb_frontend_ops lgdt3306a_ops = {
.search = lgdt3306a_search,
};
+static int lgdt3306a_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lgdt3306a_config *config = client->dev.platform_data;
+ struct lgdt3306a_state *state;
+ u8 val;
+ int ret;
+
+ dev_dbg(&client->dev, "\n");
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state) {
+ ret = -ENOMEM;
+ dev_err(&client->dev, "kzalloc() failed\n");
+ goto err;
+ }
+
+ state->client = client;
+ state->config.i2c_addr = config->i2c_addr;
+ state->config.qam_if_khz = config->qam_if_khz;
+ state->config.vsb_if_khz = config->vsb_if_khz;
+ state->config.deny_i2c_rptr = config->deny_i2c_rptr;
+ state->config.spectral_inversion = config->spectral_inversion;
+ state->config.mpeg_mode = config->mpeg_mode;
+ state->config.tpclk_edge = config->tpclk_edge;
+ state->config.tpvalid_polarity = config->tpvalid_polarity;
+ state->config.xtalMHz = config->xtalMHz;
+ state->config.has_tuner_i2c_adapter = config->has_tuner_i2c_adapter;
+ state->cfg = &state->config;
+
+ /* create mux i2c adapter for tuner */
+ state->i2c_adap = i2c_add_mux_adapter(client->adapter, &client->dev,
+ state, 0, 0, 0, lgdt3306a_select, lgdt3306a_deselect);
+ if (state->i2c_adap == NULL) {
+ ret = -ENODEV;
+ goto err_kfree;
+ }
+
+ /* create dvb_frontend */
+ memcpy(&state->frontend.ops, &lgdt3306a_ops,
+ sizeof(state->frontend.ops));
+ state->frontend.demodulator_priv = state;
+ *config->i2c_adapter = state->i2c_adap;
+ *config->fe = &state->frontend;
+
+ /* verify that we're talking to a lg3306a */
+ /* FGR - NOTE - there is no obvious ChipId to check; we check
+ * some "known" bits after reset, but it's still just a guess */
+ ret = lgdt3306a_read_reg(state, 0x0000, &val);
+ if (lg_chkerr(ret))
+ goto err_kfree;
+ if ((val & 0x74) != 0x74) {
+ pr_warn("expected 0x74, got 0x%x\n", (val & 0x74));
+#if 0
+ /* FIXME - re-enable when we know this is right */
+ goto err_kfree;
+#endif
+ }
+ ret = lgdt3306a_read_reg(state, 0x0001, &val);
+ if (lg_chkerr(ret))
+ goto err_kfree;
+ if ((val & 0xf6) != 0xc6) {
+ pr_warn("expected 0xc6, got 0x%x\n", (val & 0xf6));
+#if 0
+ /* FIXME - re-enable when we know this is right */
+ goto err_kfree;
+#endif
+ }
+ ret = lgdt3306a_read_reg(state, 0x0002, &val);
+ if (lg_chkerr(ret))
+ goto err_kfree;
+ if ((val & 0x73) != 0x03) {
+ pr_warn("expected 0x03, got 0x%x\n", (val & 0x73));
+#if 0
+ /* FIXME - re-enable when we know this is right */
+ goto err_kfree;
+#endif
+ }
+
+ state->current_frequency = -1;
+ state->current_modulation = -1;
+
+ lgdt3306a_sleep(state);
+
+ i2c_set_clientdata(client, state);
+
+ dev_info(&client->dev, "LG Electronics LGDT3306A successfully attached\n");
+ return 0;
+err_kfree:
+ kfree(state);
+err:
+ dev_dbg(&client->dev, "failed=%d\n", ret);
+ return ret;
+}
+
+static int lgdt3306a_remove(struct i2c_client *client)
+{
+ struct lgdt3306a_state *state = i2c_get_clientdata(client);
+
+ dev_dbg(&client->dev, "\n");
+
+ i2c_del_mux_adapter(state->i2c_adap);
+
+ state->frontend.ops.release = NULL;
+ state->frontend.demodulator_priv = NULL;
+
+ kfree(state);
+
+ return 0;
+}
+
+static const struct i2c_device_id lgdt3306a_id_table[] = {
+ {"lgdt3306a", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, lgdt3306a_id_table);
+
+static struct i2c_driver lgdt3306a_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "lgdt3306a",
+ },
+ .probe = lgdt3306a_probe,
+ .remove = lgdt3306a_remove,
+ .id_table = lgdt3306a_id_table,
+};
+
+module_i2c_driver(lgdt3306a_driver);
+
MODULE_DESCRIPTION("LG Electronics LGDT3306A ATSC/QAM-B Demodulator Driver");
MODULE_AUTHOR("Fred Richter <frichter@hauppauge.com>");
MODULE_LICENSE("GPL");
diff --git a/drivers/media/dvb-frontends/lgdt3306a.h b/drivers/media/dvb-frontends/lgdt3306a.h
index ed8aa3e7c950..b54e7e5334c6 100644
--- a/drivers/media/dvb-frontends/lgdt3306a.h
+++ b/drivers/media/dvb-frontends/lgdt3306a.h
@@ -19,8 +19,11 @@
#define _LGDT3306A_H_
#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
#include "dvb_frontend.h"
+#define LG3306_TUNERI2C_ON 0x00
+#define LG3306_TUNERI2C_OFF 0x80
enum lgdt3306a_mpeg_mode {
LGDT3306A_MPEG_PARALLEL = 0,
@@ -40,6 +43,18 @@ enum lgdt3306a_tp_valid_polarity {
struct lgdt3306a_config {
u8 i2c_addr;
+ /*
+ * frontend
+ * returned by driver
+ */
+ struct dvb_frontend **fe;
+
+ /*
+ * tuner I2C adapter
+ * returned by driver
+ */
+ struct i2c_adapter **i2c_adapter;
+
/* user defined IF frequency in KHz */
u16 qam_if_khz;
u16 vsb_if_khz;
@@ -56,6 +71,8 @@ struct lgdt3306a_config {
/* demod clock freq in MHz; 24 or 25 supported */
int xtalMHz;
+
+ int has_tuner_i2c_adapter;
};
#if IS_ENABLED(CONFIG_DVB_LGDT3306A)