351 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			351 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| 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
 | |
| --- a/drivers/media/dvb/frontends/lgdt3305.c
 | |
| +++ b/drivers/media/dvb/frontends/lgdt3305.c
 | |
| @@ -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
 | |
| --- a/drivers/media/dvb/frontends/lgdt3305.h
 | |
| +++ b/drivers/media/dvb/frontends/lgdt3305.h
 | |
| @@ -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
 | |
| 
 |