| From b71e18093e2e7f240797875c50c49552722f8825 Mon Sep 17 00:00:00 2001 |
| From: Jarod Wilson <jarod@redhat.com> |
| Date: Mon, 15 Feb 2010 17:13:25 -0500 |
| Subject: [PATCH 1/2] dvb: add lgdt3304 support to lgdt3305 driver |
| |
| There's a currently-unused lgdt3304 demod driver, which leaves a lot to |
| be desired as far as functionality. The 3304 is unsurprisingly quite |
| similar to the 3305, and empirical testing yeilds far better results |
| and more complete functionality by merging 3304 support into the 3305 |
| driver. (For example, the current lgdt3304 driver lacks support for |
| signal strength, snr, ucblocks, etc., which we get w/the lgdt3305). |
| |
| For the moment, not dropping the lgdt3304 driver, and its still up to |
| a given device's config setup to choose which demod driver to use, but |
| I'd suggest dropping the 3304 driver entirely. |
| |
| As a follow-up to this patch, I've got another patch that adds support |
| for the KWorld PlusTV 340U (ATSC) em2870-based tuner stick, driving |
| its lgdt3304 demod via this lgdt3305 driver, which is what I used to |
| successfully test this patch with both VSB_8 and QAM_256 signals. |
| |
| A few pieces are still a touch crude, but I think its a solid start, |
| as well as much cleaner and more feature-complete than the existing |
| lgdt3304 driver. |
| |
| Signed-off-by: Jarod Wilson <jarod@redhat.com> |
| |
| drivers/media/dvb/frontends/lgdt3305.c | 206 ++++++++++++++++++++++++++++++-- |
| drivers/media/dvb/frontends/lgdt3305.h | 6 + |
| 2 files changed, 203 insertions(+), 9 deletions(-) |
| |
| diff --git a/drivers/media/dvb/frontends/lgdt3305.c b/drivers/media/dvb/frontends/lgdt3305.c |
| index fde8c59..40695e6 100644 |
| |
| |
| @@ -1,5 +1,5 @@ |
| /* |
| - * Support for LGDT3305 - VSB/QAM |
| + * Support for LG Electronics LGDT3304 and LGDT3305 - VSB/QAM |
| * |
| * Copyright (C) 2008, 2009 Michael Krufky <mkrufky@linuxtv.org> |
| * |
| @@ -357,7 +357,10 @@ static int lgdt3305_rfagc_loop(struct lgdt3305_state *state, |
| case QAM_256: |
| agcdelay = 0x046b; |
| rfbw = 0x8889; |
| - ifbw = 0x8888; |
| + if (state->cfg->demod_chip == LGDT3305) |
| + ifbw = 0x8888; |
| + else |
| + ifbw = 0x6666; |
| break; |
| default: |
| return -EINVAL; |
| @@ -409,8 +412,18 @@ static int lgdt3305_agc_setup(struct lgdt3305_state *state, |
| lg_dbg("lockdten = %d, acqen = %d\n", lockdten, acqen); |
| |
| /* control agc function */ |
| - lgdt3305_write_reg(state, LGDT3305_AGC_CTRL_4, 0xe1 | lockdten << 1); |
| - lgdt3305_set_reg_bit(state, LGDT3305_AGC_CTRL_1, 2, acqen); |
| + switch (state->cfg->demod_chip) { |
| + case LGDT3304: |
| + lgdt3305_write_reg(state, 0x0314, 0xe1 | lockdten << 1); |
| + lgdt3305_set_reg_bit(state, 0x030e, 2, acqen); |
| + break; |
| + case LGDT3305: |
| + lgdt3305_write_reg(state, LGDT3305_AGC_CTRL_4, 0xe1 | lockdten << 1); |
| + lgdt3305_set_reg_bit(state, LGDT3305_AGC_CTRL_1, 2, acqen); |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| |
| return lgdt3305_rfagc_loop(state, param); |
| } |
| @@ -543,6 +556,11 @@ static int lgdt3305_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) |
| enable ? 0 : 1); |
| } |
| |
| +static int lgdt3304_sleep(struct dvb_frontend *fe) |
| +{ |
| + return 0; |
| +} |
| + |
| static int lgdt3305_sleep(struct dvb_frontend *fe) |
| { |
| struct lgdt3305_state *state = fe->demodulator_priv; |
| @@ -571,6 +589,55 @@ static int lgdt3305_sleep(struct dvb_frontend *fe) |
| return 0; |
| } |
| |
| +static int lgdt3304_init(struct dvb_frontend *fe) |
| +{ |
| + struct lgdt3305_state *state = fe->demodulator_priv; |
| + int ret; |
| + |
| + static struct lgdt3305_reg lgdt3304_init_data[] = { |
| + { .reg = LGDT3305_GEN_CTRL_1, .val = 0x03, }, |
| + { .reg = 0x000d, .val = 0x02, }, |
| + { .reg = 0x000e, .val = 0x02, }, |
| + { .reg = LGDT3305_DGTL_AGC_REF_1, .val = 0x32, }, |
| + { .reg = LGDT3305_DGTL_AGC_REF_2, .val = 0xc4, }, |
| + { .reg = LGDT3305_CR_CTR_FREQ_1, .val = 0x00, }, |
| + { .reg = LGDT3305_CR_CTR_FREQ_2, .val = 0x00, }, |
| + { .reg = LGDT3305_CR_CTR_FREQ_3, .val = 0x00, }, |
| + { .reg = LGDT3305_CR_CTR_FREQ_4, .val = 0x00, }, |
| + { .reg = LGDT3305_CR_CTRL_7, .val = 0xf9, }, |
| + { .reg = 0x0112, .val = 0x17, }, |
| + { .reg = 0x0113, .val = 0x15, }, |
| + { .reg = 0x0114, .val = 0x18, }, |
| + { .reg = 0x0115, .val = 0xff, }, |
| + { .reg = 0x0116, .val = 0x3c, }, |
| + { .reg = 0x0214, .val = 0x67, }, |
| + { .reg = 0x0424, .val = 0x8d, }, |
| + { .reg = 0x0427, .val = 0x12, }, |
| + { .reg = 0x0428, .val = 0x4f, }, |
| + { .reg = LGDT3305_IFBW_1, .val = 0x80, }, |
| + { .reg = LGDT3305_IFBW_2, .val = 0x00, }, |
| + { .reg = 0x030a, .val = 0x08, }, |
| + { .reg = 0x030b, .val = 0x9b, }, |
| + { .reg = 0x030d, .val = 0x00, }, |
| + { .reg = 0x030e, .val = 0x1c, }, |
| + { .reg = 0x0314, .val = 0xe1, }, |
| + { .reg = 0x000d, .val = 0x82, }, |
| + { .reg = LGDT3305_TP_CTRL_1, .val = 0x5b, }, |
| + { .reg = LGDT3305_TP_CTRL_1, .val = 0x5b, }, |
| + }; |
| + |
| + lg_dbg("\n"); |
| + |
| + ret = lgdt3305_write_regs(state, lgdt3304_init_data, |
| + ARRAY_SIZE(lgdt3304_init_data)); |
| + if (lg_fail(ret)) |
| + goto fail; |
| + |
| + ret = lgdt3305_soft_reset(state); |
| +fail: |
| + return ret; |
| +} |
| + |
| static int lgdt3305_init(struct dvb_frontend *fe) |
| { |
| struct lgdt3305_state *state = fe->demodulator_priv; |
| @@ -639,6 +706,88 @@ fail: |
| return ret; |
| } |
| |
| +static int lgdt3304_set_parameters(struct dvb_frontend *fe, |
| + struct dvb_frontend_parameters *param) |
| +{ |
| + struct lgdt3305_state *state = fe->demodulator_priv; |
| + int ret; |
| + |
| + lg_dbg("(%d, %d)\n", param->frequency, param->u.vsb.modulation); |
| + |
| + if (fe->ops.tuner_ops.set_params) { |
| + ret = fe->ops.tuner_ops.set_params(fe, param); |
| + if (fe->ops.i2c_gate_ctrl) |
| + fe->ops.i2c_gate_ctrl(fe, 0); |
| + if (lg_fail(ret)) |
| + goto fail; |
| + state->current_frequency = param->frequency; |
| + } |
| + |
| + ret = lgdt3305_set_modulation(state, param); |
| + if (lg_fail(ret)) |
| + goto fail; |
| + |
| + ret = lgdt3305_passband_digital_agc(state, param); |
| + if (lg_fail(ret)) |
| + goto fail; |
| + |
| + ret = lgdt3305_agc_setup(state, param); |
| + if (lg_fail(ret)) |
| + goto fail; |
| + |
| + /* reg 0x030d is 3304-only... seen in vsb and qam usbsnoops... */ |
| + switch (param->u.vsb.modulation) { |
| + case VSB_8: |
| + lgdt3305_write_reg(state, 0x030d, 0x00); |
| +#if 1 |
| + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_1, 0x4f); |
| + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_2, 0x0c); |
| + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_3, 0xac); |
| + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_4, 0xba); |
| +#endif |
| + break; |
| + case QAM_64: |
| + case QAM_256: |
| + lgdt3305_write_reg(state, 0x030d, 0x14); |
| +#if 1 |
| + ret = lgdt3305_set_if(state, param); |
| + if (lg_fail(ret)) |
| + goto fail; |
| +#endif |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + |
| +#if 0 |
| + /* the set_if vsb formula doesn't work for the 3304, we end up sending |
| + * 0x40851e07 instead of 0x4f0cacba (which works back to 94050, rather |
| + * than 3250, in the case of the kworld 340u) */ |
| + ret = lgdt3305_set_if(state, param); |
| + if (lg_fail(ret)) |
| + goto fail; |
| +#endif |
| + |
| + ret = lgdt3305_spectral_inversion(state, param, |
| + state->cfg->spectral_inversion |
| + ? 1 : 0); |
| + if (lg_fail(ret)) |
| + goto fail; |
| + |
| + state->current_modulation = param->u.vsb.modulation; |
| + |
| + ret = lgdt3305_mpeg_mode(state, state->cfg->mpeg_mode); |
| + if (lg_fail(ret)) |
| + goto fail; |
| + |
| + /* lgdt3305_mpeg_mode_polarity calls lgdt3305_soft_reset */ |
| + ret = lgdt3305_mpeg_mode_polarity(state, |
| + state->cfg->tpclk_edge, |
| + state->cfg->tpvalid_polarity); |
| +fail: |
| + return ret; |
| +} |
| + |
| static int lgdt3305_set_parameters(struct dvb_frontend *fe, |
| struct dvb_frontend_parameters *param) |
| { |
| @@ -847,6 +996,10 @@ static int lgdt3305_read_status(struct dvb_frontend *fe, fe_status_t *status) |
| switch (state->current_modulation) { |
| case QAM_256: |
| case QAM_64: |
| +#if 0 /* needed w/3304 to set FE_HAS_SIGNAL */ |
| + if (cr_lock) |
| + *status |= FE_HAS_SIGNAL; |
| +#endif |
| ret = lgdt3305_read_fec_lock_status(state, &fec_lock); |
| if (lg_fail(ret)) |
| goto fail; |
| @@ -992,6 +1145,7 @@ static void lgdt3305_release(struct dvb_frontend *fe) |
| kfree(state); |
| } |
| |
| +static struct dvb_frontend_ops lgdt3304_ops; |
| static struct dvb_frontend_ops lgdt3305_ops; |
| |
| struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config, |
| @@ -1012,11 +1166,21 @@ struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config, |
| state->cfg = config; |
| state->i2c_adap = i2c_adap; |
| |
| - memcpy(&state->frontend.ops, &lgdt3305_ops, |
| - sizeof(struct dvb_frontend_ops)); |
| + switch (config->demod_chip) { |
| + case LGDT3304: |
| + memcpy(&state->frontend.ops, &lgdt3304_ops, |
| + sizeof(struct dvb_frontend_ops)); |
| + break; |
| + case LGDT3305: |
| + memcpy(&state->frontend.ops, &lgdt3305_ops, |
| + sizeof(struct dvb_frontend_ops)); |
| + break; |
| + default: |
| + goto fail; |
| + } |
| state->frontend.demodulator_priv = state; |
| |
| - /* verify that we're talking to a lg dt3305 */ |
| + /* verify that we're talking to a lg dt3304/5 */ |
| ret = lgdt3305_read_reg(state, LGDT3305_GEN_CTRL_2, &val); |
| if ((lg_fail(ret)) | (val == 0)) |
| goto fail; |
| @@ -1035,12 +1199,36 @@ struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config, |
| |
| return &state->frontend; |
| fail: |
| - lg_warn("unable to detect LGDT3305 hardware\n"); |
| + lg_warn("unable to detect %s hardware\n", |
| + config->demod_chip ? "LGDT3304" : "LGDT3305"); |
| kfree(state); |
| return NULL; |
| } |
| EXPORT_SYMBOL(lgdt3305_attach); |
| |
| +static struct dvb_frontend_ops lgdt3304_ops = { |
| + .info = { |
| + .name = "LG Electronics LGDT3304 VSB/QAM Frontend", |
| + .type = FE_ATSC, |
| + .frequency_min = 54000000, |
| + .frequency_max = 858000000, |
| + .frequency_stepsize = 62500, |
| + .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB |
| + }, |
| + .i2c_gate_ctrl = lgdt3305_i2c_gate_ctrl, |
| + .init = lgdt3304_init, |
| + .sleep = lgdt3304_sleep, |
| + .set_frontend = lgdt3304_set_parameters, |
| + .get_frontend = lgdt3305_get_frontend, |
| + .get_tune_settings = lgdt3305_get_tune_settings, |
| + .read_status = lgdt3305_read_status, |
| + .read_ber = lgdt3305_read_ber, |
| + .read_signal_strength = lgdt3305_read_signal_strength, |
| + .read_snr = lgdt3305_read_snr, |
| + .read_ucblocks = lgdt3305_read_ucblocks, |
| + .release = lgdt3305_release, |
| +}; |
| + |
| static struct dvb_frontend_ops lgdt3305_ops = { |
| .info = { |
| .name = "LG Electronics LGDT3305 VSB/QAM Frontend", |
| @@ -1064,7 +1252,7 @@ static struct dvb_frontend_ops lgdt3305_ops = { |
| .release = lgdt3305_release, |
| }; |
| |
| -MODULE_DESCRIPTION("LG Electronics LGDT3305 ATSC/QAM-B Demodulator Driver"); |
| +MODULE_DESCRIPTION("LG Electronics LGDT3304/5 ATSC/QAM-B Demodulator Driver"); |
| MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION("0.1"); |
| diff --git a/drivers/media/dvb/frontends/lgdt3305.h b/drivers/media/dvb/frontends/lgdt3305.h |
| index 9cb11c9..a7f30c2 100644 |
| |
| |
| @@ -41,6 +41,11 @@ enum lgdt3305_tp_valid_polarity { |
| LGDT3305_TP_VALID_HIGH = 1, |
| }; |
| |
| +enum lgdt_demod_chip_type { |
| + LGDT3305 = 0, |
| + LGDT3304 = 1, |
| +}; |
| + |
| struct lgdt3305_config { |
| u8 i2c_addr; |
| |
| @@ -65,6 +70,7 @@ struct lgdt3305_config { |
| enum lgdt3305_mpeg_mode mpeg_mode; |
| enum lgdt3305_tp_clock_edge tpclk_edge; |
| enum lgdt3305_tp_valid_polarity tpvalid_polarity; |
| + enum lgdt_demod_chip_type demod_chip; |
| }; |
| |
| #if defined(CONFIG_DVB_LGDT3305) || (defined(CONFIG_DVB_LGDT3305_MODULE) && \ |
| -- |
| 1.6.6 |
| |