diff options
Diffstat (limited to 'drivers/media/dvb/frontends/at76c651.c')
-rw-r--r-- | drivers/media/dvb/frontends/at76c651.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/drivers/media/dvb/frontends/at76c651.c b/drivers/media/dvb/frontends/at76c651.c new file mode 100644 index 000000000000..ce2eaa1640e8 --- /dev/null +++ b/drivers/media/dvb/frontends/at76c651.c @@ -0,0 +1,450 @@ +/* + * at76c651.c + * + * Atmel DVB-C Frontend Driver (at76c651/tua6010xs) + * + * Copyright (C) 2001 fnbrd <fnbrd@gmx.de> + * & 2002-2004 Andreas Oberritter <obi@linuxtv.org> + * & 2003 Wolfram Joost <dbox2@frokaschwei.de> + * + * 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. + * + * AT76C651 + * http://www.nalanda.nitc.ac.in/industry/datasheets/atmel/acrobat/doc1293.pdf + * http://www.atmel.com/atmel/acrobat/doc1320.pdf + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include "dvb_frontend.h" +#include "at76c651.h" + + +struct at76c651_state { + + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + + const struct at76c651_config* config; + + struct dvb_frontend frontend; + + /* revision of the chip */ + u8 revision; + + /* last QAM value set */ + u8 qam; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "at76c651: " args); \ + } while (0) + + +#if ! defined(__powerpc__) +static __inline__ int __ilog2(unsigned long x) +{ + int i; + + if (x == 0) + return -1; + + for (i = 0; x != 0; i++) + x >>= 1; + + return i - 1; +} +#endif + +static int at76c651_writereg(struct at76c651_state* state, u8 reg, u8 data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = + { .addr = state->config->demod_address, .flags = 0, .buf = buf, .len = 2 }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: writereg error " + "(reg == 0x%02x, val == 0x%02x, ret == %i)\n", + __FUNCTION__, reg, data, ret); + + msleep(10); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static u8 at76c651_readreg(struct at76c651_state* state, u8 reg) +{ + int ret; + u8 val; + struct i2c_msg msg[] = { + { .addr = state->config->demod_address, .flags = 0, .buf = ®, .len = 1 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = &val, .len = 1 } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (ret == %i)\n", __FUNCTION__, ret); + + return val; +} + +static int at76c651_reset(struct at76c651_state* state) +{ + return at76c651_writereg(state, 0x07, 0x01); +} + +static void at76c651_disable_interrupts(struct at76c651_state* state) +{ + at76c651_writereg(state, 0x0b, 0x00); +} + +static int at76c651_set_auto_config(struct at76c651_state *state) +{ + /* + * Autoconfig + */ + + at76c651_writereg(state, 0x06, 0x01); + + /* + * Performance optimizations, should be done after autoconfig + */ + + at76c651_writereg(state, 0x10, 0x06); + at76c651_writereg(state, 0x11, ((state->qam == 5) || (state->qam == 7)) ? 0x12 : 0x10); + at76c651_writereg(state, 0x15, 0x28); + at76c651_writereg(state, 0x20, 0x09); + at76c651_writereg(state, 0x24, ((state->qam == 5) || (state->qam == 7)) ? 0xC0 : 0x90); + at76c651_writereg(state, 0x30, 0x90); + if (state->qam == 5) + at76c651_writereg(state, 0x35, 0x2A); + + /* + * Initialize A/D-converter + */ + + if (state->revision == 0x11) { + at76c651_writereg(state, 0x2E, 0x38); + at76c651_writereg(state, 0x2F, 0x13); + } + + at76c651_disable_interrupts(state); + + /* + * Restart operation + */ + + at76c651_reset(state); + + return 0; +} + +static void at76c651_set_bbfreq(struct at76c651_state* state) +{ + at76c651_writereg(state, 0x04, 0x3f); + at76c651_writereg(state, 0x05, 0xee); +} + +static int at76c651_set_symbol_rate(struct at76c651_state* state, u32 symbol_rate) +{ + u8 exponent; + u32 mantissa; + + if (symbol_rate > 9360000) + return -EINVAL; + + /* + * FREF = 57800 kHz + * exponent = 10 + floor (log2(symbol_rate / FREF)) + * mantissa = (symbol_rate / FREF) * (1 << (30 - exponent)) + */ + + exponent = __ilog2((symbol_rate << 4) / 903125); + mantissa = ((symbol_rate / 3125) * (1 << (24 - exponent))) / 289; + + at76c651_writereg(state, 0x00, mantissa >> 13); + at76c651_writereg(state, 0x01, mantissa >> 5); + at76c651_writereg(state, 0x02, (mantissa << 3) | exponent); + + return 0; +} + +static int at76c651_set_qam(struct at76c651_state *state, fe_modulation_t qam) +{ + switch (qam) { + case QPSK: + state->qam = 0x02; + break; + case QAM_16: + state->qam = 0x04; + break; + case QAM_32: + state->qam = 0x05; + break; + case QAM_64: + state->qam = 0x06; + break; + case QAM_128: + state->qam = 0x07; + break; + case QAM_256: + state->qam = 0x08; + break; +#if 0 + case QAM_512: + state->qam = 0x09; + break; + case QAM_1024: + state->qam = 0x0A; + break; +#endif + default: + return -EINVAL; + + } + + return at76c651_writereg(state, 0x03, state->qam); +} + +static int at76c651_set_inversion(struct at76c651_state* state, fe_spectral_inversion_t inversion) +{ + u8 feciqinv = at76c651_readreg(state, 0x60); + + switch (inversion) { + case INVERSION_OFF: + feciqinv |= 0x02; + feciqinv &= 0xFE; + break; + + case INVERSION_ON: + feciqinv |= 0x03; + break; + + case INVERSION_AUTO: + feciqinv &= 0xFC; + break; + + default: + return -EINVAL; + } + + return at76c651_writereg(state, 0x60, feciqinv); +} + +static int at76c651_set_parameters(struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + int ret; + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + at76c651_writereg(state, 0x0c, 0xc3); + state->config->pll_set(fe, p); + at76c651_writereg(state, 0x0c, 0xc2); + + if ((ret = at76c651_set_symbol_rate(state, p->u.qam.symbol_rate))) + return ret; + + if ((ret = at76c651_set_inversion(state, p->inversion))) + return ret; + + return at76c651_set_auto_config(state); +} + +static int at76c651_set_defaults(struct dvb_frontend* fe) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + at76c651_set_symbol_rate(state, 6900000); + at76c651_set_qam(state, QAM_64); + at76c651_set_bbfreq(state); + at76c651_set_auto_config(state); + + if (state->config->pll_init) { + at76c651_writereg(state, 0x0c, 0xc3); + state->config->pll_init(fe); + at76c651_writereg(state, 0x0c, 0xc2); + } + + return 0; +} + +static int at76c651_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + u8 sync; + + /* + * Bits: FEC, CAR, EQU, TIM, AGC2, AGC1, ADC, PLL (PLL=0) + */ + sync = at76c651_readreg(state, 0x80); + *status = 0; + + if (sync & (0x04 | 0x10)) /* AGC1 || TIM */ + *status |= FE_HAS_SIGNAL; + if (sync & 0x10) /* TIM */ + *status |= FE_HAS_CARRIER; + if (sync & 0x80) /* FEC */ + *status |= FE_HAS_VITERBI; + if (sync & 0x40) /* CAR */ + *status |= FE_HAS_SYNC; + if ((sync & 0xF0) == 0xF0) /* TIM && EQU && CAR && FEC */ + *status |= FE_HAS_LOCK; + + return 0; +} + +static int at76c651_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + *ber = (at76c651_readreg(state, 0x81) & 0x0F) << 16; + *ber |= at76c651_readreg(state, 0x82) << 8; + *ber |= at76c651_readreg(state, 0x83); + *ber *= 10; + + return 0; +} + +static int at76c651_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + u8 gain = ~at76c651_readreg(state, 0x91); + *strength = (gain << 8) | gain; + + return 0; +} + +static int at76c651_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + *snr = 0xFFFF - + ((at76c651_readreg(state, 0x8F) << 8) | + at76c651_readreg(state, 0x90)); + + return 0; +} + +static int at76c651_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + *ucblocks = at76c651_readreg(state, 0x82); + + return 0; +} + +static int at76c651_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 50; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void at76c651_release(struct dvb_frontend* fe) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops at76c651_ops; + +struct dvb_frontend* at76c651_attach(const struct at76c651_config* config, + struct i2c_adapter* i2c) +{ + struct at76c651_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct at76c651_state*) kmalloc(sizeof(struct at76c651_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->qam = 0; + + /* check if the demod is there */ + if (at76c651_readreg(state, 0x0e) != 0x65) goto error; + + /* finalise state setup */ + state->i2c = i2c; + state->revision = at76c651_readreg(state, 0x0f) & 0xfe; + memcpy(&state->ops, &at76c651_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops at76c651_ops = { + + .info = { + .name = "Atmel AT76C651B DVB-C", + .type = FE_QAM, + .frequency_min = 48250000, + .frequency_max = 863250000, + .frequency_stepsize = 62500, + /*.frequency_tolerance = */ /* FIXME: 12% of SR */ + .symbol_rate_min = 0, /* FIXME */ + .symbol_rate_max = 9360000, /* FIXME */ + .symbol_rate_tolerance = 4000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | FE_CAN_FEC_8_9 | FE_CAN_FEC_AUTO | + FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 | FE_CAN_QAM_128 | + FE_CAN_MUTE_TS | FE_CAN_QAM_256 | FE_CAN_RECOVER + }, + + .release = at76c651_release, + + .init = at76c651_set_defaults, + + .set_frontend = at76c651_set_parameters, + .get_tune_settings = at76c651_get_tune_settings, + + .read_status = at76c651_read_status, + .read_ber = at76c651_read_ber, + .read_signal_strength = at76c651_read_signal_strength, + .read_snr = at76c651_read_snr, + .read_ucblocks = at76c651_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Atmel AT76C651 DVB-C Demodulator Driver"); +MODULE_AUTHOR("Andreas Oberritter <obi@linuxtv.org>"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(at76c651_attach); |