diff options
Diffstat (limited to 'kernel/drivers/media/dvb-frontends/lgdt330x.c')
-rw-r--r-- | kernel/drivers/media/dvb-frontends/lgdt330x.c | 825 |
1 files changed, 825 insertions, 0 deletions
diff --git a/kernel/drivers/media/dvb-frontends/lgdt330x.c b/kernel/drivers/media/dvb-frontends/lgdt330x.c new file mode 100644 index 000000000..2e1a61893 --- /dev/null +++ b/kernel/drivers/media/dvb-frontends/lgdt330x.c @@ -0,0 +1,825 @@ +/* + * Support for LGDT3302 and LGDT3303 - VSB/QAM + * + * Copyright (C) 2005 Wilson Michaels <wilsonmichaels@earthlink.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* + * NOTES ABOUT THIS DRIVER + * + * This Linux driver supports: + * DViCO FusionHDTV 3 Gold-Q + * DViCO FusionHDTV 3 Gold-T + * DViCO FusionHDTV 5 Gold + * DViCO FusionHDTV 5 Lite + * DViCO FusionHDTV 5 USB Gold + * Air2PC/AirStar 2 ATSC 3rd generation (HD5000) + * pcHDTV HD5500 + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/byteorder.h> + +#include "dvb_frontend.h" +#include "dvb_math.h" +#include "lgdt330x_priv.h" +#include "lgdt330x.h" + +/* Use Equalizer Mean Squared Error instead of Phaser Tracker MSE */ +/* #define USE_EQMSE */ + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"Turn on/off lgdt330x frontend debugging (default:off)."); +#define dprintk(args...) \ +do { \ +if (debug) printk(KERN_DEBUG "lgdt330x: " args); \ +} while (0) + +struct lgdt330x_state +{ + struct i2c_adapter* i2c; + + /* Configuration settings */ + const struct lgdt330x_config* config; + + struct dvb_frontend frontend; + + /* Demodulator private data */ + fe_modulation_t current_modulation; + u32 snr; /* Result of last SNR calculation */ + + /* Tuner private data */ + u32 current_frequency; +}; + +static int i2c_write_demod_bytes (struct lgdt330x_state* state, + u8 *buf, /* data bytes to send */ + int len /* number of bytes to send */ ) +{ + struct i2c_msg msg = + { .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 }; + int i; + int err; + + for (i=0; i<len-1; i+=2){ + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "lgdt330x: %s error (addr %02x <- %02x, err = %i)\n", __func__, msg.buf[0], msg.buf[1], err); + if (err < 0) + return err; + else + return -EREMOTEIO; + } + msg.buf += 2; + } + return 0; +} + +/* + * This routine writes the register (reg) to the demod bus + * then reads the data returned for (len) bytes. + */ + +static int i2c_read_demod_bytes(struct lgdt330x_state *state, + enum I2C_REG reg, u8 *buf, int len) +{ + u8 wr [] = { reg }; + struct i2c_msg msg [] = { + { .addr = state->config->demod_address, + .flags = 0, .buf = wr, .len = 1 }, + { .addr = state->config->demod_address, + .flags = I2C_M_RD, .buf = buf, .len = len }, + }; + int ret; + ret = i2c_transfer(state->i2c, msg, 2); + if (ret != 2) { + printk(KERN_WARNING "lgdt330x: %s: addr 0x%02x select 0x%02x error (ret == %i)\n", __func__, state->config->demod_address, reg, ret); + if (ret >= 0) + ret = -EIO; + } else { + ret = 0; + } + return ret; +} + +/* Software reset */ +static int lgdt3302_SwReset(struct lgdt330x_state* state) +{ + u8 ret; + u8 reset[] = { + IRQ_MASK, + 0x00 /* bit 6 is active low software reset + * bits 5-0 are 1 to mask interrupts */ + }; + + ret = i2c_write_demod_bytes(state, + reset, sizeof(reset)); + if (ret == 0) { + + /* force reset high (inactive) and unmask interrupts */ + reset[1] = 0x7f; + ret = i2c_write_demod_bytes(state, + reset, sizeof(reset)); + } + return ret; +} + +static int lgdt3303_SwReset(struct lgdt330x_state* state) +{ + u8 ret; + u8 reset[] = { + 0x02, + 0x00 /* bit 0 is active low software reset */ + }; + + ret = i2c_write_demod_bytes(state, + reset, sizeof(reset)); + if (ret == 0) { + + /* force reset high (inactive) */ + reset[1] = 0x01; + ret = i2c_write_demod_bytes(state, + reset, sizeof(reset)); + } + return ret; +} + +static int lgdt330x_SwReset(struct lgdt330x_state* state) +{ + switch (state->config->demod_chip) { + case LGDT3302: + return lgdt3302_SwReset(state); + case LGDT3303: + return lgdt3303_SwReset(state); + default: + return -ENODEV; + } +} + +static int lgdt330x_init(struct dvb_frontend* fe) +{ + /* Hardware reset is done using gpio[0] of cx23880x chip. + * I'd like to do it here, but don't know how to find chip address. + * cx88-cards.c arranges for the reset bit to be inactive (high). + * Maybe there needs to be a callable function in cx88-core or + * the caller of this function needs to do it. */ + + /* + * Array of byte pairs <address, value> + * to initialize each different chip + */ + static u8 lgdt3302_init_data[] = { + /* Use 50MHz parameter values from spec sheet since xtal is 50 */ + /* Change the value of NCOCTFV[25:0] of carrier + recovery center frequency register */ + VSB_CARRIER_FREQ0, 0x00, + VSB_CARRIER_FREQ1, 0x87, + VSB_CARRIER_FREQ2, 0x8e, + VSB_CARRIER_FREQ3, 0x01, + /* Change the TPCLK pin polarity + data is valid on falling clock */ + DEMUX_CONTROL, 0xfb, + /* Change the value of IFBW[11:0] of + AGC IF/RF loop filter bandwidth register */ + AGC_RF_BANDWIDTH0, 0x40, + AGC_RF_BANDWIDTH1, 0x93, + AGC_RF_BANDWIDTH2, 0x00, + /* Change the value of bit 6, 'nINAGCBY' and + 'NSSEL[1:0] of ACG function control register 2 */ + AGC_FUNC_CTRL2, 0xc6, + /* Change the value of bit 6 'RFFIX' + of AGC function control register 3 */ + AGC_FUNC_CTRL3, 0x40, + /* Set the value of 'INLVTHD' register 0x2a/0x2c + to 0x7fe */ + AGC_DELAY0, 0x07, + AGC_DELAY2, 0xfe, + /* Change the value of IAGCBW[15:8] + of inner AGC loop filter bandwidth */ + AGC_LOOP_BANDWIDTH0, 0x08, + AGC_LOOP_BANDWIDTH1, 0x9a + }; + + static u8 lgdt3303_init_data[] = { + 0x4c, 0x14 + }; + + static u8 flip_1_lgdt3303_init_data[] = { + 0x4c, 0x14, + 0x87, 0xf3 + }; + + static u8 flip_2_lgdt3303_init_data[] = { + 0x4c, 0x14, + 0x87, 0xda + }; + + struct lgdt330x_state* state = fe->demodulator_priv; + char *chip_name; + int err; + + switch (state->config->demod_chip) { + case LGDT3302: + chip_name = "LGDT3302"; + err = i2c_write_demod_bytes(state, lgdt3302_init_data, + sizeof(lgdt3302_init_data)); + break; + case LGDT3303: + chip_name = "LGDT3303"; + switch (state->config->clock_polarity_flip) { + case 2: + err = i2c_write_demod_bytes(state, + flip_2_lgdt3303_init_data, + sizeof(flip_2_lgdt3303_init_data)); + break; + case 1: + err = i2c_write_demod_bytes(state, + flip_1_lgdt3303_init_data, + sizeof(flip_1_lgdt3303_init_data)); + break; + case 0: + default: + err = i2c_write_demod_bytes(state, lgdt3303_init_data, + sizeof(lgdt3303_init_data)); + } + break; + default: + chip_name = "undefined"; + printk (KERN_WARNING "Only LGDT3302 and LGDT3303 are supported chips.\n"); + err = -ENODEV; + } + dprintk("%s entered as %s\n", __func__, chip_name); + if (err < 0) + return err; + return lgdt330x_SwReset(state); +} + +static int lgdt330x_read_ber(struct dvb_frontend* fe, u32* ber) +{ + *ber = 0; /* Not supplied by the demod chips */ + return 0; +} + +static int lgdt330x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct lgdt330x_state* state = fe->demodulator_priv; + int err; + u8 buf[2]; + + *ucblocks = 0; + + switch (state->config->demod_chip) { + case LGDT3302: + err = i2c_read_demod_bytes(state, LGDT3302_PACKET_ERR_COUNTER1, + buf, sizeof(buf)); + break; + case LGDT3303: + err = i2c_read_demod_bytes(state, LGDT3303_PACKET_ERR_COUNTER1, + buf, sizeof(buf)); + break; + default: + printk(KERN_WARNING + "Only LGDT3302 and LGDT3303 are supported chips.\n"); + err = -ENODEV; + } + if (err < 0) + return err; + + *ucblocks = (buf[0] << 8) | buf[1]; + return 0; +} + +static int lgdt330x_set_parameters(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + /* + * Array of byte pairs <address, value> + * to initialize 8VSB for lgdt3303 chip 50 MHz IF + */ + static u8 lgdt3303_8vsb_44_data[] = { + 0x04, 0x00, + 0x0d, 0x40, + 0x0e, 0x87, + 0x0f, 0x8e, + 0x10, 0x01, + 0x47, 0x8b }; + + /* + * Array of byte pairs <address, value> + * to initialize QAM for lgdt3303 chip + */ + static u8 lgdt3303_qam_data[] = { + 0x04, 0x00, + 0x0d, 0x00, + 0x0e, 0x00, + 0x0f, 0x00, + 0x10, 0x00, + 0x51, 0x63, + 0x47, 0x66, + 0x48, 0x66, + 0x4d, 0x1a, + 0x49, 0x08, + 0x4a, 0x9b }; + + struct lgdt330x_state* state = fe->demodulator_priv; + + static u8 top_ctrl_cfg[] = { TOP_CONTROL, 0x03 }; + + int err = 0; + /* Change only if we are actually changing the modulation */ + if (state->current_modulation != p->modulation) { + switch (p->modulation) { + case VSB_8: + dprintk("%s: VSB_8 MODE\n", __func__); + + /* Select VSB mode */ + top_ctrl_cfg[1] = 0x03; + + /* Select ANT connector if supported by card */ + if (state->config->pll_rf_set) + state->config->pll_rf_set(fe, 1); + + if (state->config->demod_chip == LGDT3303) { + err = i2c_write_demod_bytes(state, lgdt3303_8vsb_44_data, + sizeof(lgdt3303_8vsb_44_data)); + } + break; + + case QAM_64: + dprintk("%s: QAM_64 MODE\n", __func__); + + /* Select QAM_64 mode */ + top_ctrl_cfg[1] = 0x00; + + /* Select CABLE connector if supported by card */ + if (state->config->pll_rf_set) + state->config->pll_rf_set(fe, 0); + + if (state->config->demod_chip == LGDT3303) { + err = i2c_write_demod_bytes(state, lgdt3303_qam_data, + sizeof(lgdt3303_qam_data)); + } + break; + + case QAM_256: + dprintk("%s: QAM_256 MODE\n", __func__); + + /* Select QAM_256 mode */ + top_ctrl_cfg[1] = 0x01; + + /* Select CABLE connector if supported by card */ + if (state->config->pll_rf_set) + state->config->pll_rf_set(fe, 0); + + if (state->config->demod_chip == LGDT3303) { + err = i2c_write_demod_bytes(state, lgdt3303_qam_data, + sizeof(lgdt3303_qam_data)); + } + break; + default: + printk(KERN_WARNING "lgdt330x: %s: Modulation type(%d) UNSUPPORTED\n", __func__, p->modulation); + return -1; + } + if (err < 0) + printk(KERN_WARNING "lgdt330x: %s: error blasting " + "bytes to lgdt3303 for modulation type(%d)\n", + __func__, p->modulation); + + /* + * select serial or parallel MPEG harware interface + * Serial: 0x04 for LGDT3302 or 0x40 for LGDT3303 + * Parallel: 0x00 + */ + top_ctrl_cfg[1] |= state->config->serial_mpeg; + + /* Select the requested mode */ + i2c_write_demod_bytes(state, top_ctrl_cfg, + sizeof(top_ctrl_cfg)); + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); + state->current_modulation = p->modulation; + } + + /* Tune to the specified frequency */ + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 0); + } + + /* Keep track of the new frequency */ + /* FIXME this is the wrong way to do this... */ + /* The tuner is shared with the video4linux analog API */ + state->current_frequency = p->frequency; + + lgdt330x_SwReset(state); + return 0; +} + +static int lgdt330x_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct lgdt330x_state *state = fe->demodulator_priv; + p->frequency = state->current_frequency; + return 0; +} + +static int lgdt3302_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct lgdt330x_state* state = fe->demodulator_priv; + u8 buf[3]; + + *status = 0; /* Reset status result */ + + /* AGC status register */ + i2c_read_demod_bytes(state, AGC_STATUS, buf, 1); + dprintk("%s: AGC_STATUS = 0x%02x\n", __func__, buf[0]); + if ((buf[0] & 0x0c) == 0x8){ + /* Test signal does not exist flag */ + /* as well as the AGC lock flag. */ + *status |= FE_HAS_SIGNAL; + } + + /* + * You must set the Mask bits to 1 in the IRQ_MASK in order + * to see that status bit in the IRQ_STATUS register. + * This is done in SwReset(); + */ + /* signal status */ + i2c_read_demod_bytes(state, TOP_CONTROL, buf, sizeof(buf)); + dprintk("%s: TOP_CONTROL = 0x%02x, IRO_MASK = 0x%02x, IRQ_STATUS = 0x%02x\n", __func__, buf[0], buf[1], buf[2]); + + + /* sync status */ + if ((buf[2] & 0x03) == 0x01) { + *status |= FE_HAS_SYNC; + } + + /* FEC error status */ + if ((buf[2] & 0x0c) == 0x08) { + *status |= FE_HAS_LOCK; + *status |= FE_HAS_VITERBI; + } + + /* Carrier Recovery Lock Status Register */ + i2c_read_demod_bytes(state, CARRIER_LOCK, buf, 1); + dprintk("%s: CARRIER_LOCK = 0x%02x\n", __func__, buf[0]); + switch (state->current_modulation) { + case QAM_256: + case QAM_64: + /* Need to understand why there are 3 lock levels here */ + if ((buf[0] & 0x07) == 0x07) + *status |= FE_HAS_CARRIER; + break; + case VSB_8: + if ((buf[0] & 0x80) == 0x80) + *status |= FE_HAS_CARRIER; + break; + default: + printk(KERN_WARNING "lgdt330x: %s: Modulation set to unsupported value\n", __func__); + } + + return 0; +} + +static int lgdt3303_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct lgdt330x_state* state = fe->demodulator_priv; + int err; + u8 buf[3]; + + *status = 0; /* Reset status result */ + + /* lgdt3303 AGC status register */ + err = i2c_read_demod_bytes(state, 0x58, buf, 1); + if (err < 0) + return err; + + dprintk("%s: AGC_STATUS = 0x%02x\n", __func__, buf[0]); + if ((buf[0] & 0x21) == 0x01){ + /* Test input signal does not exist flag */ + /* as well as the AGC lock flag. */ + *status |= FE_HAS_SIGNAL; + } + + /* Carrier Recovery Lock Status Register */ + i2c_read_demod_bytes(state, CARRIER_LOCK, buf, 1); + dprintk("%s: CARRIER_LOCK = 0x%02x\n", __func__, buf[0]); + switch (state->current_modulation) { + case QAM_256: + case QAM_64: + /* Need to understand why there are 3 lock levels here */ + if ((buf[0] & 0x07) == 0x07) + *status |= FE_HAS_CARRIER; + else + break; + i2c_read_demod_bytes(state, 0x8a, buf, 1); + if ((buf[0] & 0x04) == 0x04) + *status |= FE_HAS_SYNC; + if ((buf[0] & 0x01) == 0x01) + *status |= FE_HAS_LOCK; + if ((buf[0] & 0x08) == 0x08) + *status |= FE_HAS_VITERBI; + break; + case VSB_8: + if ((buf[0] & 0x80) == 0x80) + *status |= FE_HAS_CARRIER; + else + break; + i2c_read_demod_bytes(state, 0x38, buf, 1); + if ((buf[0] & 0x02) == 0x00) + *status |= FE_HAS_SYNC; + if ((buf[0] & 0x01) == 0x01) { + *status |= FE_HAS_LOCK; + *status |= FE_HAS_VITERBI; + } + break; + default: + printk(KERN_WARNING "lgdt330x: %s: Modulation set to unsupported value\n", __func__); + } + return 0; +} + +/* Calculate SNR estimation (scaled by 2^24) + + 8-VSB SNR equations from LGDT3302 and LGDT3303 datasheets, QAM + equations from LGDT3303 datasheet. VSB is the same between the '02 + and '03, so maybe QAM is too? Perhaps someone with a newer datasheet + that has QAM information could verify? + + For 8-VSB: (two ways, take your pick) + LGDT3302: + SNR_EQ = 10 * log10(25 * 24^2 / EQ_MSE) + LGDT3303: + SNR_EQ = 10 * log10(25 * 32^2 / EQ_MSE) + LGDT3302 & LGDT3303: + SNR_PT = 10 * log10(25 * 32^2 / PT_MSE) (we use this one) + For 64-QAM: + SNR = 10 * log10( 688128 / MSEQAM) + For 256-QAM: + SNR = 10 * log10( 696320 / MSEQAM) + + We re-write the snr equation as: + SNR * 2^24 = 10*(c - intlog10(MSE)) + Where for 256-QAM, c = log10(696320) * 2^24, and so on. */ + +static u32 calculate_snr(u32 mse, u32 c) +{ + if (mse == 0) /* No signal */ + return 0; + + mse = intlog10(mse); + if (mse > c) { + /* Negative SNR, which is possible, but realisticly the + demod will lose lock before the signal gets this bad. The + API only allows for unsigned values, so just return 0 */ + return 0; + } + return 10*(c - mse); +} + +static int lgdt3302_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + u8 buf[5]; /* read data buffer */ + u32 noise; /* noise value */ + u32 c; /* per-modulation SNR calculation constant */ + + switch(state->current_modulation) { + case VSB_8: + i2c_read_demod_bytes(state, LGDT3302_EQPH_ERR0, buf, 5); +#ifdef USE_EQMSE + /* Use Equalizer Mean-Square Error Register */ + /* SNR for ranges from -15.61 to +41.58 */ + noise = ((buf[0] & 7) << 16) | (buf[1] << 8) | buf[2]; + c = 69765745; /* log10(25*24^2)*2^24 */ +#else + /* Use Phase Tracker Mean-Square Error Register */ + /* SNR for ranges from -13.11 to +44.08 */ + noise = ((buf[0] & 7<<3) << 13) | (buf[3] << 8) | buf[4]; + c = 73957994; /* log10(25*32^2)*2^24 */ +#endif + break; + case QAM_64: + case QAM_256: + i2c_read_demod_bytes(state, CARRIER_MSEQAM1, buf, 2); + noise = ((buf[0] & 3) << 8) | buf[1]; + c = state->current_modulation == QAM_64 ? 97939837 : 98026066; + /* log10(688128)*2^24 and log10(696320)*2^24 */ + break; + default: + printk(KERN_ERR "lgdt330x: %s: Modulation set to unsupported value\n", + __func__); + return -EREMOTEIO; /* return -EDRIVER_IS_GIBBERED; */ + } + + state->snr = calculate_snr(noise, c); + *snr = (state->snr) >> 16; /* Convert from 8.24 fixed-point to 8.8 */ + + dprintk("%s: noise = 0x%08x, snr = %d.%02d dB\n", __func__, noise, + state->snr >> 24, (((state->snr>>8) & 0xffff) * 100) >> 16); + + return 0; +} + +static int lgdt3303_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + u8 buf[5]; /* read data buffer */ + u32 noise; /* noise value */ + u32 c; /* per-modulation SNR calculation constant */ + + switch(state->current_modulation) { + case VSB_8: + i2c_read_demod_bytes(state, LGDT3303_EQPH_ERR0, buf, 5); +#ifdef USE_EQMSE + /* Use Equalizer Mean-Square Error Register */ + /* SNR for ranges from -16.12 to +44.08 */ + noise = ((buf[0] & 0x78) << 13) | (buf[1] << 8) | buf[2]; + c = 73957994; /* log10(25*32^2)*2^24 */ +#else + /* Use Phase Tracker Mean-Square Error Register */ + /* SNR for ranges from -13.11 to +44.08 */ + noise = ((buf[0] & 7) << 16) | (buf[3] << 8) | buf[4]; + c = 73957994; /* log10(25*32^2)*2^24 */ +#endif + break; + case QAM_64: + case QAM_256: + i2c_read_demod_bytes(state, CARRIER_MSEQAM1, buf, 2); + noise = (buf[0] << 8) | buf[1]; + c = state->current_modulation == QAM_64 ? 97939837 : 98026066; + /* log10(688128)*2^24 and log10(696320)*2^24 */ + break; + default: + printk(KERN_ERR "lgdt330x: %s: Modulation set to unsupported value\n", + __func__); + return -EREMOTEIO; /* return -EDRIVER_IS_GIBBERED; */ + } + + state->snr = calculate_snr(noise, c); + *snr = (state->snr) >> 16; /* Convert from 8.24 fixed-point to 8.8 */ + + dprintk("%s: noise = 0x%08x, snr = %d.%02d dB\n", __func__, noise, + state->snr >> 24, (((state->snr >> 8) & 0xffff) * 100) >> 16); + + return 0; +} + +static int lgdt330x_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + /* Calculate Strength from SNR up to 35dB */ + /* Even though the SNR can go higher than 35dB, there is some comfort */ + /* factor in having a range of strong signals that can show at 100% */ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + u16 snr; + int ret; + + ret = fe->ops.read_snr(fe, &snr); + if (ret != 0) + return ret; + /* Rather than use the 8.8 value snr, use state->snr which is 8.24 */ + /* scale the range 0 - 35*2^24 into 0 - 65535 */ + if (state->snr >= 8960 * 0x10000) + *strength = 0xffff; + else + *strength = state->snr / 8960; + + return 0; +} + +static int lgdt330x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fe_tune_settings) +{ + /* I have no idea about this - it may not be needed */ + fe_tune_settings->min_delay_ms = 500; + fe_tune_settings->step_size = 0; + fe_tune_settings->max_drift = 0; + return 0; +} + +static void lgdt330x_release(struct dvb_frontend* fe) +{ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops lgdt3302_ops; +static struct dvb_frontend_ops lgdt3303_ops; + +struct dvb_frontend* lgdt330x_attach(const struct lgdt330x_config* config, + struct i2c_adapter* i2c) +{ + struct lgdt330x_state* state = NULL; + u8 buf[1]; + + /* Allocate memory for the internal state */ + state = kzalloc(sizeof(struct lgdt330x_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* Setup the state */ + state->config = config; + state->i2c = i2c; + + /* Create dvb_frontend */ + switch (config->demod_chip) { + case LGDT3302: + memcpy(&state->frontend.ops, &lgdt3302_ops, sizeof(struct dvb_frontend_ops)); + break; + case LGDT3303: + memcpy(&state->frontend.ops, &lgdt3303_ops, sizeof(struct dvb_frontend_ops)); + break; + default: + goto error; + } + state->frontend.demodulator_priv = state; + + /* Verify communication with demod chip */ + if (i2c_read_demod_bytes(state, 2, buf, 1)) + goto error; + + state->current_frequency = -1; + state->current_modulation = -1; + + return &state->frontend; + +error: + kfree(state); + dprintk("%s: ERROR\n",__func__); + return NULL; +} + +static struct dvb_frontend_ops lgdt3302_ops = { + .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, + .info = { + .name= "LG Electronics LGDT3302 VSB/QAM Frontend", + .frequency_min= 54000000, + .frequency_max= 858000000, + .frequency_stepsize= 62500, + .symbol_rate_min = 5056941, /* QAM 64 */ + .symbol_rate_max = 10762000, /* VSB 8 */ + .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB + }, + .init = lgdt330x_init, + .set_frontend = lgdt330x_set_parameters, + .get_frontend = lgdt330x_get_frontend, + .get_tune_settings = lgdt330x_get_tune_settings, + .read_status = lgdt3302_read_status, + .read_ber = lgdt330x_read_ber, + .read_signal_strength = lgdt330x_read_signal_strength, + .read_snr = lgdt3302_read_snr, + .read_ucblocks = lgdt330x_read_ucblocks, + .release = lgdt330x_release, +}; + +static struct dvb_frontend_ops lgdt3303_ops = { + .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, + .info = { + .name= "LG Electronics LGDT3303 VSB/QAM Frontend", + .frequency_min= 54000000, + .frequency_max= 858000000, + .frequency_stepsize= 62500, + .symbol_rate_min = 5056941, /* QAM 64 */ + .symbol_rate_max = 10762000, /* VSB 8 */ + .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB + }, + .init = lgdt330x_init, + .set_frontend = lgdt330x_set_parameters, + .get_frontend = lgdt330x_get_frontend, + .get_tune_settings = lgdt330x_get_tune_settings, + .read_status = lgdt3303_read_status, + .read_ber = lgdt330x_read_ber, + .read_signal_strength = lgdt330x_read_signal_strength, + .read_snr = lgdt3303_read_snr, + .read_ucblocks = lgdt330x_read_ucblocks, + .release = lgdt330x_release, +}; + +MODULE_DESCRIPTION("LGDT330X (ATSC 8VSB & ITU-T J.83 AnnexB 64/256 QAM) Demodulator Driver"); +MODULE_AUTHOR("Wilson Michaels"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(lgdt330x_attach); |