media: dvb-frontends: demod lgdt3306a: add i2c gate logic and i2c device handling...
Henning Garbers [Sat, 2 Jul 2016 20:35:52 +0000 (22:35 +0200)]
Bug: 30095176
Bug 1736911

Change-Id: Iae614a6e03babd010e94b47da905dca4b3b597d5
Signed-off-by: Sungtak Lee <taklee@google.com>
Signed-off-by: Jean Huang <jeanh@nvidia.com>
Signed-off-by: Patrick Horng <phorng@nvidia.com>
Reviewed-on: http://git-master/r/1284466
(cherry picked from commit edbe9ee471da3e24335c555afdc79ce05321d013)
Reviewed-on: http://git-master/r/1311294
GVS: Gerrit_Virtual_Submit
Reviewed-by: Vinayak Pane <vpane@nvidia.com>
Reviewed-on: http://git-master/r/1456508
Reviewed-by: Manish Tuteja <mtuteja@nvidia.com>

drivers/media/dvb-frontends/lgdt3306a.c
drivers/media/dvb-frontends/lgdt3306a.h

index 73ee1cc..9c7e412 100644 (file)
@@ -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");
index ed8aa3e..b54e7e5 100644 (file)
 #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)