diff options
author | Henning Garbers <henning.garbers@mail.de> | 2016-07-02 22:35:52 +0200 |
---|---|---|
committer | Sungtak Lee <taklee@google.com> | 2016-11-29 11:08:19 -0800 |
commit | 03f40945dc8c0bab71d80d785a63ed6846142e78 (patch) | |
tree | c2fd46d40c5fc7d8de6bf5a6bf0f2a9f8871ad75 | |
parent | 256f97fde6af4ae2d9be55af0e4283d3e10b6709 (diff) | |
download | x86_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.c | 304 | ||||
-rw-r--r-- | drivers/media/dvb-frontends/lgdt3306a.h | 17 |
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) |