linux/drivers/media/dvb-frontends/lgdt3305.c

1203 lines
30 KiB
C
Raw Normal View History

treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 157 Based on 3 normalized pattern(s): 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 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 [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] 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 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 [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-27 06:55:06 +00:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Support for LG Electronics LGDT3304 and LGDT3305 - VSB/QAM
*
* Copyright (C) 2008, 2009, 2010 Michael Krufky <mkrufky@linuxtv.org>
*
* LGDT3304 support by Jarod Wilson <jarod@redhat.com>
*/
#include <asm/div64.h>
#include <linux/dvb/frontend.h>
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
#include <linux/slab.h>
#include <media/dvb_math.h>
#include "lgdt3305.h"
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "set debug level (info=1, reg=2 (or-able))");
#define DBG_INFO 1
#define DBG_REG 2
#define lg_printk(kern, fmt, arg...) \
printk(kern "%s: " fmt, __func__, ##arg)
#define lg_info(fmt, arg...) printk(KERN_INFO "lgdt3305: " fmt, ##arg)
#define lg_warn(fmt, arg...) lg_printk(KERN_WARNING, fmt, ##arg)
#define lg_err(fmt, arg...) lg_printk(KERN_ERR, fmt, ##arg)
#define lg_dbg(fmt, arg...) if (debug & DBG_INFO) \
lg_printk(KERN_DEBUG, fmt, ##arg)
#define lg_reg(fmt, arg...) if (debug & DBG_REG) \
lg_printk(KERN_DEBUG, fmt, ##arg)
#define lg_fail(ret) \
({ \
int __ret; \
__ret = (ret < 0); \
if (__ret) \
lg_err("error %d on line %d\n", ret, __LINE__); \
__ret; \
})
struct lgdt3305_state {
struct i2c_adapter *i2c_adap;
const struct lgdt3305_config *cfg;
struct dvb_frontend frontend;
enum fe_modulation current_modulation;
u32 current_frequency;
u32 snr;
};
/* ------------------------------------------------------------------------ */
/* FIXME: verify & document the LGDT3304 registers */
#define LGDT3305_GEN_CTRL_1 0x0000
#define LGDT3305_GEN_CTRL_2 0x0001
#define LGDT3305_GEN_CTRL_3 0x0002
#define LGDT3305_GEN_STATUS 0x0003
#define LGDT3305_GEN_CONTROL 0x0007
#define LGDT3305_GEN_CTRL_4 0x000a
#define LGDT3305_DGTL_AGC_REF_1 0x0012
#define LGDT3305_DGTL_AGC_REF_2 0x0013
#define LGDT3305_CR_CTR_FREQ_1 0x0106
#define LGDT3305_CR_CTR_FREQ_2 0x0107
#define LGDT3305_CR_CTR_FREQ_3 0x0108
#define LGDT3305_CR_CTR_FREQ_4 0x0109
#define LGDT3305_CR_MSE_1 0x011b
#define LGDT3305_CR_MSE_2 0x011c
#define LGDT3305_CR_LOCK_STATUS 0x011d
#define LGDT3305_CR_CTRL_7 0x0126
#define LGDT3305_AGC_POWER_REF_1 0x0300
#define LGDT3305_AGC_POWER_REF_2 0x0301
#define LGDT3305_AGC_DELAY_PT_1 0x0302
#define LGDT3305_AGC_DELAY_PT_2 0x0303
#define LGDT3305_RFAGC_LOOP_FLTR_BW_1 0x0306
#define LGDT3305_RFAGC_LOOP_FLTR_BW_2 0x0307
#define LGDT3305_IFBW_1 0x0308
#define LGDT3305_IFBW_2 0x0309
#define LGDT3305_AGC_CTRL_1 0x030c
#define LGDT3305_AGC_CTRL_4 0x0314
#define LGDT3305_EQ_MSE_1 0x0413
#define LGDT3305_EQ_MSE_2 0x0414
#define LGDT3305_EQ_MSE_3 0x0415
#define LGDT3305_PT_MSE_1 0x0417
#define LGDT3305_PT_MSE_2 0x0418
#define LGDT3305_PT_MSE_3 0x0419
#define LGDT3305_FEC_BLOCK_CTRL 0x0504
#define LGDT3305_FEC_LOCK_STATUS 0x050a
#define LGDT3305_FEC_PKT_ERR_1 0x050c
#define LGDT3305_FEC_PKT_ERR_2 0x050d
#define LGDT3305_TP_CTRL_1 0x050e
#define LGDT3305_BERT_PERIOD 0x0801
#define LGDT3305_BERT_ERROR_COUNT_1 0x080a
#define LGDT3305_BERT_ERROR_COUNT_2 0x080b
#define LGDT3305_BERT_ERROR_COUNT_3 0x080c
#define LGDT3305_BERT_ERROR_COUNT_4 0x080d
static int lgdt3305_write_reg(struct lgdt3305_state *state, u16 reg, u8 val)
{
int ret;
u8 buf[] = { reg >> 8, reg & 0xff, val };
struct i2c_msg msg = {
.addr = state->cfg->i2c_addr, .flags = 0,
.buf = buf, .len = 3,
};
lg_reg("reg: 0x%04x, val: 0x%02x\n", reg, val);
ret = i2c_transfer(state->i2c_adap, &msg, 1);
if (ret != 1) {
lg_err("error (addr %02x %02x <- %02x, err = %i)\n",
msg.buf[0], msg.buf[1], msg.buf[2], ret);
if (ret < 0)
return ret;
else
return -EREMOTEIO;
}
return 0;
}
static int lgdt3305_read_reg(struct lgdt3305_state *state, u16 reg, u8 *val)
{
int ret;
u8 reg_buf[] = { reg >> 8, reg & 0xff };
struct i2c_msg msg[] = {
{ .addr = state->cfg->i2c_addr,
.flags = 0, .buf = reg_buf, .len = 2 },
{ .addr = state->cfg->i2c_addr,
.flags = I2C_M_RD, .buf = val, .len = 1 },
};
lg_reg("reg: 0x%04x\n", reg);
ret = i2c_transfer(state->i2c_adap, msg, 2);
if (ret != 2) {
lg_err("error (addr %02x reg %04x error (ret == %i)\n",
state->cfg->i2c_addr, reg, ret);
if (ret < 0)
return ret;
else
return -EREMOTEIO;
}
return 0;
}
#define read_reg(state, reg) \
({ \
u8 __val; \
int ret = lgdt3305_read_reg(state, reg, &__val); \
if (lg_fail(ret)) \
__val = 0; \
__val; \
})
static int lgdt3305_set_reg_bit(struct lgdt3305_state *state,
u16 reg, int bit, int onoff)
{
u8 val;
int ret;
lg_reg("reg: 0x%04x, bit: %d, level: %d\n", reg, bit, onoff);
ret = lgdt3305_read_reg(state, reg, &val);
if (lg_fail(ret))
goto fail;
val &= ~(1 << bit);
val |= (onoff & 1) << bit;
ret = lgdt3305_write_reg(state, reg, val);
fail:
return ret;
}
struct lgdt3305_reg {
u16 reg;
u8 val;
};
static int lgdt3305_write_regs(struct lgdt3305_state *state,
struct lgdt3305_reg *regs, int len)
{
int i, ret;
lg_reg("writing %d registers...\n", len);
for (i = 0; i < len - 1; i++) {
ret = lgdt3305_write_reg(state, regs[i].reg, regs[i].val);
if (lg_fail(ret))
return ret;
}
return 0;
}
/* ------------------------------------------------------------------------ */
static int lgdt3305_soft_reset(struct lgdt3305_state *state)
{
int ret;
lg_dbg("\n");
ret = lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_3, 0, 0);
if (lg_fail(ret))
goto fail;
msleep(20);
ret = lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_3, 0, 1);
fail:
return ret;
}
static inline int lgdt3305_mpeg_mode(struct lgdt3305_state *state,
enum lgdt3305_mpeg_mode mode)
{
lg_dbg("(%d)\n", mode);
return lgdt3305_set_reg_bit(state, LGDT3305_TP_CTRL_1, 5, mode);
}
static int lgdt3305_mpeg_mode_polarity(struct lgdt3305_state *state)
{
u8 val;
int ret;
enum lgdt3305_tp_clock_edge edge = state->cfg->tpclk_edge;
enum lgdt3305_tp_clock_mode mode = state->cfg->tpclk_mode;
enum lgdt3305_tp_valid_polarity valid = state->cfg->tpvalid_polarity;
lg_dbg("edge = %d, valid = %d\n", edge, valid);
ret = lgdt3305_read_reg(state, LGDT3305_TP_CTRL_1, &val);
if (lg_fail(ret))
goto fail;
val &= ~0x09;
if (edge)
val |= 0x08;
if (mode)
val |= 0x40;
if (valid)
val |= 0x01;
ret = lgdt3305_write_reg(state, LGDT3305_TP_CTRL_1, val);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_soft_reset(state);
fail:
return ret;
}
static int lgdt3305_set_modulation(struct lgdt3305_state *state,
struct dtv_frontend_properties *p)
{
u8 opermode;
int ret;
lg_dbg("\n");
ret = lgdt3305_read_reg(state, LGDT3305_GEN_CTRL_1, &opermode);
if (lg_fail(ret))
goto fail;
opermode &= ~0x03;
switch (p->modulation) {
case VSB_8:
opermode |= 0x03;
break;
case QAM_64:
opermode |= 0x00;
break;
case QAM_256:
opermode |= 0x01;
break;
default:
return -EINVAL;
}
ret = lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_1, opermode);
fail:
return ret;
}
static int lgdt3305_set_filter_extension(struct lgdt3305_state *state,
struct dtv_frontend_properties *p)
{
int val;
switch (p->modulation) {
case VSB_8:
val = 0;
break;
case QAM_64:
case QAM_256:
val = 1;
break;
default:
return -EINVAL;
}
lg_dbg("val = %d\n", val);
return lgdt3305_set_reg_bit(state, 0x043f, 2, val);
}
/* ------------------------------------------------------------------------ */
static int lgdt3305_passband_digital_agc(struct lgdt3305_state *state,
struct dtv_frontend_properties *p)
{
u16 agc_ref;
switch (p->modulation) {
case VSB_8:
agc_ref = 0x32c4;
break;
case QAM_64:
agc_ref = 0x2a00;
break;
case QAM_256:
agc_ref = 0x2a80;
break;
default:
return -EINVAL;
}
lg_dbg("agc ref: 0x%04x\n", agc_ref);
lgdt3305_write_reg(state, LGDT3305_DGTL_AGC_REF_1, agc_ref >> 8);
lgdt3305_write_reg(state, LGDT3305_DGTL_AGC_REF_2, agc_ref & 0xff);
return 0;
}
static int lgdt3305_rfagc_loop(struct lgdt3305_state *state,
struct dtv_frontend_properties *p)
{
u16 ifbw, rfbw, agcdelay;
switch (p->modulation) {
case VSB_8:
agcdelay = 0x04c0;
rfbw = 0x8000;
ifbw = 0x8000;
break;
case QAM_64:
case QAM_256:
agcdelay = 0x046b;
rfbw = 0x8889;
/* FIXME: investigate optimal ifbw & rfbw values for the
* DT3304 and re-write this switch..case block */
if (state->cfg->demod_chip == LGDT3304)
ifbw = 0x6666;
else /* (state->cfg->demod_chip == LGDT3305) */
ifbw = 0x8888;
break;
default:
return -EINVAL;
}
if (state->cfg->rf_agc_loop) {
lg_dbg("agcdelay: 0x%04x, rfbw: 0x%04x\n", agcdelay, rfbw);
/* rf agc loop filter bandwidth */
lgdt3305_write_reg(state, LGDT3305_AGC_DELAY_PT_1,
agcdelay >> 8);
lgdt3305_write_reg(state, LGDT3305_AGC_DELAY_PT_2,
agcdelay & 0xff);
lgdt3305_write_reg(state, LGDT3305_RFAGC_LOOP_FLTR_BW_1,
rfbw >> 8);
lgdt3305_write_reg(state, LGDT3305_RFAGC_LOOP_FLTR_BW_2,
rfbw & 0xff);
} else {
lg_dbg("ifbw: 0x%04x\n", ifbw);
/* if agc loop filter bandwidth */
lgdt3305_write_reg(state, LGDT3305_IFBW_1, ifbw >> 8);
lgdt3305_write_reg(state, LGDT3305_IFBW_2, ifbw & 0xff);
}
return 0;
}
static int lgdt3305_agc_setup(struct lgdt3305_state *state,
struct dtv_frontend_properties *p)
{
int lockdten, acqen;
switch (p->modulation) {
case VSB_8:
lockdten = 0;
acqen = 0;
break;
case QAM_64:
case QAM_256:
lockdten = 1;
acqen = 1;
break;
default:
return -EINVAL;
}
lg_dbg("lockdten = %d, acqen = %d\n", lockdten, acqen);
/* control agc function */
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, p);
}
static int lgdt3305_set_agc_power_ref(struct lgdt3305_state *state,
struct dtv_frontend_properties *p)
{
u16 usref = 0;
switch (p->modulation) {
case VSB_8:
if (state->cfg->usref_8vsb)
usref = state->cfg->usref_8vsb;
break;
case QAM_64:
if (state->cfg->usref_qam64)
usref = state->cfg->usref_qam64;
break;
case QAM_256:
if (state->cfg->usref_qam256)
usref = state->cfg->usref_qam256;
break;
default:
return -EINVAL;
}
if (usref) {
lg_dbg("set manual mode: 0x%04x\n", usref);
lgdt3305_set_reg_bit(state, LGDT3305_AGC_CTRL_1, 3, 1);
lgdt3305_write_reg(state, LGDT3305_AGC_POWER_REF_1,
0xff & (usref >> 8));
lgdt3305_write_reg(state, LGDT3305_AGC_POWER_REF_2,
0xff & (usref >> 0));
}
return 0;
}
/* ------------------------------------------------------------------------ */
static int lgdt3305_spectral_inversion(struct lgdt3305_state *state,
struct dtv_frontend_properties *p,
int inversion)
{
int ret;
lg_dbg("(%d)\n", inversion);
switch (p->modulation) {
case VSB_8:
ret = lgdt3305_write_reg(state, LGDT3305_CR_CTRL_7,
inversion ? 0xf9 : 0x79);
break;
case QAM_64:
case QAM_256:
ret = lgdt3305_write_reg(state, LGDT3305_FEC_BLOCK_CTRL,
inversion ? 0xfd : 0xff);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int lgdt3305_set_if(struct lgdt3305_state *state,
struct dtv_frontend_properties *p)
{
u16 if_freq_khz;
u8 nco1, nco2, nco3, nco4;
u64 nco;
switch (p->modulation) {
case VSB_8:
if_freq_khz = state->cfg->vsb_if_khz;
break;
case QAM_64:
case QAM_256:
if_freq_khz = state->cfg->qam_if_khz;
break;
default:
return -EINVAL;
}
nco = if_freq_khz / 10;
switch (p->modulation) {
case VSB_8:
nco <<= 24;
do_div(nco, 625);
break;
case QAM_64:
case QAM_256:
nco <<= 28;
do_div(nco, 625);
break;
default:
return -EINVAL;
}
nco1 = (nco >> 24) & 0x3f;
nco1 |= 0x40;
nco2 = (nco >> 16) & 0xff;
nco3 = (nco >> 8) & 0xff;
nco4 = nco & 0xff;
lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_1, nco1);
lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_2, nco2);
lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_3, nco3);
lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_4, nco4);
lg_dbg("%d KHz -> [%02x%02x%02x%02x]\n",
if_freq_khz, nco1, nco2, nco3, nco4);
return 0;
}
/* ------------------------------------------------------------------------ */
static int lgdt3305_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
{
struct lgdt3305_state *state = fe->demodulator_priv;
if (state->cfg->deny_i2c_rptr)
return 0;
lg_dbg("(%d)\n", enable);
return lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_2, 5,
enable ? 0 : 1);
}
static int lgdt3305_sleep(struct dvb_frontend *fe)
{
struct lgdt3305_state *state = fe->demodulator_priv;
u8 gen_ctrl_3, gen_ctrl_4;
lg_dbg("\n");
gen_ctrl_3 = read_reg(state, LGDT3305_GEN_CTRL_3);
gen_ctrl_4 = read_reg(state, LGDT3305_GEN_CTRL_4);
/* hold in software reset while sleeping */
gen_ctrl_3 &= ~0x01;
/* tristate the IF-AGC pin */
gen_ctrl_3 |= 0x02;
/* tristate the RF-AGC pin */
gen_ctrl_3 |= 0x04;
/* disable vsb/qam module */
gen_ctrl_4 &= ~0x01;
/* disable adc module */
gen_ctrl_4 &= ~0x02;
lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_3, gen_ctrl_3);
lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_4, gen_ctrl_4);
return 0;
}
static int lgdt3305_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, },
};
static struct lgdt3305_reg lgdt3305_init_data[] = {
{ .reg = LGDT3305_GEN_CTRL_1, .val = 0x03, },
{ .reg = LGDT3305_GEN_CTRL_2, .val = 0xb0, },
{ .reg = LGDT3305_GEN_CTRL_3, .val = 0x01, },
{ .reg = LGDT3305_GEN_CONTROL, .val = 0x6f, },
{ .reg = LGDT3305_GEN_CTRL_4, .val = 0x03, },
{ .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 = 0x79, },
{ .reg = LGDT3305_AGC_POWER_REF_1, .val = 0x32, },
{ .reg = LGDT3305_AGC_POWER_REF_2, .val = 0xc4, },
{ .reg = LGDT3305_AGC_DELAY_PT_1, .val = 0x0d, },
{ .reg = LGDT3305_AGC_DELAY_PT_2, .val = 0x30, },
{ .reg = LGDT3305_RFAGC_LOOP_FLTR_BW_1, .val = 0x80, },
{ .reg = LGDT3305_RFAGC_LOOP_FLTR_BW_2, .val = 0x00, },
{ .reg = LGDT3305_IFBW_1, .val = 0x80, },
{ .reg = LGDT3305_IFBW_2, .val = 0x00, },
{ .reg = LGDT3305_AGC_CTRL_1, .val = 0x30, },
{ .reg = LGDT3305_AGC_CTRL_4, .val = 0x61, },
{ .reg = LGDT3305_FEC_BLOCK_CTRL, .val = 0xff, },
{ .reg = LGDT3305_TP_CTRL_1, .val = 0x1b, },
};
lg_dbg("\n");
switch (state->cfg->demod_chip) {
case LGDT3304:
ret = lgdt3305_write_regs(state, lgdt3304_init_data,
ARRAY_SIZE(lgdt3304_init_data));
break;
case LGDT3305:
ret = lgdt3305_write_regs(state, lgdt3305_init_data,
ARRAY_SIZE(lgdt3305_init_data));
break;
default:
ret = -EINVAL;
}
if (lg_fail(ret))
goto fail;
ret = lgdt3305_soft_reset(state);
fail:
return ret;
}
static int lgdt3304_set_parameters(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *p = &fe->dtv_property_cache;
struct lgdt3305_state *state = fe->demodulator_priv;
int ret;
lg_dbg("(%d, %d)\n", p->frequency, p->modulation);
if (fe->ops.tuner_ops.set_params) {
ret = fe->ops.tuner_ops.set_params(fe);
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 0);
if (lg_fail(ret))
goto fail;
state->current_frequency = p->frequency;
}
ret = lgdt3305_set_modulation(state, p);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_passband_digital_agc(state, p);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_agc_setup(state, p);
if (lg_fail(ret))
goto fail;
/* reg 0x030d is 3304-only... seen in vsb and qam usbsnoops... */
switch (p->modulation) {
case VSB_8:
lgdt3305_write_reg(state, 0x030d, 0x00);
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);
break;
case QAM_64:
case QAM_256:
lgdt3305_write_reg(state, 0x030d, 0x14);
ret = lgdt3305_set_if(state, p);
if (lg_fail(ret))
goto fail;
break;
default:
return -EINVAL;
}
ret = lgdt3305_spectral_inversion(state, p,
state->cfg->spectral_inversion
? 1 : 0);
if (lg_fail(ret))
goto fail;
state->current_modulation = p->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);
fail:
return ret;
}
static int lgdt3305_set_parameters(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *p = &fe->dtv_property_cache;
struct lgdt3305_state *state = fe->demodulator_priv;
int ret;
lg_dbg("(%d, %d)\n", p->frequency, p->modulation);
if (fe->ops.tuner_ops.set_params) {
ret = fe->ops.tuner_ops.set_params(fe);
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 0);
if (lg_fail(ret))
goto fail;
state->current_frequency = p->frequency;
}
ret = lgdt3305_set_modulation(state, p);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_passband_digital_agc(state, p);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_set_agc_power_ref(state, p);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_agc_setup(state, p);
if (lg_fail(ret))
goto fail;
/* low if */
ret = lgdt3305_write_reg(state, LGDT3305_GEN_CONTROL, 0x2f);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_set_reg_bit(state, LGDT3305_CR_CTR_FREQ_1, 6, 1);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_set_if(state, p);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_spectral_inversion(state, p,
state->cfg->spectral_inversion
? 1 : 0);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_set_filter_extension(state, p);
if (lg_fail(ret))
goto fail;
state->current_modulation = p->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);
fail:
return ret;
}
static int lgdt3305_get_frontend(struct dvb_frontend *fe,
struct dtv_frontend_properties *p)
{
struct lgdt3305_state *state = fe->demodulator_priv;
lg_dbg("\n");
p->modulation = state->current_modulation;
p->frequency = state->current_frequency;
return 0;
}
/* ------------------------------------------------------------------------ */
static int lgdt3305_read_cr_lock_status(struct lgdt3305_state *state,
int *locked)
{
u8 val;
int ret;
char *cr_lock_state = "";
*locked = 0;
ret = lgdt3305_read_reg(state, LGDT3305_CR_LOCK_STATUS, &val);
if (lg_fail(ret))
goto fail;
switch (state->current_modulation) {
case QAM_256:
case QAM_64:
if (val & (1 << 1))
*locked = 1;
switch (val & 0x07) {
case 0:
cr_lock_state = "QAM UNLOCK";
break;
case 4:
cr_lock_state = "QAM 1stLock";
break;
case 6:
cr_lock_state = "QAM 2ndLock";
break;
case 7:
cr_lock_state = "QAM FinalLock";
break;
default:
cr_lock_state = "CLOCKQAM-INVALID!";
break;
}
break;
case VSB_8:
if (val & (1 << 7)) {
*locked = 1;
cr_lock_state = "CLOCKVSB";
}
break;
default:
ret = -EINVAL;
}
lg_dbg("(%d) %s\n", *locked, cr_lock_state);
fail:
return ret;
}
static int lgdt3305_read_fec_lock_status(struct lgdt3305_state *state,
int *locked)
{
u8 val;
int ret, mpeg_lock, fec_lock, viterbi_lock;
*locked = 0;
switch (state->current_modulation) {
case QAM_256:
case QAM_64:
ret = lgdt3305_read_reg(state,
LGDT3305_FEC_LOCK_STATUS, &val);
if (lg_fail(ret))
goto fail;
mpeg_lock = (val & (1 << 0)) ? 1 : 0;
fec_lock = (val & (1 << 2)) ? 1 : 0;
viterbi_lock = (val & (1 << 3)) ? 1 : 0;
*locked = mpeg_lock && fec_lock && viterbi_lock;
lg_dbg("(%d) %s%s%s\n", *locked,
mpeg_lock ? "mpeg lock " : "",
fec_lock ? "fec lock " : "",
viterbi_lock ? "viterbi lock" : "");
break;
case VSB_8:
default:
ret = -EINVAL;
}
fail:
return ret;
}
static int lgdt3305_read_status(struct dvb_frontend *fe, enum fe_status *status)
{
struct lgdt3305_state *state = fe->demodulator_priv;
u8 val;
int ret, signal, inlock, nofecerr, snrgood,
cr_lock, fec_lock, sync_lock;
*status = 0;
ret = lgdt3305_read_reg(state, LGDT3305_GEN_STATUS, &val);
if (lg_fail(ret))
goto fail;
signal = (val & (1 << 4)) ? 1 : 0;
inlock = (val & (1 << 3)) ? 0 : 1;
sync_lock = (val & (1 << 2)) ? 1 : 0;
nofecerr = (val & (1 << 1)) ? 1 : 0;
snrgood = (val & (1 << 0)) ? 1 : 0;
lg_dbg("%s%s%s%s%s\n",
signal ? "SIGNALEXIST " : "",
inlock ? "INLOCK " : "",
sync_lock ? "SYNCLOCK " : "",
nofecerr ? "NOFECERR " : "",
snrgood ? "SNRGOOD " : "");
ret = lgdt3305_read_cr_lock_status(state, &cr_lock);
if (lg_fail(ret))
goto fail;
if (signal)
*status |= FE_HAS_SIGNAL;
if (cr_lock)
*status |= FE_HAS_CARRIER;
if (nofecerr)
*status |= FE_HAS_VITERBI;
if (sync_lock)
*status |= FE_HAS_SYNC;
switch (state->current_modulation) {
case QAM_256:
case QAM_64:
/* signal bit is unreliable on the DT3304 in QAM mode */
if (((LGDT3304 == state->cfg->demod_chip)) && (cr_lock))
*status |= FE_HAS_SIGNAL;
ret = lgdt3305_read_fec_lock_status(state, &fec_lock);
if (lg_fail(ret))
goto fail;
if (fec_lock)
*status |= FE_HAS_LOCK;
break;
case VSB_8:
if (inlock)
*status |= FE_HAS_LOCK;
break;
default:
ret = -EINVAL;
}
fail:
return ret;
}
/* ------------------------------------------------------------------------ */
/* borrowed from lgdt330x.c */
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 lgdt3305_read_snr(struct dvb_frontend *fe, u16 *snr)
{
struct lgdt3305_state *state = fe->demodulator_priv;
u32 noise; /* noise value */
u32 c; /* per-modulation SNR calculation constant */
switch (state->current_modulation) {
case VSB_8:
#ifdef USE_PTMSE
/* Use Phase Tracker Mean-Square Error Register */
/* SNR for ranges from -13.11 to +44.08 */
noise = ((read_reg(state, LGDT3305_PT_MSE_1) & 0x07) << 16) |
(read_reg(state, LGDT3305_PT_MSE_2) << 8) |
(read_reg(state, LGDT3305_PT_MSE_3) & 0xff);
c = 73957994; /* log10(25*32^2)*2^24 */
#else
/* Use Equalizer Mean-Square Error Register */
/* SNR for ranges from -16.12 to +44.08 */
noise = ((read_reg(state, LGDT3305_EQ_MSE_1) & 0x0f) << 16) |
(read_reg(state, LGDT3305_EQ_MSE_2) << 8) |
(read_reg(state, LGDT3305_EQ_MSE_3) & 0xff);
c = 73957994; /* log10(25*32^2)*2^24 */
#endif
break;
case QAM_64:
case QAM_256:
noise = (read_reg(state, LGDT3305_CR_MSE_1) << 8) |
(read_reg(state, LGDT3305_CR_MSE_2) & 0xff);
c = (state->current_modulation == QAM_64) ?
97939837 : 98026066;
/* log10(688128)*2^24 and log10(696320)*2^24 */
break;
default:
return -EINVAL;
}
state->snr = calculate_snr(noise, c);
/* report SNR in dB * 10 */
*snr = (state->snr / ((1 << 24) / 10));
lg_dbg("noise = 0x%08x, snr = %d.%02d dB\n", noise,
state->snr >> 24, (((state->snr >> 8) & 0xffff) * 100) >> 16);
return 0;
}
static int lgdt3305_read_signal_strength(struct dvb_frontend *fe,
u16 *strength)
{
/* borrowed from lgdt330x.c
*
* 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 lgdt3305_state *state = fe->demodulator_priv;
u16 snr;
int ret;
*strength = 0;
ret = fe->ops.read_snr(fe, &snr);
if (lg_fail(ret))
goto fail;
/* 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;
fail:
return ret;
}
/* ------------------------------------------------------------------------ */
static int lgdt3305_read_ber(struct dvb_frontend *fe, u32 *ber)
{
*ber = 0;
return 0;
}
static int lgdt3305_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
{
struct lgdt3305_state *state = fe->demodulator_priv;
*ucblocks =
(read_reg(state, LGDT3305_FEC_PKT_ERR_1) << 8) |
(read_reg(state, LGDT3305_FEC_PKT_ERR_2) & 0xff);
return 0;
}
static int lgdt3305_get_tune_settings(struct dvb_frontend *fe,
struct dvb_frontend_tune_settings
*fe_tune_settings)
{
fe_tune_settings->min_delay_ms = 500;
lg_dbg("\n");
return 0;
}
static void lgdt3305_release(struct dvb_frontend *fe)
{
struct lgdt3305_state *state = fe->demodulator_priv;
lg_dbg("\n");
kfree(state);
}
static const struct dvb_frontend_ops lgdt3304_ops;
static const struct dvb_frontend_ops lgdt3305_ops;
struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config,
struct i2c_adapter *i2c_adap)
{
struct lgdt3305_state *state = NULL;
int ret;
u8 val;
lg_dbg("(%d-%04x)\n",
i2c_adap ? i2c_adapter_id(i2c_adap) : 0,
config ? config->i2c_addr : 0);
state = kzalloc(sizeof(struct lgdt3305_state), GFP_KERNEL);
if (state == NULL)
goto fail;
state->cfg = config;
state->i2c_adap = i2c_adap;
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 dt3304/5 */
ret = lgdt3305_read_reg(state, LGDT3305_GEN_CTRL_2, &val);
if ((lg_fail(ret)) | (val == 0))
goto fail;
ret = lgdt3305_write_reg(state, 0x0808, 0x80);
if (lg_fail(ret))
goto fail;
ret = lgdt3305_read_reg(state, 0x0808, &val);
if ((lg_fail(ret)) | (val != 0x80))
goto fail;
ret = lgdt3305_write_reg(state, 0x0808, 0x00);
if (lg_fail(ret))
goto fail;
state->current_frequency = -1;
state->current_modulation = -1;
return &state->frontend;
fail:
lg_warn("unable to detect %s hardware\n",
config->demod_chip ? "LGDT3304" : "LGDT3305");
kfree(state);
return NULL;
}
EXPORT_SYMBOL(lgdt3305_attach);
static const struct dvb_frontend_ops lgdt3304_ops = {
.delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B },
.info = {
.name = "LG Electronics LGDT3304 VSB/QAM Frontend",
.frequency_min_hz = 54 * MHz,
.frequency_max_hz = 858 * MHz,
.frequency_stepsize_hz = 62500,
.caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
},
.i2c_gate_ctrl = lgdt3305_i2c_gate_ctrl,
.init = lgdt3305_init,
.sleep = lgdt3305_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 const struct dvb_frontend_ops lgdt3305_ops = {
.delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B },
.info = {
.name = "LG Electronics LGDT3305 VSB/QAM Frontend",
.frequency_min_hz = 54 * MHz,
.frequency_max_hz = 858 * MHz,
.frequency_stepsize_hz = 62500,
.caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
},
.i2c_gate_ctrl = lgdt3305_i2c_gate_ctrl,
.init = lgdt3305_init,
.sleep = lgdt3305_sleep,
.set_frontend = lgdt3305_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,
};
MODULE_DESCRIPTION("LG Electronics LGDT3304/5 ATSC/QAM-B Demodulator Driver");
MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.2");