Path: blob/master/drivers/media/dvb/frontends/cx22702.c
15112 views
/*1Conexant 22702 DVB OFDM demodulator driver23based on:4Alps TDMB7 DVB OFDM demodulator driver56Copyright (C) 2001-2002 Convergence Integrated Media GmbH7Holger Waechtler <[email protected]>89Copyright (C) 2004 Steven Toth <[email protected]>1011This program is free software; you can redistribute it and/or modify12it under the terms of the GNU General Public License as published by13the Free Software Foundation; either version 2 of the License, or14(at your option) any later version.1516This program is distributed in the hope that it will be useful,17but WITHOUT ANY WARRANTY; without even the implied warranty of18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the19GNU General Public License for more details.2021You should have received a copy of the GNU General Public License22along with this program; if not, write to the Free Software23Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.2425*/2627#include <linux/kernel.h>28#include <linux/init.h>29#include <linux/module.h>30#include <linux/string.h>31#include <linux/slab.h>32#include <linux/delay.h>33#include "dvb_frontend.h"34#include "cx22702.h"3536struct cx22702_state {3738struct i2c_adapter *i2c;3940/* configuration settings */41const struct cx22702_config *config;4243struct dvb_frontend frontend;4445/* previous uncorrected block counter */46u8 prevUCBlocks;47};4849static int debug;50module_param(debug, int, 0644);51MODULE_PARM_DESC(debug, "Enable verbose debug messages");5253#define dprintk if (debug) printk5455/* Register values to initialise the demod */56static const u8 init_tab[] = {570x00, 0x00, /* Stop acquisition */580x0B, 0x06,590x09, 0x01,600x0D, 0x41,610x16, 0x32,620x20, 0x0A,630x21, 0x17,640x24, 0x3e,650x26, 0xff,660x27, 0x10,670x28, 0x00,680x29, 0x00,690x2a, 0x10,700x2b, 0x00,710x2c, 0x10,720x2d, 0x00,730x48, 0xd4,740x49, 0x56,750x6b, 0x1e,760xc8, 0x02,770xf9, 0x00,780xfa, 0x00,790xfb, 0x00,800xfc, 0x00,810xfd, 0x00,82};8384static int cx22702_writereg(struct cx22702_state *state, u8 reg, u8 data)85{86int ret;87u8 buf[] = { reg, data };88struct i2c_msg msg = {89.addr = state->config->demod_address, .flags = 0,90.buf = buf, .len = 2 };9192ret = i2c_transfer(state->i2c, &msg, 1);9394if (unlikely(ret != 1)) {95printk(KERN_ERR96"%s: error (reg == 0x%02x, val == 0x%02x, ret == %i)\n",97__func__, reg, data, ret);98return -1;99}100101return 0;102}103104static u8 cx22702_readreg(struct cx22702_state *state, u8 reg)105{106int ret;107u8 data;108109struct i2c_msg msg[] = {110{ .addr = state->config->demod_address, .flags = 0,111.buf = ®, .len = 1 },112{ .addr = state->config->demod_address, .flags = I2C_M_RD,113.buf = &data, .len = 1 } };114115ret = i2c_transfer(state->i2c, msg, 2);116117if (unlikely(ret != 2)) {118printk(KERN_ERR "%s: error (reg == 0x%02x, ret == %i)\n",119__func__, reg, ret);120return 0;121}122123return data;124}125126static int cx22702_set_inversion(struct cx22702_state *state, int inversion)127{128u8 val;129130val = cx22702_readreg(state, 0x0C);131switch (inversion) {132case INVERSION_AUTO:133return -EOPNOTSUPP;134case INVERSION_ON:135val |= 0x01;136break;137case INVERSION_OFF:138val &= 0xfe;139break;140default:141return -EINVAL;142}143return cx22702_writereg(state, 0x0C, val);144}145146/* Retrieve the demod settings */147static int cx22702_get_tps(struct cx22702_state *state,148struct dvb_ofdm_parameters *p)149{150u8 val;151152/* Make sure the TPS regs are valid */153if (!(cx22702_readreg(state, 0x0A) & 0x20))154return -EAGAIN;155156val = cx22702_readreg(state, 0x01);157switch ((val & 0x18) >> 3) {158case 0:159p->constellation = QPSK;160break;161case 1:162p->constellation = QAM_16;163break;164case 2:165p->constellation = QAM_64;166break;167}168switch (val & 0x07) {169case 0:170p->hierarchy_information = HIERARCHY_NONE;171break;172case 1:173p->hierarchy_information = HIERARCHY_1;174break;175case 2:176p->hierarchy_information = HIERARCHY_2;177break;178case 3:179p->hierarchy_information = HIERARCHY_4;180break;181}182183184val = cx22702_readreg(state, 0x02);185switch ((val & 0x38) >> 3) {186case 0:187p->code_rate_HP = FEC_1_2;188break;189case 1:190p->code_rate_HP = FEC_2_3;191break;192case 2:193p->code_rate_HP = FEC_3_4;194break;195case 3:196p->code_rate_HP = FEC_5_6;197break;198case 4:199p->code_rate_HP = FEC_7_8;200break;201}202switch (val & 0x07) {203case 0:204p->code_rate_LP = FEC_1_2;205break;206case 1:207p->code_rate_LP = FEC_2_3;208break;209case 2:210p->code_rate_LP = FEC_3_4;211break;212case 3:213p->code_rate_LP = FEC_5_6;214break;215case 4:216p->code_rate_LP = FEC_7_8;217break;218}219220val = cx22702_readreg(state, 0x03);221switch ((val & 0x0c) >> 2) {222case 0:223p->guard_interval = GUARD_INTERVAL_1_32;224break;225case 1:226p->guard_interval = GUARD_INTERVAL_1_16;227break;228case 2:229p->guard_interval = GUARD_INTERVAL_1_8;230break;231case 3:232p->guard_interval = GUARD_INTERVAL_1_4;233break;234}235switch (val & 0x03) {236case 0:237p->transmission_mode = TRANSMISSION_MODE_2K;238break;239case 1:240p->transmission_mode = TRANSMISSION_MODE_8K;241break;242}243244return 0;245}246247static int cx22702_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)248{249struct cx22702_state *state = fe->demodulator_priv;250u8 val;251252dprintk("%s(%d)\n", __func__, enable);253val = cx22702_readreg(state, 0x0D);254if (enable)255val &= 0xfe;256else257val |= 0x01;258return cx22702_writereg(state, 0x0D, val);259}260261/* Talk to the demod, set the FEC, GUARD, QAM settings etc */262static int cx22702_set_tps(struct dvb_frontend *fe,263struct dvb_frontend_parameters *p)264{265u8 val;266struct cx22702_state *state = fe->demodulator_priv;267268if (fe->ops.tuner_ops.set_params) {269fe->ops.tuner_ops.set_params(fe, p);270if (fe->ops.i2c_gate_ctrl)271fe->ops.i2c_gate_ctrl(fe, 0);272}273274/* set inversion */275cx22702_set_inversion(state, p->inversion);276277/* set bandwidth */278val = cx22702_readreg(state, 0x0C) & 0xcf;279switch (p->u.ofdm.bandwidth) {280case BANDWIDTH_6_MHZ:281val |= 0x20;282break;283case BANDWIDTH_7_MHZ:284val |= 0x10;285break;286case BANDWIDTH_8_MHZ:287break;288default:289dprintk("%s: invalid bandwidth\n", __func__);290return -EINVAL;291}292cx22702_writereg(state, 0x0C, val);293294p->u.ofdm.code_rate_LP = FEC_AUTO; /* temp hack as manual not working */295296/* use auto configuration? */297if ((p->u.ofdm.hierarchy_information == HIERARCHY_AUTO) ||298(p->u.ofdm.constellation == QAM_AUTO) ||299(p->u.ofdm.code_rate_HP == FEC_AUTO) ||300(p->u.ofdm.code_rate_LP == FEC_AUTO) ||301(p->u.ofdm.guard_interval == GUARD_INTERVAL_AUTO) ||302(p->u.ofdm.transmission_mode == TRANSMISSION_MODE_AUTO)) {303304/* TPS Source - use hardware driven values */305cx22702_writereg(state, 0x06, 0x10);306cx22702_writereg(state, 0x07, 0x9);307cx22702_writereg(state, 0x08, 0xC1);308cx22702_writereg(state, 0x0B, cx22702_readreg(state, 0x0B)309& 0xfc);310cx22702_writereg(state, 0x0C,311(cx22702_readreg(state, 0x0C) & 0xBF) | 0x40);312cx22702_writereg(state, 0x00, 0x01); /* Begin acquisition */313dprintk("%s: Autodetecting\n", __func__);314return 0;315}316317/* manually programmed values */318switch (p->u.ofdm.constellation) { /* mask 0x18 */319case QPSK:320val = 0x00;321break;322case QAM_16:323val = 0x08;324break;325case QAM_64:326val = 0x10;327break;328default:329dprintk("%s: invalid constellation\n", __func__);330return -EINVAL;331}332switch (p->u.ofdm.hierarchy_information) { /* mask 0x07 */333case HIERARCHY_NONE:334break;335case HIERARCHY_1:336val |= 0x01;337break;338case HIERARCHY_2:339val |= 0x02;340break;341case HIERARCHY_4:342val |= 0x03;343break;344default:345dprintk("%s: invalid hierarchy\n", __func__);346return -EINVAL;347}348cx22702_writereg(state, 0x06, val);349350switch (p->u.ofdm.code_rate_HP) { /* mask 0x38 */351case FEC_NONE:352case FEC_1_2:353val = 0x00;354break;355case FEC_2_3:356val = 0x08;357break;358case FEC_3_4:359val = 0x10;360break;361case FEC_5_6:362val = 0x18;363break;364case FEC_7_8:365val = 0x20;366break;367default:368dprintk("%s: invalid code_rate_HP\n", __func__);369return -EINVAL;370}371switch (p->u.ofdm.code_rate_LP) { /* mask 0x07 */372case FEC_NONE:373case FEC_1_2:374break;375case FEC_2_3:376val |= 0x01;377break;378case FEC_3_4:379val |= 0x02;380break;381case FEC_5_6:382val |= 0x03;383break;384case FEC_7_8:385val |= 0x04;386break;387default:388dprintk("%s: invalid code_rate_LP\n", __func__);389return -EINVAL;390}391cx22702_writereg(state, 0x07, val);392393switch (p->u.ofdm.guard_interval) { /* mask 0x0c */394case GUARD_INTERVAL_1_32:395val = 0x00;396break;397case GUARD_INTERVAL_1_16:398val = 0x04;399break;400case GUARD_INTERVAL_1_8:401val = 0x08;402break;403case GUARD_INTERVAL_1_4:404val = 0x0c;405break;406default:407dprintk("%s: invalid guard_interval\n", __func__);408return -EINVAL;409}410switch (p->u.ofdm.transmission_mode) { /* mask 0x03 */411case TRANSMISSION_MODE_2K:412break;413case TRANSMISSION_MODE_8K:414val |= 0x1;415break;416default:417dprintk("%s: invalid transmission_mode\n", __func__);418return -EINVAL;419}420cx22702_writereg(state, 0x08, val);421cx22702_writereg(state, 0x0B,422(cx22702_readreg(state, 0x0B) & 0xfc) | 0x02);423cx22702_writereg(state, 0x0C,424(cx22702_readreg(state, 0x0C) & 0xBF) | 0x40);425426/* Begin channel acquisition */427cx22702_writereg(state, 0x00, 0x01);428429return 0;430}431432/* Reset the demod hardware and reset all of the configuration registers433to a default state. */434static int cx22702_init(struct dvb_frontend *fe)435{436int i;437struct cx22702_state *state = fe->demodulator_priv;438439cx22702_writereg(state, 0x00, 0x02);440441msleep(10);442443for (i = 0; i < ARRAY_SIZE(init_tab); i += 2)444cx22702_writereg(state, init_tab[i], init_tab[i + 1]);445446cx22702_writereg(state, 0xf8, (state->config->output_mode << 1)447& 0x02);448449cx22702_i2c_gate_ctrl(fe, 0);450451return 0;452}453454static int cx22702_read_status(struct dvb_frontend *fe, fe_status_t *status)455{456struct cx22702_state *state = fe->demodulator_priv;457u8 reg0A;458u8 reg23;459460*status = 0;461462reg0A = cx22702_readreg(state, 0x0A);463reg23 = cx22702_readreg(state, 0x23);464465dprintk("%s: status demod=0x%02x agc=0x%02x\n"466, __func__, reg0A, reg23);467468if (reg0A & 0x10) {469*status |= FE_HAS_LOCK;470*status |= FE_HAS_VITERBI;471*status |= FE_HAS_SYNC;472}473474if (reg0A & 0x20)475*status |= FE_HAS_CARRIER;476477if (reg23 < 0xf0)478*status |= FE_HAS_SIGNAL;479480return 0;481}482483static int cx22702_read_ber(struct dvb_frontend *fe, u32 *ber)484{485struct cx22702_state *state = fe->demodulator_priv;486487if (cx22702_readreg(state, 0xE4) & 0x02) {488/* Realtime statistics */489*ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 7490| (cx22702_readreg(state, 0xDF) & 0x7F);491} else {492/* Averagtine statistics */493*ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 7494| cx22702_readreg(state, 0xDF);495}496497return 0;498}499500static int cx22702_read_signal_strength(struct dvb_frontend *fe,501u16 *signal_strength)502{503struct cx22702_state *state = fe->demodulator_priv;504505u16 rs_ber;506rs_ber = cx22702_readreg(state, 0x23);507*signal_strength = (rs_ber << 8) | rs_ber;508509return 0;510}511512static int cx22702_read_snr(struct dvb_frontend *fe, u16 *snr)513{514struct cx22702_state *state = fe->demodulator_priv;515516u16 rs_ber;517if (cx22702_readreg(state, 0xE4) & 0x02) {518/* Realtime statistics */519rs_ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 7520| (cx22702_readreg(state, 0xDF) & 0x7F);521} else {522/* Averagine statistics */523rs_ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 8524| cx22702_readreg(state, 0xDF);525}526*snr = ~rs_ber;527528return 0;529}530531static int cx22702_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)532{533struct cx22702_state *state = fe->demodulator_priv;534535u8 _ucblocks;536537/* RS Uncorrectable Packet Count then reset */538_ucblocks = cx22702_readreg(state, 0xE3);539if (state->prevUCBlocks < _ucblocks)540*ucblocks = (_ucblocks - state->prevUCBlocks);541else542*ucblocks = state->prevUCBlocks - _ucblocks;543state->prevUCBlocks = _ucblocks;544545return 0;546}547548static int cx22702_get_frontend(struct dvb_frontend *fe,549struct dvb_frontend_parameters *p)550{551struct cx22702_state *state = fe->demodulator_priv;552553u8 reg0C = cx22702_readreg(state, 0x0C);554555p->inversion = reg0C & 0x1 ? INVERSION_ON : INVERSION_OFF;556return cx22702_get_tps(state, &p->u.ofdm);557}558559static int cx22702_get_tune_settings(struct dvb_frontend *fe,560struct dvb_frontend_tune_settings *tune)561{562tune->min_delay_ms = 1000;563return 0;564}565566static void cx22702_release(struct dvb_frontend *fe)567{568struct cx22702_state *state = fe->demodulator_priv;569kfree(state);570}571572static const struct dvb_frontend_ops cx22702_ops;573574struct dvb_frontend *cx22702_attach(const struct cx22702_config *config,575struct i2c_adapter *i2c)576{577struct cx22702_state *state = NULL;578579/* allocate memory for the internal state */580state = kzalloc(sizeof(struct cx22702_state), GFP_KERNEL);581if (state == NULL)582goto error;583584/* setup the state */585state->config = config;586state->i2c = i2c;587588/* check if the demod is there */589if (cx22702_readreg(state, 0x1f) != 0x3)590goto error;591592/* create dvb_frontend */593memcpy(&state->frontend.ops, &cx22702_ops,594sizeof(struct dvb_frontend_ops));595state->frontend.demodulator_priv = state;596return &state->frontend;597598error:599kfree(state);600return NULL;601}602EXPORT_SYMBOL(cx22702_attach);603604static const struct dvb_frontend_ops cx22702_ops = {605606.info = {607.name = "Conexant CX22702 DVB-T",608.type = FE_OFDM,609.frequency_min = 177000000,610.frequency_max = 858000000,611.frequency_stepsize = 166666,612.caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |613FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |614FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |615FE_CAN_HIERARCHY_AUTO | FE_CAN_GUARD_INTERVAL_AUTO |616FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_RECOVER617},618619.release = cx22702_release,620621.init = cx22702_init,622.i2c_gate_ctrl = cx22702_i2c_gate_ctrl,623624.set_frontend = cx22702_set_tps,625.get_frontend = cx22702_get_frontend,626.get_tune_settings = cx22702_get_tune_settings,627628.read_status = cx22702_read_status,629.read_ber = cx22702_read_ber,630.read_signal_strength = cx22702_read_signal_strength,631.read_snr = cx22702_read_snr,632.read_ucblocks = cx22702_read_ucblocks,633};634635MODULE_DESCRIPTION("Conexant CX22702 DVB-T Demodulator driver");636MODULE_AUTHOR("Steven Toth");637MODULE_LICENSE("GPL");638639640