@ -1,21 +1,57 @@
/*
* fc0012 tuner support for rtl - sd r
* Fitipower FC0012 tuner drive r
*
* Based on tuner_fc0012 . c found as part of the ( seemingly GPLed )
* rtl2832u Linux DVB driver .
* Copyright ( C ) 2012 Hans - Frieder Vogt < hfvogt @ gmx . net >
*
* Rewritten and hacked into rtl - sdr by David Basden < davidb - sdr @ rcpt . to >
* modified for use in librtlsdr
* Copyright ( C ) 2012 Steve Markgraf < steve @ steve - m . de >
*
* 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 0213 9 , USA .
*/
# include <stdio.h>
# include <stdint.h>
# include "rtlsdr_i2c.h"
# include "tuner_fc0012.h"
# define CRYSTAL_FREQ 28800000
static int fc0012_writereg ( void * dev , uint8_t reg , uint8_t val )
{
uint8_t data [ 2 ] ;
data [ 0 ] = reg ;
data [ 1 ] = val ;
if ( rtlsdr_i2c_write_fn ( dev , FC0012_I2C_ADDR , data , 2 ) < 0 )
return - 1 ;
return 0 ;
}
static int fc0012_readreg ( void * dev , uint8_t reg , uint8_t * val )
{
uint8_t data = reg ;
if ( rtlsdr_i2c_write_fn ( dev , FC0012_I2C_ADDR , & data , 1 ) < 0 )
return - 1 ;
if ( rtlsdr_i2c_read_fn ( dev , FC0012_I2C_ADDR , & data , 1 ) < 0 )
return - 1 ;
# define FC0012_LNAGAIN FC0012_LNA_GAIN_HI
* val = data ;
return 0 ;
}
/* Incomplete list of register settings:
*
@ -51,205 +87,132 @@
* ( big value - > low freq )
*/
/* glue functions to rtl-sdr code */
int FC0012_Write ( void * pTuner , unsigned char RegAddr , unsigned char Byte )
int fc0012_init ( void * dev )
{
uint8_t data [ 2 ] ;
data [ 0 ] = RegAddr ;
data [ 1 ] = Byte ;
int ret = 0 ;
unsigned int i ;
uint8_t reg [ ] = {
0x00 , /* dummy reg. 0 */
0x05 , /* reg. 0x01 */
0x10 , /* reg. 0x02 */
0x00 , /* reg. 0x03 */
0x00 , /* reg. 0x04 */
0x0f , /* reg. 0x05: may also be 0x0a */
0x00 , /* reg. 0x06: divider 2, VCO slow */
0x00 , /* reg. 0x07: may also be 0x0f */
0xff , /* reg. 0x08: AGC Clock divide by 256, AGC gain 1/256,
Loop Bw 1 / 8 */
0x6e , /* reg. 0x09: Disable LoopThrough, Enable LoopThrough: 0x6f */
0xb8 , /* reg. 0x0a: Disable LO Test Buffer */
0x82 , /* reg. 0x0b: Output Clock is same as clock frequency,
may also be 0x83 */
0xfc , /* reg. 0x0c: depending on AGC Up-Down mode, may need 0xf8 */
0x02 , /* reg. 0x0d: AGC Not Forcing & LNA Forcing, 0x02 for DVB-T */
0x00 , /* reg. 0x0e */
0x00 , /* reg. 0x0f */
0x00 , /* reg. 0x10: may also be 0x0d */
0x00 , /* reg. 0x11 */
0x1f , /* reg. 0x12: Set to maximum gain */
0x08 , /* reg. 0x13: Set to Middle Gain: 0x08,
Low Gain : 0x00 , High Gain : 0x10 , enable IX2 : 0x80 */
0x00 , /* reg. 0x14 */
0x04 , /* reg. 0x15: Enable LNA COMPS */
} ;
if ( rtlsdr_i2c_write_fn ( pTuner , FC0012_I2C_ADDR , data , 2 ) < 0 )
return FC0012_ERROR ;
return FC0012_OK ;
}
int FC0012_Read ( void * pTuner , unsigned char RegAddr , unsigned char * pByte )
{
uint8_t data = RegAddr ;
if ( rtlsdr_i2c_write_fn ( pTuner , FC0012_I2C_ADDR , & data , 1 ) < 0 )
return FC0012_ERROR ;
if ( rtlsdr_i2c_read_fn ( pTuner , FC0012_I2C_ADDR , & data , 1 ) < 0 )
return FC0012_ERROR ;
* pByte = data ;
return FC0012_OK ;
}
# ifdef DEBUG
# define DEBUGF printf
# else
# define DEBUGF(...) ()
# endif
#if 0
void FC0012_Dump_Registers ( )
{
# ifdef DEBUG
unsigned char regBuf ;
int ret ;
int i ;
DEBUGF ( " \n FC0012 registers: \n " ) ;
for ( i = 0 ; i < = 0x15 ; + + i )
{
ret = FC0012_Read ( pTuner , i , & regBuf ) ;
if ( ret ) DEBUGF ( " \n Couldn't read register %02x \n " , i ) ;
DEBUGF ( " R%x=%02x " , i , regBuf ) ;
switch ( rtlsdr_get_tuner_clock ( dev ) ) {
case FC_XTAL_27_MHZ :
case FC_XTAL_28_8_MHZ :
reg [ 0x07 ] | = 0x20 ;
break ;
case FC_XTAL_36_MHZ :
default :
break ;
}
DEBUGF ( " \n " ) ;
FC0012_Read ( pTuner , 0x06 , & regBuf ) ;
DEBUGF ( " LNA_POWER_DOWN: \t %s \n " , regBuf & 1 ? " Powered down " : " Not Powered Down " ) ;
DEBUGF ( " VCO_SPEED: \t %s \n " , regBuf & 0x8 ? " High speed " : " Slow speed " ) ;
DEBUGF ( " Bandwidth: \t %s \n " , ( regBuf & 0xC ) ? " 8MHz " : " less than 8MHz " ) ;
FC0012_Read ( pTuner , 0x07 , & regBuf ) ;
DEBUGF ( " Crystal Speed: \t %s \n " , ( regBuf & 0x20 ) ? " 28.8MHz " : " 36MHZ<!> " ) ;
FC0012_Read ( pTuner , 0x09 , & regBuf ) ;
DEBUGF ( " RSSI calibration mode: \t %s \n " , ( regBuf & 0x10 ) ? " RSSI CALIBRATION IN PROGRESS<!> " : " Disabled " ) ;
FC0012_Read ( pTuner , 0x0d , & regBuf ) ;
DEBUGF ( " LNA Force: \t %s \n " , ( regBuf & 0x1 ) ? " Forced " : " Not Forced " ) ;
FC0012_Read ( pTuner , 0x13 , & regBuf ) ;
DEBUGF ( " LNA Gain: \t " ) ;
switch ( regBuf & 0x18 ) {
case ( 0x00 ) : DEBUGF ( " Low \n " ) ; break ;
case ( 0x08 ) : DEBUGF ( " Middle \n " ) ; break ;
case ( 0x10 ) : DEBUGF ( " High \n " ) ; break ;
default : DEBUGF ( " unknown gain value 0x18 \n " ) ;
}
# endif
}
# endif
reg [ 0x07 ] | = 0x20 ;
int FC0012_Open ( void * pTuner )
{
// DEBUGF("FC0012_Open start");
if ( FC0012_Write ( pTuner , 0x01 , 0x05 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x02 , 0x10 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x03 , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x04 , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x05 , 0x0F ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x06 , 0x00 ) ) return - 1 ; // divider 2, VCO slow
if ( FC0012_Write ( pTuner , 0x07 , 0x20 ) ) return - 1 ; // change to 0x00 for a 36MHz crystal
if ( FC0012_Write ( pTuner , 0x08 , 0xFF ) ) return - 1 ; // AGC Clock divide by 254, AGC gain 1/256, Loop Bw 1/8
if ( FC0012_Write ( pTuner , 0x09 , 0x6E ) ) return - 1 ; // Disable LoopThrough
if ( FC0012_Write ( pTuner , 0x0A , 0xB8 ) ) return - 1 ; // Disable LO Test Buffer
if ( FC0012_Write ( pTuner , 0x0B , 0x82 ) ) return - 1 ; // Output Clock is same as clock frequency
//if (FC0012_Write(pTuner, 0x0C, 0xF8)) return -1;
if ( FC0012_Write ( pTuner , 0x0C , 0xFC ) ) return - 1 ; // AGC up-down mode
if ( FC0012_Write ( pTuner , 0x0D , 0x02 ) ) return - 1 ; // AGC Not Forcing & LNA Forcing
if ( FC0012_Write ( pTuner , 0x0E , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x0F , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x10 , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x11 , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x12 , 0x1F ) ) return - 1 ; // Set to maximum gain
if ( FC0012_Write ( pTuner , 0x13 , FC0012_LNAGAIN ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x14 , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x15 , 0x04 ) ) return - 1 ; // Enable LNA COMPS
/* Black magic from nim_rtl2832_fc0012.c in DVB driver.
Even though we ' ve set 0x11 to 0x00 above , this needs to happen to have
it go back
*/
if ( FC0012_Write ( pTuner , 0x0d , 0x02 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x11 , 0x00 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x15 , 0x04 ) ) return - 1 ;
// DEBUGF("FC0012_Open SUCCESS");
return FC0012_OK ;
}
// if (priv->dual_master)
reg [ 0x0c ] | = 0x02 ;
# if 0
// Frequency is in kHz. Bandwidth is in MHz
// This is pseudocode to set GPIO6 for VHF/UHF filter switching.
// Trying to do this in reality leads to fail currently. I'm probably doing it wrong.
void FC0012_Frequency_Control ( unsigned int Frequency , unsigned short Bandwidth )
{
if ( Frequency < 260000 & & Frequency > 150000 )
{
// set GPIO6 = low
// 1. Set tuner frequency
// 2. if the program quality is not good enough, switch to frequency + 500kHz
// 3. if the program quality is still no good, switch to frequency - 500kHz
for ( i = 1 ; i < sizeof ( reg ) ; i + + ) {
ret = fc0012_writereg ( dev , i , reg [ i ] ) ;
if ( ret )
break ;
}
else
{
// set GPIO6 = high
// set tuner frequency
}
return ret ;
}
# endif
int FC0012_SetFrequency ( void * pTuner , unsigned long Frequency , unsigned short B andwidth)
int fc0012_set_params ( void * dev , uint32_t freq , uint32_t bandwidth )
{
int VCO_band = 0 ;
unsigned long doubleVCO ;
unsigned short xin , xdiv ;
unsigned char reg [ 21 ] , am , pm , multi ;
unsigned char read_byte ;
unsigned long CrystalFreqKhz ;
// DEBUGF("FC0012_SetFrequency start");
int i , ret = 0 ;
uint8_t reg [ 7 ] , am , pm , multi , tmp ;
uint64_t f_vco ;
uint32_t xtal_freq_div_2 ;
uint16_t xin , xdiv ;
int vco_select = 0 ;
xtal_freq_div_2 = rtlsdr_get_tuner_clock ( dev ) / 2 ;
/* select frequency divider and the frequency of VCO */
if ( freq < 37084000 ) { /* freq * 96 < 3560000000 */
multi = 96 ;
reg [ 5 ] = 0x82 ;
reg [ 6 ] = 0x00 ;
} else if ( freq < 55625000 ) { /* freq * 64 < 3560000000 */
multi = 64 ;
reg [ 5 ] = 0x82 ;
reg [ 6 ] = 0x02 ;
} else if ( freq < 74167000 ) { /* freq * 48 < 3560000000 */
multi = 48 ;
reg [ 5 ] = 0x42 ;
reg [ 6 ] = 0x00 ;
} else if ( freq < 111250000 ) { /* freq * 32 < 3560000000 */
multi = 32 ;
reg [ 5 ] = 0x42 ;
reg [ 6 ] = 0x02 ;
} else if ( freq < 148334000 ) { /* freq * 24 < 3560000000 */
multi = 24 ;
reg [ 5 ] = 0x22 ;
reg [ 6 ] = 0x00 ;
} else if ( freq < 222500000 ) { /* freq * 16 < 3560000000 */
multi = 16 ;
reg [ 5 ] = 0x22 ;
reg [ 6 ] = 0x02 ;
} else if ( freq < 296667000 ) { /* freq * 12 < 3560000000 */
multi = 12 ;
reg [ 5 ] = 0x12 ;
reg [ 6 ] = 0x00 ;
} else if ( freq < 445000000 ) { /* freq * 8 < 3560000000 */
multi = 8 ;
reg [ 5 ] = 0x12 ;
reg [ 6 ] = 0x02 ;
} else if ( freq < 593334000 ) { /* freq * 6 < 3560000000 */
multi = 6 ;
reg [ 5 ] = 0x0a ;
reg [ 6 ] = 0x00 ;
} else {
multi = 4 ;
reg [ 5 ] = 0x0a ;
reg [ 6 ] = 0x02 ;
}
CrystalFreqKhz = ( rtlsdr_get_tuner_clock ( pTuner ) + 500 ) / 1000 ;
f_vco = freq * multi ;
//===================================== Select frequency divider and the frequency of VCO
if ( Frequency * 96 < 3560000 )
{
multi = 96 ; reg [ 5 ] = 0x82 ; reg [ 6 ] = 0x00 ;
}
else if ( Frequency * 64 < 3560000 )
{
multi = 64 ; reg [ 5 ] = 0x82 ; reg [ 6 ] = 0x02 ;
}
else if ( Frequency * 48 < 3560000 )
{
multi = 48 ; reg [ 5 ] = 0x42 ; reg [ 6 ] = 0x00 ;
}
else if ( Frequency * 32 < 3560000 )
{
multi = 32 ; reg [ 5 ] = 0x42 ; reg [ 6 ] = 0x02 ;
}
else if ( Frequency * 24 < 3560000 )
{
multi = 24 ; reg [ 5 ] = 0x22 ; reg [ 6 ] = 0x00 ;
if ( f_vco > = 3060000000U ) {
reg [ 6 ] | = 0x08 ;
vco_select = 1 ;
}
else if ( Frequency * 16 < 3560000 )
{
multi = 16 ; reg [ 5 ] = 0x22 ; reg [ 6 ] = 0x02 ;
}
else if ( Frequency * 12 < 3560000 )
{
multi = 12 ; reg [ 5 ] = 0x12 ; reg [ 6 ] = 0x00 ;
}
else if ( Frequency * 8 < 3560000 )
{
multi = 8 ; reg [ 5 ] = 0x12 ; reg [ 6 ] = 0x02 ;
}
else if ( Frequency * 6 < 3560000 )
{
multi = 6 ; reg [ 5 ] = 0x0A ; reg [ 6 ] = 0x00 ;
}
else
{
multi = 4 ; reg [ 5 ] = 0x0A ; reg [ 6 ] = 0x02 ;
}
doubleVCO = Frequency * multi ;
reg [ 6 ] = reg [ 6 ] | 0x08 ;
VCO_band = 1 ;
xdiv = ( unsigned short ) ( doubleVCO / ( CrystalFreqKhz / 2 ) ) ;
if ( ( doubleVCO - xdiv * ( CrystalFreqKhz / 2 ) ) > = ( CrystalFreqKhz / 4 ) )
xdiv = xdiv + 1 ;
if ( freq > = 45000000 ) {
/* From divided value (XDIV) determined the FA and FP value */
xdiv = ( uint16_t ) ( f_vco / xtal_freq_div_2 ) ;
if ( ( f_vco - xdiv * xtal_freq_div_2 ) > = ( xtal_freq_div_2 / 2 ) )
xdiv + + ;
pm = ( unsigned char ) ( xdiv / 8 ) ;
am = ( unsigned char ) ( xdiv - ( 8 * pm ) ) ;
pm = ( uint8_t ) ( xdiv / 8 ) ;
am = ( uint8_t ) ( xdiv - ( 8 * pm ) ) ;
if ( am < 2 ) {
reg [ 1 ] = am + 8 ;
@ -258,70 +221,92 @@ int FC0012_SetFrequency(void *pTuner, unsigned long Frequency, unsigned short Ba
reg [ 1 ] = am ;
reg [ 2 ] = pm ;
}
} else {
/* fix for frequency less than 45 MHz */
reg [ 1 ] = 0x06 ;
reg [ 2 ] = 0x11 ;
}
// From VCO frequency determines the XIN ( fractional part of Delta Sigma PLL) and divided value (XDIV).
xin = ( unsigned short ) ( doubleVCO - ( ( unsigned short ) ( doubleVCO / ( CrystalFreqKhz / 2 ) ) ) * ( CrystalFreqKhz / 2 ) ) ;
xin = ( ( xin < < 15 ) / ( unsigned short ) ( CrystalFreqKhz / 2 ) ) ;
if ( xin > = ( unsigned short ) 16384 )
xin = xin + ( unsigned short ) 32768 ;
reg [ 3 ] = ( unsigned char ) ( xin > > 8 ) ;
reg [ 4 ] = ( unsigned char ) ( xin & 0x00FF ) ;
/* fix clock out */
reg [ 6 ] | = 0x20 ;
/* From VCO frequency determines the XIN ( fractional part of Delta
Sigma PLL ) and divided value ( XDIV ) */
xin = ( uint16_t ) ( ( f_vco - ( f_vco / xtal_freq_div_2 ) * xtal_freq_div_2 ) / 1000 ) ;
xin = ( xin < < 15 ) / ( xtal_freq_div_2 / 1000 ) ;
if ( xin > = 16384 )
xin + = 32768 ;
reg [ 3 ] = xin > > 8 ; /* xin with 9 bit resolution */
reg [ 4 ] = xin & 0xff ;
reg [ 6 ] & = 0x3f ; /* bits 6 and 7 describe the bandwidth */
switch ( bandwidth ) {
case 6000000 :
reg [ 6 ] | = 0x80 ;
break ;
case 7000000 :
reg [ 6 ] | = 0x40 ;
break ;
case 8000000 :
default :
break ;
}
// DEBUGF("Frequency: %lu, Fa: %d, Fp: %d, Xin:%d \n", Frequency, am, pm, xin);
/* modified for Realtek demod */
reg [ 5 ] | = 0x07 ;
switch ( Bandwidth )
{
case 6 : reg [ 6 ] = 0x80 | reg [ 6 ] ; break ;
case 7 : reg [ 6 ] = ( ~ 0x80 & reg [ 6 ] ) | 0x40 ; break ;
case 8 : default : reg [ 6 ] = ~ 0xC0 & reg [ 6 ] ; break ;
for ( i = 1 ; i < = 6 ; i + + ) {
ret = fc0012_writereg ( dev , i , reg [ i ] ) ;
if ( ret )
goto exit ;
}
if ( FC0012_Write ( pTuner , 0x01 , reg [ 1 ] ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x02 , reg [ 2 ] ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x03 , reg [ 3 ] ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x04 , reg [ 4 ] ) ) return - 1 ;
//reg[5] = reg[5] | 0x07; // This is really not cool. Why is it there?
if ( FC0012_Write ( pTuner , 0x05 , reg [ 5 ] ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x06 , reg [ 6 ] ) ) return - 1 ;
// VCO Calibration
if ( FC0012_Write ( pTuner , 0x0E , 0x80 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x0E , 0x00 ) ) return - 1 ;
// Read resulting VCO control voltage
if ( FC0012_Write ( pTuner , 0x0E , 0x00 ) ) return - 1 ;
if ( FC0012_Read ( pTuner , 0x0E , & read_byte ) ) return - 1 ;
reg [ 14 ] = 0x3F & read_byte ;
// Adjust VCO range if control voltage is at the limit
if ( VCO_band )
{
// high-band VCO hitting low frequency bound
if ( reg [ 14 ] > 0x3C )
{
// select low-band VCO
reg [ 6 ] = ~ 0x08 & reg [ 6 ] ;
if ( FC0012_Write ( pTuner , 0x06 , reg [ 6 ] ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x0E , 0x80 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x0E , 0x00 ) ) return - 1 ;
/* VCO Calibration */
ret = fc0012_writereg ( dev , 0x0e , 0x80 ) ;
if ( ! ret )
ret = fc0012_writereg ( dev , 0x0e , 0x00 ) ;
/* VCO Re-Calibration if needed */
if ( ! ret )
ret = fc0012_writereg ( dev , 0x0e , 0x00 ) ;
if ( ! ret ) {
// msleep(10);
ret = fc0012_readreg ( dev , 0x0e , & tmp ) ;
}
if ( ret )
goto exit ;
/* vco selection */
tmp & = 0x3f ;
if ( vco_select ) {
if ( tmp > 0x3c ) {
reg [ 6 ] & = ~ 0x08 ;
ret = fc0012_writereg ( dev , 0x06 , reg [ 6 ] ) ;
if ( ! ret )
ret = fc0012_writereg ( dev , 0x0e , 0x80 ) ;
if ( ! ret )
ret = fc0012_writereg ( dev , 0x0e , 0x00 ) ;
}
else
{
// low-band VCO hitting high frequency bound
if ( reg [ 14 ] < 0x02 ) {
// select high-band VCO
reg [ 6 ] = 0x08 | reg [ 6 ] ;
if ( FC0012_Write ( pTuner , 0x06 , reg [ 6 ] ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x0E , 0x80 ) ) return - 1 ;
if ( FC0012_Write ( pTuner , 0x0E , 0x00 ) ) return - 1 ;
} else {
if ( tmp < 0x02 ) {
reg [ 6 ] | = 0x08 ;
ret = fc0012_writereg ( dev , 0x06 , reg [ 6 ] ) ;
if ( ! ret )
ret = fc0012_writereg ( dev , 0x0e , 0x80 ) ;
if ( ! ret )
ret = fc0012_writereg ( dev , 0x0e , 0x00 ) ;
}
}
// DEBUGF("FC0012_SetFrequency SUCCESS"); FC0012_Dump_Registers();
return FC0012_OK ;
exit :
return ret ;
}
int fc0012_set_gain ( void * dev , int gain )
{
/* TODO add gain regulation */
return 0 ;
}