diff mbox

[RFC] Analog Device ADMTV 102 silicon tuner support patch

Message ID 15ed362e0906021913r7683ac62n411a5d2e6bd9ad17@mail.gmail.com (mailing list archive)
State Changes Requested
Headers show

Commit Message

David Wong June 3, 2009, 2:13 a.m. UTC
Hi all,

  This ADMTV102 tuner code is grab from open sourced driver for
MyCinema U3100 Mini DMB-TH from ASUS.
I made some clean up to separate the tuner code from demod code.

  The original driver author cannot be reached, so I don't know should
I declare the copyright.
Please comment.

Regards,
David T.L. Wong

Comments

Mauro Carvalho Chehab June 3, 2009, 12:22 p.m. UTC | #1
Em Wed, 3 Jun 2009 10:13:22 +0800
David Wong <davidtlwong@gmail.com> escreveu:

> Hi all,
> 
>   This ADMTV102 tuner code is grab from open sourced driver for
> MyCinema U3100 Mini DMB-TH from ASUS.
> I made some clean up to separate the tuner code from demod code.
> 
>   The original driver author cannot be reached, so I don't know should
> I declare the copyright.

This is probably the most important thing to do: we should be sure that
the driver doesn't have any licensing troubles, since you'll need to testify it
via Signed-off-by. So, if you couldn't find the original author, you may try to
reach the companies envolved, e. g. the distro kernel people, Asus and Analog
Device, asking for their SOB at the original driver.

Also, if the driver is authored by Oliver, as stated at the .h comment:

+ *  Driver for Analog Device ADMTV102 silicon tuner
+ *
+ *  Copyright (c) 2006 Olivier DANET <odanet@caramail.com>

You should preserve his name at the meta-tags, instead of:

+MODULE_AUTHOR("David T.L. Wong");

(or, if you made significant changes, it is OK to add your name, without
suppressing the original author's name)

Another important point: when committing such drivers, the better is to add the
first patch (without Kconfig/Makefile changes) as-is, and then write a second
patch with your changes. This helps to have a clearer boundary line about your
contributions. The last patch should be the Kconfig/Makefile changes. This
helps to avoid trying to build a driver that may not compile well, due to some
API changes, for example, that needs to be fixed during the upstream merging
phase.

> Please comment.
> 
> Regards,
> David T.L. Wong




Cheers,
Mauro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff -r 5ec629d784ad linux/drivers/media/common/tuners/Kconfig
--- a/linux/drivers/media/common/tuners/Kconfig	Mon Jun 01 22:34:20 2009 -0300
+++ b/linux/drivers/media/common/tuners/Kconfig	Wed Jun 03 10:01:24 2009 +0800
@@ -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
diff -r 5ec629d784ad linux/drivers/media/common/tuners/Makefile
--- a/linux/drivers/media/common/tuners/Makefile	Mon Jun 01 22:34:20 2009 -0300
+++ b/linux/drivers/media/common/tuners/Makefile	Wed Jun 03 10:01:24 2009 +0800
@@ -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
diff -r 5ec629d784ad linux/drivers/media/common/tuners/admtv102.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linux/drivers/media/common/tuners/admtv102.c	Wed Jun 03 10:01:24 2009 +0800
@@ -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 = &reg, .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");
diff -r 5ec629d784ad linux/drivers/media/common/tuners/admtv102.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linux/drivers/media/common/tuners/admtv102.h	Wed Jun 03 10:01:24 2009 +0800
@@ -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
diff -r 5ec629d784ad linux/drivers/media/common/tuners/admtv102_priv.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linux/drivers/media/common/tuners/admtv102_priv.h	Wed Jun 03 10:01:24 2009 +0800
@@ -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);