From patchwork Wed Jun 3 02:13:22 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Wong X-Patchwork-Id: 27599 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n532DRsJ026650 for ; Wed, 3 Jun 2009 02:13:27 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751071AbZFCCNX (ORCPT ); Tue, 2 Jun 2009 22:13:23 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751795AbZFCCNX (ORCPT ); Tue, 2 Jun 2009 22:13:23 -0400 Received: from qw-out-2122.google.com ([74.125.92.25]:49584 "EHLO qw-out-2122.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751071AbZFCCNW (ORCPT ); Tue, 2 Jun 2009 22:13:22 -0400 Received: by qw-out-2122.google.com with SMTP id 5so5931368qwd.37 for ; Tue, 02 Jun 2009 19:13:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:received:date:message-id:subject :from:to:content-type; bh=rrWFBJ2bEGm5y0gjwXqWNIVJuQV5MIfeaNaiVWB6jDQ=; b=YeqfbcZUTxmPSCeP3zKJrEnvWjCTz5IWez5uTjRPDmg6YEddGLec/scXSJr0hxOIrj eeY9eof5SXaPAVipFGNn7T1AYf/e5VHJFRx5Qpr4rRaByRgVS6I6robor4bvPIrk7hFV F04+C37+oQ5Yi8hOWPH9oVKPt5U1q5PqL5IyU= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:date:message-id:subject:from:to:content-type; b=rjprzH0Dv5Jth2tosJNS97dv9TpVZJpUqBdMeX7Hm/bAZQ8GAMPgX91aW/RdRdevst pUpMdHF/poraiVm5IpPOqWr5Fai42cuKIhB3PaHd8aiYagILgw/Wl78oh7mdk8hl8zIz NY0wczI4ZEOdkOWNZU+ZQizPvp4xU0KDbb2GE= MIME-Version: 1.0 Received: by 10.231.39.202 with SMTP id h10mr108240ibe.35.1243995202974; Tue, 02 Jun 2009 19:13:22 -0700 (PDT) Date: Wed, 3 Jun 2009 10:13:22 +0800 Message-ID: <15ed362e0906021913r7683ac62n411a5d2e6bd9ad17@mail.gmail.com> Subject: [RFC] Analog Device ADMTV 102 silicon tuner support patch From: David Wong To: linux-media@vger.kernel.org Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org 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 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 +#include +#include +#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"); 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 + * + * 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);