@@ -172,4 +172,11 @@
help
Say Y here to support the Freescale MC44S803 based tuners
+config MEDIA_TUNER_ADMTV102
+ tristate "Analog Device MTV 102 silicon tuner"
+ depends on VIDEO_MEDIA && I2C
+ default m if MEDIA_TUNER_CUSTOMIZE
+ help
+ Say Y here to support the Analog Device MTV 102 silicon tuner
+
endif # MEDIA_TUNER_CUSTOMISE
@@ -23,6 +23,7 @@
obj-$(CONFIG_MEDIA_TUNER_MXL5005S) += mxl5005s.o
obj-$(CONFIG_MEDIA_TUNER_MXL5007T) += mxl5007t.o
obj-$(CONFIG_MEDIA_TUNER_MC44S803) += mc44s803.o
+obj-$(CONFIG_MEDIA_TUNER_ADMTV102) += admtv102.o
EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core
EXTRA_CFLAGS += -Idrivers/media/dvb/frontends
@@ -0,0 +1,728 @@
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include "dvb_frontend.h"
+
+#include "admtv102.h"
+#include "admtv102_priv.h"
+
+/* define the register address and data of Tuner that
+ need to be initialized when power on */
+static const u8 init_data_uhf[100] = {
+/*Addr Data */
+ 0x10, 0x6A, /* LNA current, 0x4C will degrade 1 dB performance but get
+ better power consumption. */
+ 0x11, 0xD8, /* Mixer current */
+ 0x12, 0xC0, /* Mixer gain */
+ 0x15, 0x3D, /* TOP and ADJ to optimize SNR and ACI */
+ 0x17, 0x97, /* TOP and ADJ to optimize SNR and ACI 9A->97,Changed TOP
+ and ADJ to optimize SNR and ACI */
+ 0x18, 0x03, /* Changed power detector saturation voltage point and
+ warning voltage point */ /* new insert */
+ 0x1F, 0x17, /* VCOSEL,PLLF RFPGA amp current */
+ 0x20, 0xFF, /* RFPGA amp current */
+ 0x21, 0xA4, /* RFPGA amp current */
+ 0x22, 0xA4, /* RFPGA amp current */
+ 0x23, 0xDF, /* Mixer Bias control */
+ 0x26, 0xFA, /* PLL BUFFER CURRENT */
+ 0x27, 0x00, /* CONVCOL/H for VCO current control */
+ 0x28, 0xFF, /* CONVCOBUFL/H for VCO buffer amplifier current control */
+ 0x29, 0xFF, /* CONDIV1/2 for first and second divider current control */
+ 0x2A, 0xFF, /* CONDIV3/4 for third and last divider current control */
+ 0x2B, 0xE7, /* CONDIV5 for third and last divider current control */
+ 0X2C, 0xFF, /* CONBUF0/1 for L-Band Buffer amp and first Buffer amp
+ current control */
+ 0x2E, 0xFB, /* CONBUF4 for forth Buffer amp current control */
+ 0x30, 0x80, /* LFSW(Internal Loop Filter) to improve phase noise F8->80 */
+ 0x32, 0xc2, /* LOOP filter boundary */
+ 0x33, 0x80, /* DC offset control */
+ 0x34, 0xEC, /* DC offset control */
+ 0x39, 0x96, /* AGCHM AGC compensation value when LNA changes */
+ 0x3A, 0xA0, /* AGCHM AGC compensation value when LNA changes */
+ 0x3B, 0x05, /* AGCHM AGC compensation value when LNA changes */
+ 0x3C, 0xD0, /* AGCHM AGC compensation value when LNA changes */
+ 0x44, 0xDF, /* BBPGA needs to stay on for current silicon revision */
+ 0x48, 0x23, /* current for output buffer amp 23->21 */
+ 0x49, 0x08, /* gain mode for output buffer amp */
+ 0x4A, 0xA0, /* trip point for BBVGA */
+ 0x4B, 0x9D, /* trip point for RFPGA */
+ 0x4C, 0x9D, /* ADJRSSI warning point */
+ 0x4D, 0xC3, /* PLL current for stability PLL lock */
+ 0xff, 0xff
+};
+
+static const u8 init_data_vhf[100] = {
+/* Addr Data */
+ 0x10, 0x08, /* LNA current */
+ 0x11, 0xc2, /* Mixer current */
+ 0x12, 0xC0, /* Mixer gain */
+ 0x17, 0x98, /* TOP and ADJ to optimize SNR and ACI 9A->98 */
+ 0x1F, 0x17, /* VCOSEL,PLLF RFPGA amp current */
+ 0x20, 0x9b, /* RFPGA amp current */
+ 0x21, 0xA4, /* RFPGA amp current */
+ 0x22, 0xA4, /* RFPGA amp current */
+ 0x23, 0x9F, /* Mixer Bias control */
+ 0x26, 0xF9, /* PLL BUFFER CURRENT */
+ 0x27, 0x11, /* CONVCOL/H for VCO current control */
+ 0x28, 0x92, /* CONVCOBUFL/H for VCO buffer amplifier current control */
+ 0x29, 0xBC, /* CONDIV1/2 for first and second divider current control */
+ 0x2B, 0xE7, /* CONDIV5 for third and last divider current control */
+ 0x2D, 0x9C,
+ 0x2E, 0xCE, /* CONBUF4 for forth Buffer amp current control */
+ 0x2F, 0x1F,
+ 0x30, 0x80, /* LFSW(Internal Loop Filter) to improve phase noise */
+ 0x32, 0xc2, /* LOOP filter boundary */
+ 0x33, 0x80, /* DC offset control */
+ 0x34, 0xEC, /* DC offset control */
+ 0x48, 0x29, /* current for output buffer amp */
+ 0x49, 0x08, /* gain mode for output buffer amp */
+ 0x4A, 0xA0, /* trip point for BBVGA */
+ 0x4B, 0x9D, /* trip point for RFPGA */
+ 0x4C, 0x9D, /* ADJRSSI warning point */
+ 0x4D, 0xC3, /* PLL current for stability PLL lock */
+ 0xff, 0xff
+};
+
+static const u8 pll_reg_tab[10][3] = {
+ /* 0x24, 0x31, 0x38 */
+ {0x0F, 0x04, 0x50}, /* 13MHz //0x24: 0xnB-> 0xnF */
+ {0x1F, 0x04, 0x50}, /* 16.384MHz */
+ {0x2F, 0x04, 0x50}, /* 19.2MHz */
+ {0x3F, 0x04, 0x50}, /* 20.48MHz */
+ {0x4F, 0x15, 0x51}, /* 24.576MHz */
+ {0x5F, 0x15, 0x51}, /* 26MHz */
+ {0x5A, 0x04, 0x51}, /* 30.4MHz *//* special set for 30.4MHz case */
+ {0x6F, 0x15, 0x51}, /* 36MHz */
+ {0x7F, 0x15, 0x51}, /* 38.4MHz */
+ {0x3F, 0x04, 0x50}, /* 20MHz */
+};
+
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
+
+#define dprintk(args...) do { \
+ if (debug) { \
+ printk(KERN_DEBUG "admtv102: " args); \
+ printk("\n"); } \
+ } while (0)
+
+/* Reads a single register */
+static int admtv102_readreg(struct admtv102_priv *priv, u8 reg, u8 *val)
+{
+ struct i2c_msg msg[2] = {
+ { .addr = priv->cfg->i2c_address, .flags = 0,
+ .buf = ®, .len = 1 },
+ { .addr = priv->cfg->i2c_address, .flags = I2C_M_RD,
+ .buf = val, .len = 1 },
+ };
+
+ if (i2c_transfer(priv->i2c, msg, 2) != 2) {
+ printk(KERN_WARNING "admtv102 I2C read failed\n");
+ return -EREMOTEIO;
+ }
+ if (debug >= 2)
+ printk(KERN_DEBUG "%s(reg=0x%02X) : 0x%02X\n",
+ __func__, reg, *val);
+ return 0;
+}
+
+/* Writes a single register */
+static int admtv102_writereg(struct admtv102_priv *priv, u8 reg, u8 val)
+{
+ u8 buf[2] = { reg, val };
+ struct i2c_msg msg = {
+ .addr = priv->cfg->i2c_address, .flags = 0, .buf = buf, .len = 2
+ };
+
+ if (i2c_transfer(priv->i2c, &msg, 1) != 1) {
+ printk(KERN_WARNING "admtv102 I2C write failed\n");
+ return -EREMOTEIO;
+ }
+ if (debug >= 2)
+ printk(KERN_DEBUG "%s(reg=0x%02X, val=0x%02X)\n",
+ __func__, reg, val);
+ return 0;
+}
+
+/* Writes a set of consecutive registers */
+static int admtv102_writeregs(struct admtv102_priv *priv, u8 *buf, u8 len)
+{
+ struct i2c_msg msg = {
+ .addr = priv->cfg->i2c_address, .flags = 0,
+ .buf = buf, .len = len
+ };
+ if (i2c_transfer(priv->i2c, &msg, 1) != 1) {
+ printk(KERN_WARNING "admtv102 I2C write failed (len=%i)\n",
+ (int)len);
+ return -EREMOTEIO;
+ }
+ return 0;
+}
+
+void config_tuner(struct admtv102_priv *state, const u8 *addrdata)
+{
+ int i = 0;
+ u8 addr, data;
+
+ while (addrdata[i] != 0xFF) {
+ addr = addrdata[i++];
+ data = addrdata[i++];
+ admtv102_writereg(state, addr, data);
+ }
+}
+
+/*
+ Function: Set Tuner LPF configuration
+ Input:
+ ref_clk_type -- Tuner PLL Type
+ lpfBW -- Band width , in MHz unit
+*/
+void set_lpf(struct admtv102_priv *state, u32 ref_clk_type, u8 lpfBW)
+{
+ u8 tuneval;
+ u8 t;
+ int ret;
+
+ admtv102_writereg(state, 0x15, (u8)(0x38 | ((lpfBW-3)&0x07)));
+ admtv102_writereg(state, 0x25 , (_EXTUNEOFF << 2) | (_TUNEEN<<1));
+ msleep(10);
+
+ tuneval = 0x10; /* default value */
+ ret = admtv102_readreg(state, 0x0F, &t);
+ if (ret == 0)
+ tuneval = t;
+
+ /* change Tuning mode : auto-tune => manual tune(hold mode). */
+ admtv102_writereg(state, 0x25 , (_EXTUNEON << 2) | (_TUNEEN << 1));
+
+ if (ref_clk_type == ADMTV102_REFCLK30400) {
+ /* Write CTUNE val. in order to store tuned value. */
+ admtv102_writereg(state, 0x25,
+ (u8)(((tuneval+state->ctune_clkofs)<<3) |
+ (_EXTUNEON << 2) | (_TUNEEN << 1))
+ );
+ } else {
+ /* Write CTUNE val. in order to store tuned value. */
+ admtv102_writereg(state, 0x25,
+ (u8)((tuneval<<3) | (_EXTUNEON << 2) |
+ (_TUNEEN << 1))
+ );
+ }
+
+ return;
+}
+
+/*
+ Tuner PLL Register Setting.
+ reg_dat -- reg set value table
+*/
+void pll_reg_set(struct admtv102_priv *state, const u8 *reg_dat, int pll_type)
+{
+ int i = 0;
+ u8 data, splitid;
+
+ if (ADMTV102_REFCLK30400 == pll_type) {
+ admtv102_readreg(state, 0x00, &splitid);
+
+ if (0x0E == splitid) {
+ /* 0x5A */
+ data = REFCLK30400_CLKSEL_REG_SPLITID0E;
+ } else if (0x0F == splitid) {
+ /* 0x6A, for mass product */
+ data = REFCLK30400_CLKSEL_REG_SPLITID0F;
+ } else
+ data = reg_dat[i];
+
+ admtv102_writereg(state, 0x24, data);
+ i++;
+ } else {
+ data = reg_dat[i++];
+ admtv102_writereg(state, 0x24, data);
+ }
+
+ data = reg_dat[i++];
+ admtv102_writereg(state, 0x31, data);
+
+ data = reg_dat[i++];
+ admtv102_writereg(state, 0x38, data);
+}
+
+/* Distinguish Tuner Chip type by reading SplidID */
+int get_tuner_type(struct admtv102_priv *state)
+{
+ u8 splitid, type;
+
+ admtv102_readreg(state, 0x00, &splitid);
+
+ state->ctune_clkofs = CTUNE_CLKOFS_SPLIT0E;
+ switch (splitid) {
+ case 0x0E:
+ state->ctune_clkofs = CTUNE_CLKOFS_SPLIT0E;
+ type = TUNER_ADMTV102;
+ break;
+ case 0x0F: /* for mass product version */
+ state->ctune_clkofs = CTUNE_CLKOFS_SPLIT0F;
+ type = TUNER_ADMTV102;
+ break;
+ case 0x08:
+ case 0x0A:
+ type = TUNER_MTV102;
+ break;
+ default:
+ type = TUNER_NEWMTV102;
+ }
+
+ return type;
+}
+
+void tuner_init(struct admtv102_priv *state)
+{
+ int pll_type = state->cfg->ref_clk_type;
+
+ get_tuner_type(state);
+ state->icp = 0;
+ state->convco = 0;
+ state->curTempState = HIGH_TEMP;
+ if (pll_type < 0 || pll_type > 9)
+ pll_type = 1;
+ if (state->vhf_set == VHF_SUPPORT)
+ config_tuner(state, &init_data_vhf[0]);
+ else
+ config_tuner(state, &init_data_uhf[0]);
+
+ pll_reg_set(state, &pll_reg_tab[pll_type][0], pll_type);
+ set_lpf(state, pll_type, 8);
+}
+
+/*
+ Function: frequency setting for ADMTV102
+ Input:
+ frequency : Tuner center frequency in MHz
+ lpf_bw : Channel Bandwidth in MHz (default: 8- 8MHZ)
+ ref_clk_type: Tuner PLL reference clock type
+ frequency setting formula
+ LOfrequency=(Clockfrequency/PLLR*(PLLN+PLLF/2^20))/PLLS;
+*/
+void set_rf_freq(struct admtv102_priv *state, u32 frequency, u32 lpf_bw)
+{
+ u32 mtv10x_refclk;
+ u32 pll_freq, Freq;
+ u32 div_sel = 0, vco_sel = 0;
+ u8 pc4 = 0, pc8_16 = 0, data47, temper;
+ u32 seg_num;
+ u8 pll_r;
+ u32 lofreq;
+ u32 pll_n, pll_f, tmp;
+ u16 multi_factor, sub_exp, div_1, div_2;
+
+ dprintk("%s(freq=%d, bw=%d)\n", __func__, frequency, lpf_bw);
+
+ state->frequency = frequency;
+ /* judge if the vhf_set has conflict with frequency value or not */
+ /* VHF is 174MHz ~ 245MHz */
+ if (frequency > 400 && VHF_SUPPORT == state->vhf_set) {
+ state->vhf_set = UHF_SUPPORT;
+ tuner_init(state);
+ } else if (frequency < 400 && UHF_SUPPORT == state->vhf_set) {
+ state->vhf_set = VHF_SUPPORT;
+ tuner_init(state);
+ }
+
+ pll_r = 1;
+ lofreq = frequency * 1000;
+ div_sel = lo2pll_freq(lofreq);
+ seg_num = (0x01 << div_sel);
+
+ if (seg_num >= 1 && seg_num <= 16) {
+ pll_freq = lofreq * (16/seg_num);
+ Freq = frequency * (16/seg_num);
+ } else {
+ pll_freq = lofreq * 2;
+ Freq = frequency * 2;
+ }
+
+ switch (state->cfg->ref_clk_type) {
+ case ADMTV102_REFCLK13000:
+ mtv10x_refclk = 130; /* 13*multi_factor */
+ multi_factor = 10;
+ sub_exp = 1;
+ div_1 = 5;
+ div_2 = 13; /* 130=13*5*(2^1) */
+ break;
+ case ADMTV102_REFCLK16384:
+ mtv10x_refclk = 16384;
+ multi_factor = 1000;
+ sub_exp = 14;
+ div_1 = 1;
+ div_2 = 1; /* 16384== 2^14 */
+ break;
+ case ADMTV102_REFCLK19200:
+ mtv10x_refclk = 192;
+ multi_factor = 10;
+ sub_exp = 6;
+ div_1 = 1;
+ div_2 = 3; /* 192 = 2^6 *3 */
+ break;
+ case ADMTV102_REFCLK20480:
+ mtv10x_refclk = 2048;
+ multi_factor = 100;
+ sub_exp = 11;
+ div_1 = 1;
+ div_2 = 1; /* 2048 = 2^11 */
+ break;
+ case ADMTV102_REFCLK24576:
+ mtv10x_refclk = 24576;
+ multi_factor = 1000;
+ sub_exp = 13;
+ div_1 = 1;
+ div_2 = 3; /* 24576 = 2^13*3 */
+ break;
+ case ADMTV102_REFCLK26000:
+ mtv10x_refclk = 260;
+ multi_factor = 10;
+ sub_exp = 2;
+ div_1 = 5;
+ div_2 = 13; /* 260=13*5*(2^2) */
+ break;
+ case ADMTV102_REFCLK30400:
+ mtv10x_refclk = 304;
+ multi_factor = 10;
+ pll_r = 2 ;
+ set_lpf(state, ADMTV102_REFCLK30400, (u8)lpf_bw);
+ admtv102_writereg(state, 0x19, pll_r); /* Ref. Clock Divider
+ PLL0 register , bit[7:4] is reserved, bit[3:0] is PLLR */
+ sub_exp = 4;
+ div_1 = 1;
+ div_2 = 19; /* 304 = 16*19 */
+ break;
+ case ADMTV102_REFCLK36000:
+ mtv10x_refclk = 360;
+ multi_factor = 10;
+ sub_exp = 3;
+ div_1 = 5;
+ div_2 = 9; /* 360=2^3*9*5 */
+ break;
+ case ADMTV102_REFCLK38400:
+ mtv10x_refclk = 384;
+ multi_factor = 10;
+ sub_exp = 7;
+ div_1 = 1;
+ div_2 = 3; /* 384 = 2^7 *3 */
+ break;
+ case ADMTV102_REFCLK20000:
+ mtv10x_refclk = 200;
+ multi_factor = 10;
+ sub_exp = 3;
+ div_1 = 5;
+ div_2 = 5; /* 200=8*5*5 */
+ break;
+ default:
+ mtv10x_refclk = 16384;
+ multi_factor = 1000;
+ sub_exp = 14;
+ div_1 = 1;
+ div_2 = 1; /* 16384== 2^14 */
+ }
+
+ pll_n = Freq * multi_factor * pll_r / mtv10x_refclk;
+ tmp = ((pll_r * Freq * multi_factor / div_1) << (20-sub_exp)) / div_2;
+ pll_f = tmp - (pll_n << 20);
+
+ data47 = 0x10; /* default Value */
+ admtv102_readreg(state, 0x2f, &data47);
+
+ if (pll_n <= 66) {
+ /* 4-prescaler */
+ pc4 = 1;
+ } else {
+ /* 8-prescaler */
+ pc4 = 0;
+ }
+ data47 = (u8)(data47 | (pc4<<6) | (pc8_16<<5));
+
+ /* do a reset operation */
+ admtv102_writereg(state, 0x27, 0x00);
+ /* PLL Reset Enable */
+ admtv102_writereg(state, 0x2f, data47); /* Reset Seq. 0 => 1 */
+ admtv102_writereg(state, 0x2f, (u8) (data47 | 0x80));
+
+ if ((pll_freq*2) < 2592000) /* 648*1000*2*2 according to data sheet */
+ vco_sel = 0;
+ else
+ vco_sel = 1;
+
+ /* read 0x09 register bit [5:0] */
+ /* To get temperature sensor value */
+ temper = 0x0C;
+ admtv102_readreg(state, 0x09, &temper) ;
+ temper &= 0x3f; /* get low 6 bits */
+
+ dp_phase_tuning(state, lofreq, temper);
+ /* these value may changes: g_icp=0,
+ state->convco=0, state->curTempState=HIGH_TEMP; */
+
+ if (lofreq > 400000) {
+ /*if iRF=UHF */
+ admtv102_writereg(state, 0x1a,
+ (u8)((state->icp << 2) | ((pll_n&0x300)>>8)));
+ } else {
+ admtv102_writereg(state, 0x1a,
+ (u8)(0xFC | ((pll_n&0x300) >> 8)));
+ }
+
+ /* PLL Setting */
+ admtv102_writereg(state, 0x1b, (u8)(pll_n & 0xFF));
+ admtv102_writereg(state, 0x1c,
+ (u8)((div_sel<<4) | (vco_sel<<7) | ((pll_f&0xF0000)>>16)));
+ admtv102_writereg(state, 0x1d, (u8)((pll_f&0x0FF00)>>8));
+ admtv102_writereg(state, 0x1e, (u8)(pll_f&0xFF));
+ admtv102_writereg(state, 0x15, (u8)(0x38 | ((lpf_bw-3)&0x07)));
+
+ /* PLL Reset */
+ admtv102_writereg(state, 0x2f, data47); /* Reset Seq. 0 => 1 */
+ admtv102_writereg(state, 0x2f, (u8)(data47 | 0x80));
+ admtv102_writereg(state, 0x2f, data47);
+
+ admtv102_writereg(state, 0x27, state->convco);
+ if (lofreq > 400000)
+ admtv102_writereg(state, 0x29, 0xBF);
+}
+
+/*
+ Function: calculate DIVSEL according to lofreq
+ Input: lofreq -- Frequency point value in kHz unit
+ Return: divsel -- DIVSEL in register 0x1C
+*/
+int lo2pll_freq(u32 lofreq)
+{
+ u32 fdef_lo_bound_freq = 940000;
+ int seg_num, divsel = 0;
+
+ seg_num = (int)(lofreq / (fdef_lo_bound_freq / 16));
+
+ while (seg_num > 0x01) {
+ seg_num >>= 1;
+ divsel++;
+ }
+
+ return divsel;
+}
+
+/*
+ Function: Change CONVCO value according to Temperature Sensor(0x27[0:4])
+ Input: lofreq -- frequency in kHz unit
+ temper -- Tuner 0x09 register content
+*/
+void dp_phase_tuning(struct admtv102_priv *state, u32 lofreq, u8 temper)
+{
+ if (state->vhf_set == VHF_SUPPORT) {
+ if (temper <= VLOW_DEG_BOUNDARY) {
+ state->convco = RGLOW_DEG_CONVCO_VHF;
+ state->curTempState = LOW_TEMP;
+ } else if (temper >= VHIGH_DEG_BOUNDARY) {
+ state->convco = RGHIGH_DEG_CONVCO;
+ state->curTempState = HIGH_TEMP;
+ }
+ } else {
+ if (temper <= VLOW_DEG_BOUNDARY) {
+ state->convco = RGLOW_DEG_CONDIV;
+ state->icp = 0x3F;
+ state->curTempState = LOW_TEMP;
+ } else if (temper >= VHIGH_DEG_BOUNDARY) {
+ state->convco = RGHIGH_DEG_CONDIV;
+ if ((lofreq > 610000) && (lofreq < 648000)) {
+ /*610MHz ~ 648MHz */
+ state->icp = 0x1F;
+ } else
+ state->icp = 0x3F;
+ state->curTempState = HIGH_TEMP;
+ } else {
+ if (state->curTempState) {
+ state->convco = RGLOW_DEG_CONDIV;
+ state->icp = 0x3F;
+ } else {
+ state->convco = RGHIGH_DEG_CONDIV;
+ if ((lofreq > 610000) &&
+ (lofreq < 648000)) {
+ /* 610MHz ~ 648MHz */
+ state->icp = 0x1F;
+ } else
+ state->icp = 0x3F;
+ }
+ }
+ }
+}
+
+/*
+ Function: Do tuner temperature compensate, it can be called by processor
+ for every 5~10 seconds. This may improve the tuner performance.
+ Input: lofreq -- frequency in kHz unit
+*/
+void temperature_compensate(struct admtv102_priv *state, long lofreq)
+{
+ u8 old_val, new_val, temper;
+
+ old_val = 0xFC;
+ temper = 0x0C;
+
+ admtv102_readreg(state, 0x09, &temper);
+ temper &= 0x3F;
+
+ dp_phase_tuning(state, lofreq, temper);
+
+ admtv102_readreg(state, 0x1A, &old_val);
+
+ /* reserve bit 7, bit1 and bit 0 */
+ new_val = (u8)((state->icp<<2) | (old_val & 0x83));
+ /* write state->icp into 0x1A register bit[6:2] */
+ admtv102_writereg(state, 0x1A, new_val);
+ admtv102_writereg(state, 0x27, state->convco);
+}
+
+int pll_lock_check(struct admtv102_priv *state)
+{
+ int pll_lock = 0;
+ int lock, ad_out;
+ u8 t;
+
+ /* check if the tuner PLL is locked or not */
+ admtv102_readreg(state, 0x06, &t);
+ lock = ((t & 0x02) == 0x02) ? 1 : 0;
+
+ admtv102_readreg(state, 0x04, &t);
+ ad_out = t & 0x0f;
+
+ /* PLL lock cross check */
+ if (lock &&
+ ((ad_out > AD_OUT_MIN) && (ad_out < AD_OUT_MAX)))
+ pll_lock = 1;
+
+ return pll_lock;
+}
+
+/* ========================================================================= */
+
+static int admtv102_set_params(struct dvb_frontend *fe,
+ struct dvb_frontend_parameters *params)
+{
+ struct admtv102_priv *priv;
+ u32 freq;
+ int i;
+ int ret = 0;
+ priv = fe->tuner_priv;
+
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */
+
+ freq = params->frequency / 1000 / 1000; /* Hz -> MHz */
+ priv->bandwidth = (fe->ops.info.type == FE_OFDM) ?
+ params->u.ofdm.bandwidth : 0;
+
+ set_rf_freq(priv, freq, priv->bandwidth / 1000 / 1000);
+
+ /* Waits for PLL lock or timeout */
+ i = 0;
+ do {
+ if (pll_lock_check(priv))
+ break;
+ msleep(4);
+ i++;
+ } while (i < 10);
+
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */
+
+ return ret;
+}
+
+static int admtv102_get_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+ struct admtv102_priv *priv = fe->tuner_priv;
+ *frequency = priv->frequency;
+ return 0;
+}
+
+static int admtv102_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
+{
+ struct admtv102_priv *priv = fe->tuner_priv;
+ *bandwidth = priv->bandwidth;
+ return 0;
+}
+
+static int admtv102_init(struct dvb_frontend *fe)
+{
+ struct admtv102_priv *state = fe->tuner_priv;
+
+ tuner_init(state);
+ set_rf_freq(state, 746, 8);
+ return 0;
+}
+
+static int admtv102_sleep(struct dvb_frontend *fe)
+{
+ return 0;
+}
+
+static int admtv102_release(struct dvb_frontend *fe)
+{
+ kfree(fe->tuner_priv);
+ fe->tuner_priv = NULL;
+ return 0;
+}
+
+static const struct dvb_tuner_ops admtv102_tuner_ops = {
+ .info = {
+ .name = "Analog Device ADMTV102",
+ .frequency_min = 48000000,
+ .frequency_max = 860000000,
+ .frequency_step = 50000,
+ },
+
+ .release = admtv102_release,
+
+ .init = admtv102_init,
+ .sleep = admtv102_sleep,
+
+ .set_params = admtv102_set_params,
+ .get_frequency = admtv102_get_frequency,
+ .get_bandwidth = admtv102_get_bandwidth
+};
+
+struct dvb_frontend *admtv102_attach(struct dvb_frontend *fe,
+ struct i2c_adapter *i2c, struct admtv102_config *cfg)
+{
+ struct admtv102_priv *priv = NULL;
+ u8 id = 0;
+
+ priv = kzalloc(sizeof(struct admtv102_priv), GFP_KERNEL);
+ if (priv == NULL)
+ return NULL;
+
+ priv->cfg = cfg;
+ priv->i2c = i2c;
+
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */
+
+ if (admtv102_readreg(priv, 0, &id) != 0) {
+ kfree(priv);
+ return NULL;
+ }
+
+ memcpy(&fe->ops.tuner_ops, &admtv102_tuner_ops,
+ sizeof(struct dvb_tuner_ops));
+
+ fe->tuner_priv = priv;
+
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */
+
+ return fe;
+}
+EXPORT_SYMBOL(admtv102_attach);
+
+MODULE_AUTHOR("David T.L. Wong");
+MODULE_DESCRIPTION("Analog Device ADMTV102 silicon tuner driver");
+MODULE_LICENSE("GPL");
@@ -0,0 +1,58 @@
+/*
+ * Driver for Analog Device ADMTV102 silicon tuner
+ *
+ * Copyright (c) 2006 Olivier DANET <odanet@caramail.com>
+ *
+ * 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.=
+ */
+
+#ifndef ADMTV102_H
+#define ADMTV102_H
+
+#define ADMTV102_REFCLK13000 0
+#define ADMTV102_REFCLK16384 1
+#define ADMTV102_REFCLK19200 2
+#define ADMTV102_REFCLK20480 3
+#define ADMTV102_REFCLK24576 4
+#define ADMTV102_REFCLK26000 5
+#define ADMTV102_REFCLK30400 6
+#define ADMTV102_REFCLK36000 7
+#define ADMTV102_REFCLK38400 8
+#define ADMTV102_REFCLK20000 9
+
+
+struct dvb_frontend;
+struct i2c_adapter;
+
+struct admtv102_config {
+ u8 i2c_address;
+ u8 ref_clk_type;
+};
+
+#if defined(CONFIG_MEDIA_TUNER_ADMTV102) || \
+ (defined(CONFIG_MEDIA_TUNER_ADMTV102_MODULE) && defined(MODULE))
+extern struct dvb_frontend *admtv102_attach(struct dvb_frontend *fe,
+ struct i2c_adapter *i2c, struct admtv102_config *cfg);
+#else
+static inline struct dvb_frontend *admtv102_attach(struct dvb_frontend *fe,
+ struct i2c_adapter *i2c, struct admtv102_config *cfg)
+{
+ printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+ return NULL;
+}
+#endif /* CONFIG_MEDIA_TUNER_ADMTV102 */
+
+#endif
@@ -0,0 +1,54 @@
+struct admtv102_priv {
+ struct admtv102_config *cfg;
+ struct i2c_adapter *i2c;
+
+ u32 frequency;
+ u32 bandwidth;
+
+ int vhf_set;
+ u8 icp;
+ u8 convco;
+ u8 curTempState;
+ u8 ctune_clkofs;
+};
+
+#define TUNER_MTV102 0x00
+#define TUNER_ADMTV102 0x01
+#define TUNER_NEWMTV102 0x02
+
+#define VHF_SUPPORT 1
+#define UHF_SUPPORT 0
+
+#define VLOW_DEG_BOUNDARY 7
+#define RGLOW_DEG_CONDIV 0xEC
+#define VHIGH_DEG_BOUNDARY 9
+#define RGHIGH_DEG_CONDIV 0x00
+#define HIGH_TEMP 0
+#define LOW_TEMP 1
+#define RGLOW_DEG_CONVCO_VHF 0x9D
+#define RGHIGH_DEG_CONVCO 0x00
+
+#define _EXTUNEOFF 0
+#define _EXTUNEON 1
+#define _TUNEEN 1
+#define _TUNEDIS 0
+#define CTUNEOFS 0x01 /* default = 0 */
+
+#define CTUNE_CLKOFS_SPLIT0E 0x09
+#define CTUNE_CLKOFS_SPLIT0F 0x00 /* for mass product */
+#define REFCLK30400_CLKSEL_REG_SPLITID0E 0x5A /* for split ID 0x0e */
+#define REFCLK30400_CLKSEL_REG_SPLITID0F 0x6A /* for split ID is 0x0f */
+
+#define AD_OUT_MIN 2
+#define AD_OUT_MAX 12
+
+void config_tuner(struct admtv102_priv *state, const u8 *addrdata);
+void tuner_init(struct admtv102_priv *state);
+int get_tuner_type(struct admtv102_priv *state);
+void set_rf_freq(struct admtv102_priv *state, u32 frequency, u32 lpfBW);
+void set_lpf(struct admtv102_priv *state, u32 ref_clk_type, u8 lpfBW);
+int lo2pll_freq(u32 lofreq);
+void pll_reg_set(struct admtv102_priv *state, const u8 *reg_dat, int pll_type);
+void dp_phase_tuning(struct admtv102_priv *state, u32 lofreq, u8 temper);
+void temperature_compensate(struct admtv102_priv *state, long lofreq);
+int pll_lock_check(struct admtv102_priv *state);