2015-08-05 17:22:42 +02:00
|
|
|
/*
|
|
|
|
* tda18212: Driver for the TDA18212 tuner
|
|
|
|
*
|
|
|
|
* Copyright (C) 2011-2013 Digital Devices GmbH
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* version 2 only, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
* 02110-1301, USA
|
|
|
|
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/firmware.h>
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/version.h>
|
|
|
|
#include <asm/div64.h>
|
|
|
|
|
2020-08-29 15:32:42 +02:00
|
|
|
#include <media/dvb_frontend.h>
|
2015-08-05 17:22:42 +02:00
|
|
|
|
|
|
|
#ifndef CHK_ERROR
|
|
|
|
#define CHK_ERROR(s) if ((status = s) < 0) break
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define MASTER_PSM_AGC1 0
|
|
|
|
#define MASTER_AGC1_6_15dB 1
|
|
|
|
|
|
|
|
#define SLAVE_PSM_AGC1 1
|
|
|
|
#define SLAVE_AGC1_6_15dB 0
|
|
|
|
|
|
|
|
/* 0 = 2 Vpp ... 2 = 1 Vpp, 7 = 0.5 Vpp */
|
|
|
|
#define IF_LEVEL_DVBC 2
|
|
|
|
#define IF_LEVEL_DVBT 2
|
|
|
|
|
|
|
|
enum {
|
|
|
|
ID_1 = 0x00,
|
|
|
|
ID_2 = 0x01,
|
|
|
|
ID_3 = 0x02,
|
|
|
|
THERMO_1,
|
|
|
|
THERMO_2,
|
|
|
|
POWER_STATE_1,
|
|
|
|
POWER_STATE_2,
|
|
|
|
INPUT_POWER_LEVEL,
|
|
|
|
IRQ_STATUS,
|
|
|
|
IRQ_ENABLE,
|
|
|
|
IRQ_CLEAR,
|
|
|
|
IRQ_SET,
|
|
|
|
AGC1_1,
|
|
|
|
AGC2_1,
|
|
|
|
AGCK_1,
|
|
|
|
RF_AGC_1,
|
|
|
|
IR_MIXER_1 = 0x10,
|
|
|
|
AGC5_1,
|
|
|
|
IF_AGC,
|
|
|
|
IF_1,
|
|
|
|
REFERENCE,
|
|
|
|
IF_FREQUENCY_1,
|
|
|
|
RF_FREQUENCY_1,
|
|
|
|
RF_FREQUENCY_2,
|
|
|
|
RF_FREQUENCY_3,
|
|
|
|
MSM_1,
|
|
|
|
MSM_2,
|
|
|
|
PSM_1,
|
|
|
|
DCC_1,
|
|
|
|
FLO_MAX,
|
|
|
|
IR_CAL_1,
|
|
|
|
IR_CAL_2,
|
|
|
|
IR_CAL_3 = 0x20,
|
|
|
|
IR_CAL_4,
|
|
|
|
VSYNC_MGT,
|
|
|
|
IR_MIXER_2,
|
|
|
|
AGC1_2,
|
|
|
|
AGC5_2,
|
|
|
|
RF_CAL_1,
|
|
|
|
RF_CAL_2,
|
|
|
|
RF_CAL_3,
|
|
|
|
RF_CAL_4,
|
|
|
|
RF_CAL_5,
|
|
|
|
RF_CAL_6,
|
|
|
|
RF_FILTER_1,
|
|
|
|
RF_FILTER_2,
|
|
|
|
RF_FILTER_3,
|
|
|
|
RF_BAND_PASS_FILTER,
|
|
|
|
CP_CURRENT = 0x30,
|
|
|
|
AGC_DET_OUT = 0x31,
|
|
|
|
RF_AGC_GAIN_1 = 0x32,
|
|
|
|
RF_AGC_GAIN_2 = 0x33,
|
|
|
|
IF_AGC_GAIN = 0x34,
|
|
|
|
POWER_1 = 0x35,
|
|
|
|
POWER_2 = 0x36,
|
|
|
|
MISC_1,
|
|
|
|
RFCAL_LOG_1,
|
|
|
|
RFCAL_LOG_2,
|
|
|
|
RFCAL_LOG_3,
|
|
|
|
RFCAL_LOG_4,
|
|
|
|
RFCAL_LOG_5,
|
|
|
|
RFCAL_LOG_6,
|
|
|
|
RFCAL_LOG_7,
|
|
|
|
RFCAL_LOG_8,
|
|
|
|
RFCAL_LOG_9 = 0x40,
|
|
|
|
RFCAL_LOG_10 = 0x41,
|
|
|
|
RFCAL_LOG_11 = 0x42,
|
|
|
|
RFCAL_LOG_12 = 0x43,
|
|
|
|
REG_MAX,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum HF_Standard {
|
|
|
|
HF_None = 0, HF_B, HF_DK, HF_G, HF_I, HF_L, HF_L1, HF_MN, HF_FM_Radio,
|
|
|
|
HF_AnalogMax, HF_DVBT_6MHZ, HF_DVBT_7MHZ, HF_DVBT_8MHZ,
|
|
|
|
HF_DVBT, HF_ATSC, HF_DVBC_6MHZ, HF_DVBC_7MHZ,
|
|
|
|
HF_DVBC_8MHZ, HF_DVBC
|
|
|
|
};
|
|
|
|
|
|
|
|
struct SStandardParams {
|
|
|
|
s32 m_IFFrequency;
|
|
|
|
u32 m_BandWidth;
|
|
|
|
u8 m_IF_1; /* FF IF_HP_fc:2 IF_Notch:1 LP_FC_Offset:2 LP_FC:3 */
|
|
|
|
u8 m_IR_MIXER_2; /* 03 :6 HI_Pass:1 DC_Notch:1 */
|
|
|
|
u8 m_AGC1_1; /* 0F :4 AGC1_Top:4 */
|
|
|
|
u8 m_AGC2_1; /* 0F :4 AGC2_Top:4 */
|
|
|
|
/*EF RF_AGC_Adapt:1 RF_AGC_Adapt_Top:2 :1 RF_Atten_3dB:1 RF_AGC_Top:3 */
|
|
|
|
u8 m_RF_AGC_1_Low;
|
|
|
|
/*EF RF_AGC_Adapt:1 RF_AGC_Adapt_Top:2 :1 RF_Atten_3dB:1 RF_AGC_Top:3 */
|
|
|
|
u8 m_RF_AGC_1_High;
|
|
|
|
u8 m_IR_MIXER_1; /* 0F :4 IR_mixer_Top:4 */
|
|
|
|
u8 m_AGC5_1; /* 1F :3 AGC5_Ana AGC5_Top:4 */
|
|
|
|
u8 m_AGCK_1; /* 0F :4 AGCK_Step:2 AGCK_Mode:2 */
|
|
|
|
u8 m_PSM_1; /* 20 :2 PSM_StoB:1 :5 */
|
|
|
|
bool m_AGC1_Freeze;
|
|
|
|
bool m_LTO_STO_immune;
|
|
|
|
};
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
static struct SStandardParams
|
|
|
|
m_StandardTable[HF_DVBC_8MHZ - HF_DVBT_6MHZ + 1] = {
|
|
|
|
{ 3250000, 6000000, 0x20, 0x03, 0x00, 0x07, 0x2B,
|
|
|
|
0x2C, 0x0B, 0x0B, 0x02, 0x20, false, false }, /* HF_DVBT_6MHZ */
|
|
|
|
{ 3500000, 7000000, 0x31, 0x01, 0x00, 0x07, 0x2B,
|
|
|
|
0x2C, 0x0B, 0x0B, 0x02, 0x20, false, false }, /* HF_DVBT_7MHZ */
|
|
|
|
{ 4000000, 8000000, 0x22, 0x01, 0x00, 0x07, 0x2B,
|
|
|
|
0x2C, 0x0B, 0x0B, 0x02, 0x20, false, false }, /* HF_DVBT_8MHZ */
|
|
|
|
{ 0000000, 0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, false, false }, /* HF_DVBT (Unused) */
|
|
|
|
{ 3250000, 6000000, 0x20, 0x03, 0x0A, 0x07, 0x6D,
|
|
|
|
0x6D, 0x0E, 0x0E, 0x02, 0x20, false, false }, /* HF_ATSC */
|
|
|
|
{ 3600000, 6000000, 0x10, 0x01, 0x00, 0x07, 0x83,
|
|
|
|
0x83, 0x0B, 0x0B, 0x02, 0x00, true , true }, /* HF_DVBC_6MHZ */
|
|
|
|
{ 5000000, 7000000, 0x93, 0x03, 0x00, 0x07, 0x83,
|
|
|
|
0x83, 0x0B, 0x0B, 0x02, 0x00, true , true },
|
|
|
|
/* HF_DVBC_7MHZ (not documented by NXP, use same settings as 8 MHZ) */
|
|
|
|
{ 5000000, 8000000, 0x43, 0x03, 0x00, 0x07, 0x83,
|
|
|
|
0x83, 0x0B, 0x0B, 0x02, 0x00, true , true }, /* HF_DVBC_8MHZ */
|
|
|
|
};
|
|
|
|
#else
|
|
|
|
static struct SStandardParams
|
|
|
|
m_StandardTable[HF_DVBC_8MHZ - HF_DVBT_6MHZ + 1] = {
|
|
|
|
{ 4000000, 6000000, 0x41, 0x03, 0x00, 0x07, 0x2B,
|
|
|
|
0x2C, 0x0B, 0x0B, 0x02, 0x20, false, false }, /* HF_DVBT_6MHZ */
|
|
|
|
{ 4500000, 7000000, 0x42, 0x03, 0x00, 0x07, 0x2B,
|
|
|
|
0x2C, 0x0B, 0x0B, 0x02, 0x20, false, false }, /* HF_DVBT_7MHZ */
|
|
|
|
{ 5000000, 8000000, 0x43, 0x03, 0x00, 0x07, 0x2B,
|
|
|
|
0x2C, 0x0B, 0x0B, 0x02, 0x20, false, false }, /* HF_DVBT_8MHZ */
|
|
|
|
/* ------------------------------ */
|
|
|
|
{ 0000000, 0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, false, false }, /* HF_DVBT (Unused)*/
|
|
|
|
{ 3250000, 6000000, 0x20, 0x03, 0x0A, 0x07, 0x6D,
|
|
|
|
0x6D, 0x0E, 0x0E, 0x02, 0x20, false, false }, /* HF_ATSC */
|
|
|
|
{ 3600000, 6000000, 0x10, 0x01, 0x00, 0x07, 0x83,
|
|
|
|
0x83, 0x0B, 0x0B, 0x02, 0x00, true , true }, /* HF_DVBC_6MHZ */
|
|
|
|
{ 5000000, 7000000, 0x93, 0x03, 0x00, 0x07, 0x83,
|
|
|
|
0x83, 0x0B, 0x0B, 0x02, 0x00, true , true },
|
|
|
|
/* HF_DVBC_7MHZ (not documented by NXP, use same settings as 8 MHZ) */
|
|
|
|
{ 5000000, 8000000, 0x43, 0x03, 0x00, 0x07, 0x83,
|
|
|
|
0x83, 0x0B, 0x0B, 0x02, 0x00, true , true }, /* HF_DVBC_8MHZ */
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
struct tda_state {
|
|
|
|
struct i2c_adapter *i2c;
|
|
|
|
u8 adr;
|
|
|
|
|
|
|
|
enum HF_Standard m_Standard;
|
|
|
|
u32 m_Frequency;
|
|
|
|
u32 IF;
|
|
|
|
|
|
|
|
bool m_isMaster;
|
|
|
|
bool m_bPowerMeasurement;
|
|
|
|
bool m_bLTEnable;
|
|
|
|
bool m_bEnableFreeze;
|
|
|
|
|
|
|
|
u16 m_ID;
|
|
|
|
|
|
|
|
s32 m_SettlingTime;
|
|
|
|
|
|
|
|
u8 m_IFLevelDVBC;
|
|
|
|
u8 m_IFLevelDVBT;
|
|
|
|
u8 Regs[REG_MAX];
|
|
|
|
u8 m_LastPowerLevel;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int i2c_readn(struct i2c_adapter *adapter, u8 adr, u8 *data, int len)
|
|
|
|
{
|
|
|
|
struct i2c_msg msgs[1] = {{.addr = adr, .flags = I2C_M_RD,
|
|
|
|
.buf = data, .len = len} };
|
|
|
|
return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int i2c_read(struct i2c_adapter *adap,
|
|
|
|
u8 adr, u8 *msg, int len, u8 *answ, int alen)
|
|
|
|
{
|
|
|
|
struct i2c_msg msgs[2] = { { .addr = adr, .flags = 0,
|
|
|
|
.buf = msg, .len = len},
|
|
|
|
{ .addr = adr, .flags = I2C_M_RD,
|
|
|
|
.buf = answ, .len = alen } };
|
|
|
|
if (i2c_transfer(adap, msgs, 2) != 2) {
|
|
|
|
pr_err("tda18212dd: i2c_read error\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int i2c_write(struct i2c_adapter *adap, u8 adr, u8 *data, int len)
|
|
|
|
{
|
|
|
|
struct i2c_msg msg = {.addr = adr, .flags = 0,
|
|
|
|
.buf = data, .len = len};
|
|
|
|
|
|
|
|
if (i2c_transfer(adap, &msg, 1) != 1) {
|
|
|
|
pr_err("tda18212: i2c_write error\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_regs(struct tda_state *state,
|
|
|
|
u8 SubAddr, u8 *Regs, u16 nRegs)
|
|
|
|
{
|
|
|
|
u8 data[REG_MAX + 1];
|
|
|
|
|
|
|
|
data[0] = SubAddr;
|
|
|
|
memcpy(data + 1, Regs, nRegs);
|
|
|
|
return i2c_write(state->i2c, state->adr, data, nRegs + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_reg(struct tda_state *state, u8 SubAddr, u8 Reg)
|
|
|
|
{
|
|
|
|
u8 msg[2] = {SubAddr, Reg};
|
|
|
|
|
|
|
|
return i2c_write(state->i2c, state->adr, msg, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int Read(struct tda_state *state, u8 *Regs)
|
|
|
|
{
|
|
|
|
return i2c_readn(state->i2c, state->adr, Regs, REG_MAX);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int update_regs(struct tda_state *state, u8 RegFrom, u8 RegTo)
|
|
|
|
{
|
|
|
|
return write_regs(state, RegFrom,
|
|
|
|
&state->Regs[RegFrom], RegTo-RegFrom + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int update_reg(struct tda_state *state, u8 Reg)
|
|
|
|
{
|
|
|
|
return write_reg(state, Reg, state->Regs[Reg]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int read_regs(struct tda_state *state,
|
|
|
|
u8 SubAddr, u8 *Regs, u16 nRegs)
|
|
|
|
{
|
|
|
|
return i2c_read(state->i2c, state->adr,
|
|
|
|
&SubAddr, 1, Regs, nRegs);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_reg(struct tda_state *state,
|
|
|
|
u8 SubAddr, u8 *Reg)
|
|
|
|
{
|
|
|
|
return i2c_read(state->i2c, state->adr,
|
|
|
|
&SubAddr, 1, Reg, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_reg1(struct tda_state *state, u8 Reg)
|
|
|
|
{
|
|
|
|
return read_reg(state, Reg, &state->Regs[Reg]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void init_state(struct tda_state *state)
|
|
|
|
{
|
|
|
|
u32 ulIFLevelDVBC = IF_LEVEL_DVBC;
|
|
|
|
u32 ulIFLevelDVBT = IF_LEVEL_DVBT;
|
|
|
|
u32 ulPowerMeasurement = 1;
|
|
|
|
u32 ulLTEnable = 1;
|
|
|
|
u32 ulEnableFreeze = 0;
|
|
|
|
|
|
|
|
state->m_Frequency = 0;
|
|
|
|
state->m_isMaster = true;
|
|
|
|
state->m_ID = 0;
|
|
|
|
state->m_LastPowerLevel = 0xFF;
|
|
|
|
state->m_IFLevelDVBC = (ulIFLevelDVBC & 0x07);
|
|
|
|
state->m_IFLevelDVBT = (ulIFLevelDVBT & 0x07);
|
|
|
|
state->m_bPowerMeasurement = (ulPowerMeasurement != 0);
|
|
|
|
state->m_bLTEnable = (ulLTEnable != 0);
|
|
|
|
state->m_bEnableFreeze = (ulEnableFreeze != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int StartCalibration(struct tda_state *state)
|
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
do {
|
|
|
|
state->Regs[POWER_2] &= ~0x02; /* RSSI CK = 31.25 kHz */
|
|
|
|
CHK_ERROR(update_reg(state, POWER_2));
|
|
|
|
|
|
|
|
/* AGC1 Do Step = 2 */
|
|
|
|
state->Regs[AGC1_2] = (state->Regs[AGC1_2] & ~0x60) | 0x40;
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_2)); /* AGC */
|
|
|
|
|
|
|
|
/* AGC2 Do Step = 1 */
|
|
|
|
state->Regs[RF_FILTER_3] =
|
|
|
|
(state->Regs[RF_FILTER_3] & ~0xC0) | 0x40;
|
|
|
|
CHK_ERROR(update_reg(state, RF_FILTER_3));
|
|
|
|
|
|
|
|
/* AGCs Assym Up Step = 3 // Datasheet sets all bits to 1! */
|
|
|
|
state->Regs[AGCK_1] |= 0xC0;
|
|
|
|
CHK_ERROR(update_reg(state, AGCK_1));
|
|
|
|
|
|
|
|
/* AGCs Assym Do Step = 2 */
|
|
|
|
state->Regs[AGC5_1] = (state->Regs[AGC5_1] & ~0x60) | 0x40;
|
|
|
|
CHK_ERROR(update_reg(state, AGC5_1));
|
|
|
|
|
|
|
|
state->Regs[IRQ_CLEAR] |= 0x80; /* Reset IRQ */
|
|
|
|
CHK_ERROR(update_reg(state, IRQ_CLEAR));
|
|
|
|
|
|
|
|
state->Regs[MSM_1] = 0x3B; /* Set Calibration */
|
|
|
|
state->Regs[MSM_2] = 0x01; /* Start MSM */
|
|
|
|
CHK_ERROR(update_regs(state, MSM_1, MSM_2));
|
|
|
|
state->Regs[MSM_2] = 0x00;
|
|
|
|
} while (0);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int FinishCalibration(struct tda_state *state)
|
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
u8 RFCal_Log[12];
|
|
|
|
|
|
|
|
do {
|
|
|
|
u8 IRQ = 0;
|
|
|
|
int Timeout = 150; /* 1.5 s */
|
|
|
|
while (true) {
|
|
|
|
CHK_ERROR(read_reg(state, IRQ_STATUS, &IRQ));
|
|
|
|
if ((IRQ & 0x80) != 0)
|
|
|
|
break;
|
|
|
|
Timeout -= 1;
|
|
|
|
if (Timeout == 0) {
|
|
|
|
status = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
usleep_range(10000, 12000);
|
|
|
|
}
|
|
|
|
CHK_ERROR(status);
|
|
|
|
|
|
|
|
state->Regs[FLO_MAX] = 0x0A;
|
|
|
|
CHK_ERROR(update_reg(state, FLO_MAX));
|
|
|
|
|
|
|
|
state->Regs[AGC1_1] &= ~0xC0;
|
|
|
|
if (state->m_bLTEnable)
|
|
|
|
state->Regs[AGC1_1] |= 0x80; /* LTEnable */
|
|
|
|
|
|
|
|
state->Regs[AGC1_1] |= (state->m_isMaster ?
|
|
|
|
MASTER_AGC1_6_15dB :
|
|
|
|
SLAVE_AGC1_6_15dB) << 6;
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_1));
|
|
|
|
|
|
|
|
state->Regs[PSM_1] &= ~0xC0;
|
|
|
|
state->Regs[PSM_1] |= (state->m_isMaster ?
|
|
|
|
MASTER_PSM_AGC1 : SLAVE_PSM_AGC1) << 6;
|
|
|
|
CHK_ERROR(update_reg(state, PSM_1));
|
|
|
|
|
|
|
|
state->Regs[REFERENCE] |= 0x03; /* XTOUT = 3 */
|
|
|
|
CHK_ERROR(update_reg(state, REFERENCE));
|
|
|
|
|
|
|
|
CHK_ERROR(read_regs(state, RFCAL_LOG_1,
|
|
|
|
RFCal_Log, sizeof(RFCal_Log)));
|
|
|
|
} while (0);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PowerOn(struct tda_state *state)
|
|
|
|
{
|
|
|
|
state->Regs[POWER_STATE_2] &= ~0x0F;
|
|
|
|
update_reg(state, POWER_STATE_2);
|
|
|
|
/* Digital clock source = Sigma Delta */
|
|
|
|
state->Regs[REFERENCE] |= 0x40;
|
|
|
|
update_reg(state, REFERENCE);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int Standby(struct tda_state *state)
|
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* Digital clock source = Quarz */
|
|
|
|
state->Regs[REFERENCE] &= ~0x40;
|
|
|
|
CHK_ERROR(update_reg(state, REFERENCE));
|
|
|
|
|
|
|
|
state->Regs[POWER_STATE_2] &= ~0x0F;
|
|
|
|
state->Regs[POWER_STATE_2] |= state->m_isMaster ? 0x08 : 0x0E;
|
|
|
|
CHK_ERROR(update_reg(state, POWER_STATE_2));
|
|
|
|
} while (0);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int attach_init(struct tda_state *state)
|
|
|
|
{
|
|
|
|
int stat = 0;
|
|
|
|
u8 Id[2];
|
|
|
|
u8 PowerState = 0x00;
|
|
|
|
|
|
|
|
state->m_Standard = HF_None;
|
|
|
|
|
|
|
|
/* first read after cold reset sometimes fails on some cards,
|
|
|
|
try twice */
|
|
|
|
stat = read_regs(state, ID_1, Id, sizeof(Id));
|
|
|
|
stat = read_regs(state, ID_1, Id, sizeof(Id));
|
|
|
|
if (stat < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
state->m_ID = ((Id[0] & 0x7F) << 8) | Id[1];
|
|
|
|
state->m_isMaster = ((Id[0] & 0x80) != 0);
|
|
|
|
if (!state->m_isMaster)
|
|
|
|
state->m_bLTEnable = false;
|
|
|
|
|
|
|
|
if (state->m_ID != 18212)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
stat = read_reg(state, POWER_STATE_1 , &PowerState);
|
|
|
|
if (stat < 0)
|
|
|
|
return stat;
|
2021-02-24 20:20:10 +01:00
|
|
|
|
2015-08-05 17:22:42 +02:00
|
|
|
if (state->m_isMaster) {
|
|
|
|
if (PowerState & 0x02) {
|
|
|
|
/* msleep for XTAL Calibration
|
|
|
|
(on a PC this should be long done) */
|
|
|
|
u8 IRQStatus = 0;
|
|
|
|
int Timeout = 10;
|
|
|
|
|
|
|
|
while (Timeout > 0) {
|
|
|
|
read_reg(state, IRQ_STATUS, &IRQStatus);
|
|
|
|
if (IRQStatus & 0x20)
|
|
|
|
break;
|
|
|
|
Timeout -= 1;
|
|
|
|
usleep_range(10000, 12000);
|
|
|
|
}
|
|
|
|
if ((IRQStatus & 0x20) == 0)
|
|
|
|
stat = -ETIMEDOUT;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
write_reg(state, FLO_MAX, 0x00);
|
|
|
|
write_reg(state, CP_CURRENT, 0x68);
|
|
|
|
}
|
|
|
|
Read(state, state->Regs);
|
|
|
|
|
|
|
|
PowerOn(state);
|
|
|
|
StartCalibration(state);
|
|
|
|
FinishCalibration(state);
|
|
|
|
Standby(state);
|
|
|
|
|
2015-09-19 22:20:35 +02:00
|
|
|
#if 0
|
2015-08-05 17:22:42 +02:00
|
|
|
{
|
|
|
|
u8 RFCal_Log[12];
|
|
|
|
|
|
|
|
read_regs(state, RFCAL_LOG_1, RFCal_Log, sizeof(RFCal_Log));
|
|
|
|
pr_info("RFCal Log: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
|
|
|
RFCal_Log[0], RFCal_Log[1],
|
|
|
|
RFCal_Log[2], RFCal_Log[3],
|
|
|
|
RFCal_Log[4], RFCal_Log[5],
|
|
|
|
RFCal_Log[6], RFCal_Log[7],
|
|
|
|
RFCal_Log[8], RFCal_Log[9],
|
|
|
|
RFCal_Log[10], RFCal_Log[11]);
|
|
|
|
}
|
2015-09-19 22:20:35 +02:00
|
|
|
#endif
|
2015-08-05 17:22:42 +02:00
|
|
|
return stat;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PowerMeasurement(struct tda_state *state, u8 *pPowerLevel)
|
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
|
|
|
|
do {
|
|
|
|
u8 IRQ = 0;
|
|
|
|
int Timeout = 70; /* 700 ms */
|
|
|
|
|
|
|
|
state->Regs[IRQ_CLEAR] |= 0x80; /* Reset IRQ */
|
|
|
|
CHK_ERROR(update_reg(state, IRQ_CLEAR));
|
|
|
|
|
|
|
|
state->Regs[MSM_1] = 0x80; /* power measurement */
|
|
|
|
state->Regs[MSM_2] = 0x01; /* Start MSM */
|
|
|
|
CHK_ERROR(update_regs(state, MSM_1, MSM_2));
|
|
|
|
state->Regs[MSM_2] = 0x00;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
CHK_ERROR(read_reg(state, IRQ_STATUS, &IRQ));
|
|
|
|
if ((IRQ & 0x80) != 0)
|
|
|
|
break;
|
|
|
|
Timeout -= 1;
|
|
|
|
if (Timeout == 0) {
|
|
|
|
status = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
usleep_range(10000, 12000);
|
|
|
|
}
|
|
|
|
CHK_ERROR(status);
|
|
|
|
|
|
|
|
CHK_ERROR(read_reg1(state, INPUT_POWER_LEVEL));
|
|
|
|
*pPowerLevel = state->Regs[INPUT_POWER_LEVEL] & 0x7F;
|
|
|
|
|
|
|
|
if (*pPowerLevel > 110)
|
|
|
|
*pPowerLevel = 110;
|
|
|
|
} while (0);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int SetFrequency(struct tda_state *state, u32 Frequency,
|
|
|
|
enum HF_Standard Standard)
|
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
struct SStandardParams *StandardParams;
|
|
|
|
u32 f = Frequency / 1000;
|
|
|
|
u8 IRQ = 0;
|
|
|
|
int Timeout = 25; /* 250 ms */
|
|
|
|
u32 fRatio = Frequency / 16000000;
|
|
|
|
u32 fDelta = Frequency - fRatio * 16000000;
|
|
|
|
|
|
|
|
if (Standard < HF_DVBT_6MHZ || Standard > HF_DVBC_8MHZ)
|
|
|
|
return -EINVAL;
|
|
|
|
StandardParams = &m_StandardTable[Standard - HF_DVBT_6MHZ];
|
|
|
|
|
|
|
|
if (StandardParams->m_IFFrequency == 0)
|
|
|
|
return -EINVAL;
|
|
|
|
state->m_Standard = HF_None;
|
|
|
|
state->m_Frequency = 0;
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* IF Level */
|
|
|
|
state->Regs[IF_AGC] = (Standard >= HF_DVBC_6MHZ) ?
|
|
|
|
state->m_IFLevelDVBC : state->m_IFLevelDVBT;
|
|
|
|
CHK_ERROR(update_reg(state, IF_AGC));
|
|
|
|
|
|
|
|
/* Standard setup */
|
|
|
|
state->Regs[IF_1] = StandardParams->m_IF_1;
|
|
|
|
CHK_ERROR(update_reg(state, IF_1));
|
|
|
|
|
|
|
|
state->Regs[IR_MIXER_2] = (state->Regs[IR_MIXER_2] & ~0x03) |
|
|
|
|
StandardParams->m_IR_MIXER_2;
|
|
|
|
CHK_ERROR(update_reg(state, IR_MIXER_2));
|
|
|
|
|
|
|
|
state->Regs[AGC1_1] = (state->Regs[AGC1_1] & ~0x0F) |
|
|
|
|
StandardParams->m_AGC1_1;
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_1));
|
|
|
|
|
|
|
|
state->Regs[AGC2_1] = (state->Regs[AGC2_1] & ~0x0F) |
|
|
|
|
StandardParams->m_AGC2_1;
|
|
|
|
CHK_ERROR(update_reg(state, AGC2_1));
|
|
|
|
|
|
|
|
state->Regs[RF_AGC_1] &= ~0xEF;
|
|
|
|
if (Frequency < 291000000)
|
|
|
|
state->Regs[RF_AGC_1] |= StandardParams->m_RF_AGC_1_Low;
|
|
|
|
else
|
|
|
|
state->Regs[RF_AGC_1] |=
|
|
|
|
StandardParams->m_RF_AGC_1_High;
|
|
|
|
CHK_ERROR(update_reg(state, RF_AGC_1));
|
|
|
|
|
|
|
|
state->Regs[IR_MIXER_1] =
|
|
|
|
(state->Regs[IR_MIXER_1] & ~0x0F) |
|
|
|
|
StandardParams->m_IR_MIXER_1;
|
|
|
|
CHK_ERROR(update_reg(state, IR_MIXER_1));
|
|
|
|
|
|
|
|
state->Regs[AGC5_1] = (state->Regs[AGC5_1] & ~0x1F) |
|
|
|
|
StandardParams->m_AGC5_1;
|
|
|
|
CHK_ERROR(update_reg(state, AGC5_1));
|
|
|
|
|
|
|
|
state->Regs[AGCK_1] = (state->Regs[AGCK_1] & ~0x0F) |
|
|
|
|
StandardParams->m_AGCK_1;
|
|
|
|
CHK_ERROR(update_reg(state, AGCK_1));
|
|
|
|
|
|
|
|
state->Regs[PSM_1] = (state->Regs[PSM_1] & ~0x20) |
|
|
|
|
StandardParams->m_PSM_1;
|
|
|
|
CHK_ERROR(update_reg(state, PSM_1));
|
|
|
|
|
|
|
|
state->Regs[IF_FREQUENCY_1] = (StandardParams->m_IFFrequency /
|
|
|
|
50000);
|
|
|
|
CHK_ERROR(update_reg(state, IF_FREQUENCY_1));
|
|
|
|
|
|
|
|
if (state->m_isMaster && StandardParams->m_LTO_STO_immune) {
|
|
|
|
u8 tmp;
|
|
|
|
u8 RF_Filter_Gain;
|
|
|
|
|
|
|
|
CHK_ERROR(read_reg(state, RF_AGC_GAIN_1, &tmp));
|
|
|
|
RF_Filter_Gain = (tmp & 0x30) >> 4;
|
|
|
|
|
|
|
|
state->Regs[RF_FILTER_1] =
|
|
|
|
(state->Regs[RF_FILTER_1] & ~0x0C) |
|
|
|
|
(RF_Filter_Gain << 2);
|
|
|
|
CHK_ERROR(update_reg(state, RF_FILTER_1));
|
|
|
|
|
|
|
|
state->Regs[RF_FILTER_1] |= 0x10; /* Force */
|
|
|
|
CHK_ERROR(update_reg(state, RF_FILTER_1));
|
|
|
|
|
|
|
|
while (RF_Filter_Gain != 0) {
|
|
|
|
RF_Filter_Gain -= 1;
|
|
|
|
state->Regs[RF_FILTER_1] =
|
|
|
|
(state->Regs[RF_FILTER_1] & ~0x0C) |
|
|
|
|
(RF_Filter_Gain << 2);
|
|
|
|
CHK_ERROR(update_reg(state, RF_FILTER_1));
|
|
|
|
usleep_range(10000, 12000);
|
|
|
|
}
|
|
|
|
CHK_ERROR(status);
|
|
|
|
|
|
|
|
state->Regs[RF_AGC_1] |= 0x08;
|
|
|
|
CHK_ERROR(update_reg(state, RF_AGC_1));
|
|
|
|
}
|
|
|
|
|
|
|
|
state->Regs[IRQ_CLEAR] |= 0x80; /* Reset IRQ */
|
|
|
|
CHK_ERROR(update_reg(state, IRQ_CLEAR));
|
|
|
|
|
|
|
|
CHK_ERROR(PowerOn(state));
|
|
|
|
|
|
|
|
state->Regs[RF_FREQUENCY_1] = ((f >> 16) & 0xFF);
|
|
|
|
state->Regs[RF_FREQUENCY_2] = ((f >> 8) & 0xFF);
|
|
|
|
state->Regs[RF_FREQUENCY_3] = (f & 0xFF);
|
|
|
|
CHK_ERROR(update_regs(state, RF_FREQUENCY_1, RF_FREQUENCY_3));
|
|
|
|
|
|
|
|
state->Regs[MSM_1] = 0x41; /* Tune */
|
|
|
|
state->Regs[MSM_2] = 0x01; /* Start MSM */
|
|
|
|
CHK_ERROR(update_regs(state, MSM_1, MSM_2));
|
|
|
|
state->Regs[MSM_2] = 0x00;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
CHK_ERROR(read_reg(state, IRQ_STATUS, &IRQ));
|
|
|
|
if ((IRQ & 0x80) != 0)
|
|
|
|
break;
|
|
|
|
Timeout -= 1;
|
|
|
|
if (Timeout == 0) {
|
|
|
|
status = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
usleep_range(10000, 12000);
|
|
|
|
}
|
|
|
|
CHK_ERROR(status);
|
|
|
|
|
|
|
|
if (state->m_isMaster && StandardParams->m_LTO_STO_immune) {
|
|
|
|
state->Regs[RF_AGC_1] &= ~0x08;
|
|
|
|
CHK_ERROR(update_reg(state, RF_AGC_1));
|
|
|
|
|
|
|
|
msleep(50);
|
|
|
|
|
|
|
|
state->Regs[RF_FILTER_1] &= ~0x10; /* remove force */
|
|
|
|
CHK_ERROR(update_reg(state, RF_FILTER_1));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Spur reduction */
|
|
|
|
|
|
|
|
if (Frequency < 72000000)
|
|
|
|
state->Regs[REFERENCE] |= 0x40; /* Set digital clock */
|
|
|
|
else if (Frequency < 104000000)
|
|
|
|
state->Regs[REFERENCE] &= ~0x40; /*Clear digital clock*/
|
|
|
|
else if (Frequency < 120000000)
|
|
|
|
state->Regs[REFERENCE] |= 0x40; /* Set digital clock */
|
|
|
|
else {
|
|
|
|
if (fDelta <= 8000000) {
|
|
|
|
/* Clear or set digital clock */
|
|
|
|
if (fRatio & 1)
|
|
|
|
state->Regs[REFERENCE] &= ~0x40;
|
|
|
|
else
|
|
|
|
state->Regs[REFERENCE] |= 0x40;
|
|
|
|
} else {
|
|
|
|
/* Set or clear digital clock */
|
|
|
|
if (fRatio & 1)
|
|
|
|
state->Regs[REFERENCE] |= 0x40;
|
|
|
|
else
|
|
|
|
state->Regs[REFERENCE] &= ~0x40;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
CHK_ERROR(update_reg(state, REFERENCE));
|
|
|
|
|
|
|
|
if (StandardParams->m_AGC1_Freeze && state->m_bEnableFreeze) {
|
|
|
|
u8 tmp;
|
|
|
|
int AGC1GainMin = 0;
|
|
|
|
int nSteps = 10;
|
|
|
|
int Step = 0;
|
|
|
|
|
|
|
|
CHK_ERROR(read_reg(state, AGC1_2, &tmp));
|
|
|
|
if ((tmp & 0x80) == 0) {
|
|
|
|
state->Regs[AGC1_2] |= 0x80; /* Loop off */
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_2));
|
|
|
|
state->Regs[AGC1_2] |= 0x10; /* Force gain */
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_2));
|
|
|
|
}
|
|
|
|
/* Adapt */
|
|
|
|
if (state->Regs[AGC1_1] & 0x40) { /* AGC1_6_15dB set */
|
|
|
|
AGC1GainMin = 6;
|
|
|
|
nSteps = 4;
|
|
|
|
}
|
|
|
|
while (Step < nSteps) {
|
|
|
|
int Down = 0;
|
|
|
|
int Up = 0, i;
|
|
|
|
u8 AGC1_Gain;
|
|
|
|
|
|
|
|
Step = Step + 1;
|
|
|
|
|
|
|
|
for (i = 0; i < 40; i += 1) {
|
|
|
|
CHK_ERROR(read_reg(state, AGC_DET_OUT,
|
|
|
|
&tmp));
|
|
|
|
Up += (tmp & 0x02) ? 1 : -4;
|
|
|
|
Down += (tmp & 0x01) ? 14 : -1;
|
|
|
|
usleep_range(1000, 2000);
|
|
|
|
}
|
|
|
|
CHK_ERROR(status);
|
|
|
|
AGC1_Gain = (state->Regs[AGC1_2] & 0x0F);
|
|
|
|
if (Up >= 15 && AGC1_Gain != 9) {
|
|
|
|
state->Regs[AGC1_2] =
|
|
|
|
(state->Regs[AGC1_2] & ~0x0F) |
|
|
|
|
(AGC1_Gain + 1);
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_2));
|
|
|
|
} else if (Down >= 10 &&
|
|
|
|
AGC1_Gain != AGC1GainMin) {
|
|
|
|
state->Regs[AGC1_2] =
|
|
|
|
(state->Regs[AGC1_2] & ~0x0F) |
|
|
|
|
(AGC1_Gain - 1);
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_2));
|
|
|
|
} else
|
|
|
|
Step = nSteps;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state->Regs[AGC1_2] &= ~0x10; /* unforce gain */
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_2));
|
|
|
|
state->Regs[AGC1_2] &= ~0x80; /* Loop on */
|
|
|
|
CHK_ERROR(update_reg(state, AGC1_2));
|
|
|
|
}
|
|
|
|
|
|
|
|
state->m_Standard = Standard;
|
|
|
|
state->m_Frequency = Frequency;
|
|
|
|
|
|
|
|
if (state->m_bPowerMeasurement)
|
|
|
|
PowerMeasurement(state, &state->m_LastPowerLevel);
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sleep(struct dvb_frontend *fe)
|
|
|
|
{
|
|
|
|
struct tda_state *state = fe->tuner_priv;
|
|
|
|
|
|
|
|
Standby(state);
|
2015-09-24 23:29:40 +02:00
|
|
|
write_reg(state, THERMO_2, 0x01);
|
|
|
|
read_reg1(state, THERMO_1);
|
|
|
|
write_reg(state, THERMO_2, 0x00);
|
2015-12-21 13:48:02 +01:00
|
|
|
/* printk("sleep: temp = %u\n", state->Regs[THERMO_1]); */
|
2015-08-05 17:22:42 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int init(struct dvb_frontend *fe)
|
|
|
|
{
|
|
|
|
/* struct tda_state *state = fe->tuner_priv; */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-09-26 21:18:04 +02:00
|
|
|
static void release(struct dvb_frontend *fe)
|
2015-08-05 17:22:42 +02:00
|
|
|
{
|
|
|
|
kfree(fe->tuner_priv);
|
|
|
|
fe->tuner_priv = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_params(struct dvb_frontend *fe)
|
|
|
|
{
|
|
|
|
struct tda_state *state = fe->tuner_priv;
|
|
|
|
struct dtv_frontend_properties *p = &fe->dtv_property_cache;
|
|
|
|
int status = 0;
|
|
|
|
int Standard;
|
|
|
|
u32 bw;
|
|
|
|
|
|
|
|
bw = (p->bandwidth_hz + 999999) / 1000000;
|
|
|
|
state->m_Frequency = p->frequency;
|
|
|
|
if (p->delivery_system == SYS_DVBT ||
|
|
|
|
p->delivery_system == SYS_DVBT2 ||
|
|
|
|
p->delivery_system == SYS_ISDBT ||
|
|
|
|
p->delivery_system == SYS_DVBC2) {
|
|
|
|
switch (bw) {
|
|
|
|
case 6:
|
|
|
|
Standard = HF_DVBT_6MHZ;
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
Standard = HF_DVBT_7MHZ;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
case 8:
|
|
|
|
Standard = HF_DVBT_8MHZ;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (p->delivery_system == SYS_DVBC_ANNEX_A) {
|
|
|
|
switch (bw) {
|
|
|
|
case 6:
|
|
|
|
Standard = HF_DVBC_6MHZ;
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
Standard = HF_DVBC_7MHZ;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
case 8:
|
|
|
|
Standard = HF_DVBC_8MHZ;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (fe->ops.i2c_gate_ctrl)
|
|
|
|
fe->ops.i2c_gate_ctrl(fe, 1);
|
|
|
|
SetFrequency(state, state->m_Frequency, Standard);
|
|
|
|
if (fe->ops.i2c_gate_ctrl)
|
|
|
|
fe->ops.i2c_gate_ctrl(fe, 0);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_frequency(struct dvb_frontend *fe, u32 *frequency)
|
|
|
|
{
|
|
|
|
struct tda_state *state = fe->tuner_priv;
|
|
|
|
|
|
|
|
*frequency = state->IF;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_rf_strength(struct dvb_frontend *fe, u16 *st)
|
|
|
|
{
|
|
|
|
struct tda_state *state = fe->tuner_priv;
|
|
|
|
|
|
|
|
*st = state->m_LastPowerLevel;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_if(struct dvb_frontend *fe, u32 *frequency)
|
|
|
|
{
|
|
|
|
struct tda_state *state = fe->tuner_priv;
|
|
|
|
|
|
|
|
state->IF = 0;
|
|
|
|
if (state->m_Standard < HF_DVBT_6MHZ ||
|
|
|
|
state->m_Standard > HF_DVBC_8MHZ)
|
|
|
|
return 0;
|
|
|
|
state->IF = m_StandardTable[state->m_Standard -
|
|
|
|
HF_DVBT_6MHZ].m_IFFrequency;
|
|
|
|
*frequency = state->IF;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
|
|
|
|
{
|
|
|
|
/* struct tda_state *state = fe->tuner_priv; */
|
|
|
|
/* *bandwidth = priv->bandwidth; */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct dvb_tuner_ops tuner_ops = {
|
|
|
|
.info = {
|
|
|
|
.name = "NXP TDA18212",
|
2020-08-29 15:32:42 +02:00
|
|
|
.frequency_min_hz = 47125000,
|
|
|
|
.frequency_max_hz = 865000000,
|
|
|
|
.frequency_step_hz = 62500
|
2015-08-05 17:22:42 +02:00
|
|
|
},
|
|
|
|
.init = init,
|
|
|
|
.sleep = sleep,
|
|
|
|
.set_params = set_params,
|
|
|
|
.release = release,
|
|
|
|
.get_frequency = get_frequency,
|
|
|
|
.get_if_frequency = get_if,
|
|
|
|
.get_bandwidth = get_bandwidth,
|
|
|
|
.get_rf_strength = get_rf_strength,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct dvb_frontend *tda18212dd_attach(struct dvb_frontend *fe,
|
|
|
|
struct i2c_adapter *i2c, u8 adr)
|
|
|
|
{
|
|
|
|
struct tda_state *state;
|
|
|
|
int stat;
|
|
|
|
|
|
|
|
state = kzalloc(sizeof(struct tda_state), GFP_KERNEL);
|
|
|
|
if (!state)
|
|
|
|
return NULL;
|
|
|
|
state->adr = adr;
|
|
|
|
state->i2c = i2c;
|
|
|
|
memcpy(&fe->ops.tuner_ops, &tuner_ops, sizeof(struct dvb_tuner_ops));
|
|
|
|
init_state(state);
|
|
|
|
|
|
|
|
if (fe->ops.i2c_gate_ctrl)
|
|
|
|
fe->ops.i2c_gate_ctrl(fe, 1);
|
|
|
|
stat = attach_init(state);
|
|
|
|
if (fe->ops.i2c_gate_ctrl)
|
|
|
|
fe->ops.i2c_gate_ctrl(fe, 0);
|
|
|
|
if (stat < 0) {
|
|
|
|
kfree(state);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
fe->tuner_priv = state;
|
|
|
|
return fe;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(tda18212dd_attach);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("TDA18212 driver");
|
|
|
|
MODULE_AUTHOR("Ralph Metzler, Manfred Voelkel");
|
2018-03-22 19:32:17 +01:00
|
|
|
MODULE_LICENSE("GPL v2");
|
2015-08-05 17:22:42 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Local variables:
|
|
|
|
* c-basic-offset: 8
|
|
|
|
* End:
|
|
|
|
*/
|