/* ------------------------------------------------------------------------- */
/* File Name: wg48Driver.c                                                   */
/*                                                                           */
/* Description: MAX3541 Driver                                               */
/*                                                                           */
/* Copyright (C) 2008 Maxim Integrated Products                              */
/*                                                                           */
/* History:                                                                  */
/*    date: 4/11/08                                                          */
/* version: 1.2.0                                                            */
/* author: Paul Nichol                                                       */
/* comment:  This version is for MCU's that only have integer math.          */
/*                                                                           */
/*  IMPORTANT: This code is for integer only frequencies.  To get            */
/*             fractional frequencies, the frequencies will have to be       */
/*             scaled accordingly without overflowing in Max3541SetSynth()   */
/*             The Max3541ReadRom() has a table that bypasses the values     */
/*             read back from ROM for pre-production parts.  This will       */
/*             have to be removed before production parts are used.          */
/*-------------------------------------------------------------------------- */

#include "max3541.h"    

#define HAVE_32BIT_MATH

#define NUM_REGS 13
#define NUM_READ_REGS 2

#define MAX3541_NDIV_HI 0
#define MAX3541_NDIV_LO 0x1
#define MAX3541_RDIV 0x2
#define MAX3541_VCO 0x3
#define MAX3541_IFOVLD 0x4
#define MAX3541_CONTROL 0x5
#define MAX3541_SHUTDOWN 0x6
#define MAX3541_TFS 0x7
#define MAX3541_TFP 0x8
#define MAX3541_TFROM_ADDR 0x9
#define MAX3541_TFFUSE_DATA 0xa
#define MAX3541_ROM_DATA 0xb
#define MAX3541_STATUS 0xc

#define VHF_L 0
#define VHF_H 1
#define UHF 2

#define SER0 0
#define SER1 1
#define PAR0 2
#define PAR1 3

#define MAX3541_BIAS 0x0

#define PAL 0
#define DVBT 1

#define RDIV_CHNGPOINT 100    /* Frequency at which the R-divider changes when tuning. */
#define FreqUHF 470           /* lower end of UHF band in MHz */

#define INT16  short          /* compiler type for 16 bit integer */
#define UINT16 unsigned short /* compiler type for 16 bit unsigned integer */
#define BOOL int

#ifndef FALSE
#define FALSE               0
#endif

#ifndef TRUE
#define TRUE                1
#endif

static UINT16 Max3541Init(UINT16 XtalRef, UINT16 IfFreq, UINT16 RfFreq);
static void Max3541SetFrequency(UINT16 Frequency);
static unsigned short Max3541Read(int reg);
static void Max3541Write(int reg, int value);
static BOOL Max3541GetStatus(void);

static void Max3541SetPreFilter(UINT16);
static void Max3541SetTrackingFilter(UINT16);
static void Max3541SetSynth(UINT16);
static void Max3541VcoTune(void);
static void Max3541ReadRom(void);
static void Max3541_If_VGA(BOOL);
static void Max3541Standard(int std);
static UINT16 tfs_i(UINT16 S0, UINT16 S1, UINT16 FreqRF, UINT16 c[5]);

unsigned char regs[12];
UINT16 TFRomCoefs[3][4];



UINT16 mXtalRef = 8;              /*Frequency applied to Max3541 Xtal pin.*/
UINT16 mLo_Frequency = 506;
UINT16 mRf_Frequency = 470;
UINT16 mIf_Frequency = 36;
int mStandard = DVBT;




/* table of fixed coefficients per Max3540 broadband datasheet page 18 */
static UINT16 co[6][5]={{ 22, 4* 11,80,4* 40, 20 }, /* VHF LO TFS */
                        { 16, 4* 4,40,4* 20, 10 },   /* VHF LO TFP */
                        {25,16* 13,50,4* 20,20 },  /* VHF HI TFS */
                        {16,16* 8,15,4* 6,10 },    /* VHF HI TFP */
                        {   30,   10,  26,   8,20 },    /* UHF TFS */
                        {   16,    8,  14,   8,10 }};   /* UHF TFP */



UINT16 Max3541Init(UINT16 XtalRef, UINT16 IfFreq, UINT16 RfFreq)
{  
	/* Initialize every register. Don't rely on Max3541 power on reset.*/
     
	mXtalRef = XtalRef;

	mIf_Frequency = IfFreq;
	mRf_Frequency = RfFreq;
	mLo_Frequency = mRf_Frequency + mIf_Frequency;
	mStandard = DVBT;	
   

   /*Initialize the registers in the Max3541:*/
   regs[MAX3541_RDIV]    =	32;
   regs[MAX3541_VCO]		=	0x9f;
   regs[MAX3541_IFOVLD]	=	0x4;  /* CP = 1.0mA */
   regs[MAX3541_CONTROL]	=	0x0;
   regs[MAX3541_SHUTDOWN]=	0x0;
   regs[MAX3541_TFS]		=	0xef;
   regs[MAX3541_TFP]		=	0x2c;
   regs[MAX3541_TFROM_ADDR] =  0x00;
   regs[MAX3541_TFFUSE_DATA]= 0x00;

	/* First read calibration constants from Max3541 and store in global
      variables for use when setting RF tracking filter */   
	Max3541ReadRom();
	Max3541SetTrackingFilter(mRf_Frequency);

	Max3541SetPreFilter(mRf_Frequency);

	Max3541SetSynth(mLo_Frequency);  

   /*  Maxim evkit I2c communication... Replace by microprocessor specific code */
   I2C_IWriteBlock(0,regs,11);

   Max3541VcoTune();

return TRUE;
}


/* Set the broadcast standard.  0 = PAL, 1 = DVB-T */
void Max3541Standard(int std)
{
	mStandard = std;
}

/*This will set the LO Frequency */
void Max3541SetFrequency(UINT16 Frequency)		
{
	UINT16 CP_Bits, RDiv;
   mRf_Frequency = Frequency;
   mLo_Frequency = mRf_Frequency + mIf_Frequency ;
   
 /* CPBits Reg4<3:2> control the charge pump current. */

	/* b3  b2 DEC cp current   */
	/* --  -- --- ---------- */
	/* 0   0  0   0.5mA */
   /* 0   1  1   1.0mA */
	/* 1   0  2   1.5mA */
	/* 1   1  3   2.0mA */
   
   /* CPBits will use the DEC(imal) value. */

	/* define charge pump current and R-Divider */
	if (mStandard == DVBT){
		if (mRf_Frequency >= FreqUHF){  /* UHF band */
	      CP_Bits = 3;    /* 2mA */
		}
		else{                    /* VHF Band */
	      CP_Bits = 0;    /* .5mA  */
		}
      if (mRf_Frequency <= RDIV_CHNGPOINT)
			RDiv = 32;
	   else
		   RDiv = 16;
	}
	else{   /* PAL */
		RDiv = 32;
      CP_Bits = 0;  /* .5mA  */
   }

	/* Update the R-Divider register (lower 7 bits of register ) */
   regs[MAX3541_RDIV] = RDiv & 0x7f;  /* bit 7 always low n*/

	/* Update the CP Current register (bits <3:2>) */
   regs[MAX3541_IFOVLD] = (regs[MAX3541_IFOVLD] & 0xf3) | (CP_Bits << 2);

   /* Program tracking filters and other frequency dependent registers */
   Max3541SetPreFilter(mRf_Frequency);
   Max3541SetTrackingFilter(mRf_Frequency);

   /* set up PLL divider ratios */
   Max3541SetSynth(mLo_Frequency);

   /*  Send registers that have been changed */
   /*  Maxim evkit I2c communication... Replace by microprocessor specific code */
   I2C_IWriteBlock(MAX3541_NDIV_HI,(regs+ MAX3541_NDIV_HI),1) ; 
   I2C_IWriteBlock(MAX3541_NDIV_LO,(regs+ MAX3541_NDIV_LO),1) ; 
   I2C_IWriteBlock(MAX3541_RDIV,(regs+ MAX3541_RDIV),1) ; 
   I2C_IWriteBlock(MAX3541_IFOVLD,(regs+ MAX3541_IFOVLD),1) ; 
   I2C_IWriteBlock(MAX3541_CONTROL,(regs+MAX3541_CONTROL),1) ;
   I2C_IWriteBlock(MAX3541_TFS,(regs+MAX3541_TFS),1) ;
   I2C_IWriteBlock(MAX3541_TFP,(regs+MAX3541_TFP),1) ;
 
   /*  Set the Vco until there is lock  */
   Max3541VcoTune();

}
/* Read out Max3541 ROM table containing calibration constants
     and setup global variables with that data. */
/* IMPORTANT NOTE: Special Version!
	This version of Max3541ReadRom() is only to be used with the following parts:
   200735, 200741, and 200748.
	Please refer to subsequent versions of this software for other parts.
	This version hardcodes the ROM table values that are used in the Tracking Filter equations.
	It still needs to use the value in the first row of the ROM table for IFOVLD.
*/

void Max3541ReadRom()
 {
   UINT16 OD_Bits;
	unsigned char dbytes[9];
	unsigned char i;
   unsigned char outbyte[1];

	for (i=0; i<9; i++)     
	{
		outbyte[0] = i;
		I2C_IWriteBlock(MAX3541_TFROM_ADDR,outbyte,1);   /*Set Index in ROM in table to read back Bias*/
      I2C_IReadBlock(MAX3541_ROM_DATA,(dbytes+i),1); /*Read back from table.*/
	}

	outbyte[0] = 0;
	I2C_IWriteBlock(MAX3541_TFROM_ADDR,outbyte,1);   /* ReSet Index in ROM in table to 0 */

	/*  Get it the OD<2:0> bis from the results of the ROM table read above */
	OD_Bits = ( dbytes[0] & 0xE0 ) >> 5;	

/*   !!!WARNING!!!!!                                                         */
/********  Cut For Production Parts Starting Here: ***************************/
/*         This hard code section is just for pre-production part testing    */
/*         If you do not remove this section when production parts are       */
/*         available, you can have lot related failures of this driver!!!    */

    /*
	dbytes[1]=0x7a;
	dbytes[2]=0x30;
	dbytes[3]=0x58;
	dbytes[4]=0xa6;
	dbytes[5]=0x75;
	dbytes[6]=0x99;
	dbytes[7]=0xdd;
	dbytes[8]=0xdc;
    */

/*********End Cut Here ******************************************************/

   TFRomCoefs[VHF_L][SER0] = dbytes[1]/4;  /*'LS0 )*/
   TFRomCoefs[VHF_L][SER1] = ((dbytes[1] & 0x3 ) * 4) + ((dbytes[2] & 0xc0)/64);  /* 'LS1*/
   TFRomCoefs[VHF_L][PAR0] = dbytes[2] & 0x3f;  /*'LP0*/
   TFRomCoefs[VHF_L][PAR1] = (dbytes[3] & 0xf0)/16;  /*LP1 */
   TFRomCoefs[VHF_H][SER0] = dbytes[3] & 0xf;  /*'HS0 */
   TFRomCoefs[VHF_H][SER1] = (dbytes[4] & 0xf0)/16;  /*'HS1 */
   TFRomCoefs[VHF_H][PAR0] = dbytes[4] & 0xf;  /*'HP0 */
   TFRomCoefs[VHF_H][PAR1] = dbytes[5] / 16;  /*'HP1 */
   TFRomCoefs[UHF][SER0] =  ((dbytes[5]  & 0xf) * 16)  + (dbytes[6] / 16);  /*'US0 */
   TFRomCoefs[UHF][SER1] = ((dbytes[6] & 0xf) * 4) + (dbytes[7] / 64 );  /*'US1 */
   TFRomCoefs[UHF][PAR0] = ((dbytes[7] & 0x1f) * 4) + (dbytes[8]/64);  /*'UP0 */
   TFRomCoefs[UHF][PAR1] = dbytes[8] & 0x3f;  /*'UP1 */

	/* Copy the OD<2:0> bits into the IFOVLD register at bit positions IFOVLD<2:0>  */
   regs[MAX3541_IFOVLD] = (regs[MAX3541_IFOVLD] & 0xF) | (OD_Bits << 4);

 }

/*  This will set the Pre Filter in TEST Register 5 bits<1:0>  */
void Max3541SetPreFilter(UINT16 Freq)
{
	UINT16 PFmode=0;

	if (Freq < 100)
		PFmode = 0;   /*  VHF input and Low Pass Filter on */
    else if ( Freq < 350)
		PFmode = 1;	/* VHF input and Low Pass Filter off */
    else 
        PFmode = 2;  /* UHF Input and Low Pass Filter off */
  
    regs[MAX3541_CONTROL] = (regs[MAX3541_CONTROL] & 0xFC ) | PFmode;
}

/*As you tune in frequency,the tracking filter needs
to be set as a function of frequency.  Stored in the ROM on the
IC are two end points for the VHFL, VHFH, and UHF frequency bands. 
This routine performs the necessary function to calculate the
needed series and parallel capicitor values from these two end
points for the current frequency.  Once the value is calculated, 
it is loaded into the Tracking Filter Caps register and the 
internal capacitors are switched in tuning the tracking 
filter.
*/
void  Max3541SetTrackingFilter(UINT16 Freq)
{
	/*  Calculate the series and parallel capacitor values for the given frequency  */
	/*  band.  These values are then written to the registers.  This causes the     */
	/*  Max3541's internal series and parallel capacitors to change thus tuning the */
	/*  tracking filter to the proper frequency.                                    */
   
   UINT16 band,tfs,tfp;

   /*  Set the TF Bits (Tracking Filter Select) in IFOVLD reg bits TF<1:0> */
	/*  for the correct band based on the current frequency. */
   if (( Freq > 50) && ( Freq < 200 ))  /* VHF Low Band */
   {
        band = VHF_L;
   }
   else if (( Freq >= 200) & (Freq < 450) ) /* VHF High*/
   {
        band = VHF_H;
   }
   else{    /* UHF */
        band = UHF;
	}

	/* Write the TF<1:0> Bits into the IFOVLD register */
   regs[MAX3541_IFOVLD] = (regs[MAX3541_IFOVLD] & 0xFC ) | band; 

   //tfs = tfs_f(TFRomCoefs[band][SER0], TFRomCoefs[band][SER1], Freq, co[band*2]);
   //tfp = tfs_f(TFRomCoefs[band][PAR0], TFRomCoefs[band][PAR1], Freq, co[(band*2)+1]);
       	tfs = tfs_i(TFRomCoefs[band][SER0], TFRomCoefs[band][SER1],  Freq, co[band*2]);
         tfp = tfs_i(TFRomCoefs[band][PAR0],TFRomCoefs[band][PAR1],  Freq, co[(band*2)+1]);


	/* Write the TFS Bits into the Tracking Filter Series Capacitor register */
   regs[MAX3541_TFS] = tfs;

	/* Write the TFP Bits into the Tracking Filter Parallel Capacitor register */
   regs[MAX3541_TFP] = (regs[MAX3541_TFP] & 0xc0 ) | (0x3f &  tfp);

   /* Note:  Checking the tracking filter calculation.    */
	/* Tune to the frequencies below, if the calculation is done */
	/* correctly, the numbers for tfs and tfp will match the table.  */
   /* For the pow function approximation, the values can be slightly off  */
	/* at the higher end.  No more than 2% difference is allowed. */
	/*  */
	/*   Frequencey   tfs  tfp  */
	/*   ----------   ---  ---  */
	/*    50 MHz      255  63   */
	/*   100 MHz      111  27   */
	/*   250 MHz      143  29   */
	/*   470 MHz      206  20   */
	/*   858 MHz      14   2    */

}


// Floating point for comparison only.
//UINT16 tfs_f(UINT16 S0, UINT16 S1, float FreqRF, UINT16 c[5])
//{ 
//	
//	float y  = (c[0] * 0.1 + S0/256.0 * c[1] * 0.1) +
//         (c[2] * -0.1 +  S1/64.0 * 0.1 * c[3]) * FreqRF * 1e-3;
//   return (UINT16)( pow(10.0,y)-c[4]+0.5);
//}



/* calculate aproximation for Max3540 tracking filter useing integer math only */

UINT16 tfs_i(UINT16 S0, UINT16 S1, UINT16 FreqRF, UINT16 c[5])
{  UINT16 i,y,add,res;
   
   y=((256*c[0])+c[1]*S0)+(((-64*c[2]+S1*c[3])/5)*(FreqRF/2))/25;  
                    /* above sequence has been choosen to avoid overflows */
   y=(10*y)/111;                   /* approximation for nom*10*LN(10)/256 */
   add=y; res=100+y;
   for (i=2; i<12; i++)  
   { 
#ifdef HAVE_32BIT_MATH
     add=(add*y)/(i*100);          /* this only works with 32-bit math */
#else
     UINT32 a=long_mul(add,y);     /* on 16-bit compilers we need to use our own */
     add=long_div(a,i*100);        /* 32-bit math functions */
#endif
     res+=add;
   }
   return (res+50)/100-c[4];
}

#ifndef HAVE_32BIT_MATH

/* definitions and two routines to emulate 32-bit math in case the compiler does
   not support 32-bit integer math */

typedef struct { UINT16 hi,lo; } UINT32;   /* 32-bit integer for 16 bit compilers */

/* 16bit x 16bit multiplication with 32bit result */
UINT32 long_mul(UINT16 a, UINT16 b)
{ UINT16 a0,a1,b0,b1,d,d0,d1,e,e0,e1,f,f0,f1,g,g0,g1;
  UINT16 sum, carry, roll,pmax;
  UINT32 prod;
  a1=a>>8; a0=a-(a1<<8);
  b1=b>>8; b0=b-(b1<<8);
  d=a0*b0; d1=d>>8; d0=d-(d1<<8);
  e=a0*b1; e1=e>>8; e0=e-(e1<<8);
  f=a1*b0; f1=f>>8; f0=f-(f1<<8);
  g=a1*b1; g1=g>>8; g0=g-(g1<<8);
  sum=d1+e0+f0; carry=0; roll=256; pmax=roll-1;
  while (pmax<sum)
  { sum-=roll; carry++; }
  prod.lo=d0+(sum<<8);
  prod.hi=carry+e1+f1+g0+(g1<<8);
  return prod;
}

/* 32bit / 16bit division with 16bit result */
INT16 long_div(UINT32 n, UINT16 d)
{ INT16 remainder=0;
  UINT32 quotient;
  int i;
  quotient=n;
  for (i=0; i<32; i++)
  { UINT16 sbit= 32768 & quotient.hi;
    remainder<<=1;
    if (sbit) remainder|=1;
    quotient.hi<<=1;
    if (quotient.lo & 32768U)
      quotient.hi|=1;
    quotient.lo<<=1;
    if (remainder>=d)
    { remainder-=d;
      quotient.lo|=1;
    }
  }
  return quotient.lo;
}

#endif


/* set Max3541 PLL divider ratio's for a given RF frequency and
     given XTal frequency, both in MHz */
void Max3541SetSynth(UINT16 LoFreq)
{
   UINT16 VcoDiv,NDiv,RDiv;
   UINT16 Changeoverpt;

   mLo_Frequency =  LoFreq;
   RDiv = regs[MAX3541_RDIV] & 0x7f;
   Changeoverpt = 540;
    
   if (LoFreq >= Changeoverpt)
         VcoDiv = 0;
      else if (LoFreq >= (Changeoverpt >> 1))
         VcoDiv = 1;
      else if (LoFreq >= (Changeoverpt >> 2)) 
         VcoDiv = 2;
      else
         VcoDiv = 3;
      regs[MAX3541_VCO] = (regs[MAX3541_VCO] & 0xfc) | VcoDiv;
  
		
      NDiv = (10 * mLo_Frequency  *  RDiv / mXtalRef + 5) / 10 ;
     
      mLo_Frequency = NDiv / RDiv * mXtalRef;  /*  this is the actual Lo Freq given by NDiv */

	  regs[MAX3541_NDIV_HI] =  ((NDiv & 0x7F00) >> 8);
     regs[MAX3541_NDIV_LO] =  (NDiv & 0xFF);
      
}
/* This routine will step through the VCO's and check the LDO in the status register
to see if it has locked.  If the LDO is 0,1 or 6,7 then it is not locked, and the VCO is changed.
Otherwise the Pll is locked.
*/
void Max3541VcoTune(void)
{
	UINT16 vco,ldo;
	UINT16 done = 0;
	unsigned char dbytes[2];
    regs[MAX3541_VCO] |= 0x4 ;		/* Set LD in Reg3<2> = 1 */
		
	I2C_IWriteBlock(MAX3541_VCO, (regs  + MAX3541_VCO), 1);

	vco = 9;
	do 
	{
		regs[MAX3541_VCO] = (regs[MAX3541_VCO] & 0x7 ) | ( vco << 3 );
		
	   I2C_IWriteBlock(MAX3541_VCO, (regs  + MAX3541_VCO), 1);
		I2C_IReadBlock(MAX3541_STATUS, dbytes,1);
		ldo = (dbytes[0] & 0x70) >>4;

		if ((ldo < 2 ) && (vco>8))
			vco--;
		else if (ldo < 6 )
			done = 1;
		else if (ldo >= 6 )
			vco++;
		
	}while ((!done) & (vco<32));


	regs[MAX3541_VCO] &= 0xfb ;		/* Set LD in Reg3<2> = 0 */
	I2C_IWriteBlock(MAX3541_VCO, (regs  + MAX3541_VCO), 1);
}
	

/* This function will return the Lock Detect status of the chip, indicating whether it is tuned or not.
   The LD bit in REG3 is first set to 1 before the read. Then it is turned off after the read.
   if  1 < LDO < 6 Then the pll is locked
     else NOT locked.
*/
BOOL Max3541GetStatus(void)
{
    unsigned char dbytes[1],ldo;

	regs[MAX3541_VCO] |= 0x4 ;		/* Set LD in Reg3<2> = 1 */
	I2C_IWriteBlock(MAX3541_VCO, (regs  + MAX3541_VCO), 1);

   I2C_IReadBlock(MAX3541_STATUS, dbytes, 1);
	ldo = (dbytes[0] & 0x70 ) >>4  ; 

	regs[MAX3541_VCO] &= 0xfb ;		/* Set LD in Reg3<2> = 0 */
	I2C_IWriteBlock(MAX3541_VCO, (regs  + MAX3541_VCO), 1);

	if ((ldo<2 ) || (ldo>5))
		return FALSE;
	else
		return TRUE;
}

void Max3541_If_VGA(BOOL onoff)
{
	/* Turn on or off IF_VGA.  */
	if (onoff == TRUE)
	  regs[MAX3541_CONTROL] &= 0xfb;  /* IF VGA off, reg5<2> = 0 */
	else
	  regs[MAX3541_CONTROL] |= 4;  /* IF VGA off, reg5<2> = 1*/

   I2C_IWriteBlock(MAX3541_CONTROL, (regs + MAX3541_CONTROL), 1);
}

///*  Simple function that will read one register specified by reg */
//unsigned  char  Max3541Read(int reg)
//{
//    unsigned short dbytes[15];
//    I2C_IReadBlock(reg, dbytes, 1);
//	return dbytes[0]; 
//}
///*  Simple function that will write one register specified by reg */
//void Max3541Write(int reg,int value)
//{
//     unsigned short dbytes[2];
//     dbytes[0] = value;
//     I2C_Write(reg,dbytes, 1);
//}


/*============================================================================*/
/*                   STANDARD INCLUDE FILES                                   */
/*============================================================================*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>


/*============================================================================*/
/*                   PROJECT INCLUDE FILES                                    */
/*============================================================================*/
#include "dvb_frontend.h"

/*============================================================================*/
/*                   MACRO DEFINITION                                         */
/*============================================================================*/


/*============================================================================*/
/*                   TYPE DEFINITION                                          */
/*============================================================================*/
struct max3541_ctx {
    struct i2c_adapter*     i2c;
    u8 i2c_addr;
    struct dvb_frontend*    frontend;
};

static struct max3541_ctx*  ctx;
static int freq;


/*============================================================================*/
/*                   PUBLIC VARIABLES DEFINITION                              */
/*============================================================================*/


/*============================================================================*/
/*                   STATIC VARIABLES DECLARATIONS                            */
/*============================================================================*/
/*============================================================================*/
/*                   STATIC FUNCTIONS DECLARATIONS                            */
/*============================================================================*/

/*============================================================================*/
/*                   PUBLIC FUNCTIONS DEFINITIONS                             */
/*============================================================================*/

/*============================================================================*/
/* max3541_release                                                            */
/*============================================================================*/
int
max3541_release(
        struct dvb_frontend*    fe
        )
{
    /* free memory */
    kfree(ctx);
    ctx = NULL;
    fe->tuner_priv = NULL;

    printk("max3541_detached\n");
    return 0;
}

/*============================================================================*/
/* max3541_pll_init                                                           */
/*============================================================================*/
int
max3541_pll_init (
        struct dvb_frontend* fe
        )
{
    freq = 0;

    if (fe->ops.i2c_gate_ctrl)
        fe->ops.i2c_gate_ctrl(fe, 1);

    Max3541Standard(DVBT);
    Max3541Init(8,      // 4MHz XtalRef
                36,     // 36MHz IfFreq
                0);     // RfFreq

    Max3541_If_VGA(1);

    if (fe->ops.i2c_gate_ctrl)
        fe->ops.i2c_gate_ctrl(fe, 0);

    return 0;
}

/*============================================================================*/
/* max3541_pll_set                                                            */
/*============================================================================*/
int 
max3541_pll_set (
        struct dvb_frontend*            fe,
        struct dvb_frontend_parameters* params
        )
{
    if(freq == (params->frequency) / 1000000){
        printk("pll_set on the same freq (%d), do nothing\n",
               freq);
        return 0;
    }

    freq = params->frequency / 1000000;

    Max3541SetFrequency(freq);

    return 0;
}

/*============================================================================*/
/* max3541_attach                                                          */
/*============================================================================*/
static struct dvb_tuner_ops max3541_tuner_ops = {
    .info =
    {
        .name = "Maxim 3541",
        .frequency_min  = 174000000, /* TODO TBC */
        .frequency_max  = 864000000, /* TODO TBC */
        .frequency_step = 8000000
    },

    .set_params        = max3541_pll_set,
    .get_frequency     = 0,
    .get_bandwidth     = 0,
    .release           = max3541_release,
    .init              = max3541_pll_init
};

struct dvb_frontend *
max3541_attach (
        struct dvb_frontend*    fe,
        u8 i2c_addr,
        struct i2c_adapter*     i2c
        )
{
    /* allocate memory for the internal state */
    ctx = (struct max3541_ctx*)kmalloc(sizeof(struct max3541_ctx), GFP_KERNEL);
    if (ctx == NULL) {
        printk("can't allocate memory for ctx in max3541_attach\n");
        return NULL;
    }

    memset(ctx, 0, sizeof(struct max3541_ctx));

    ctx->i2c = i2c;
    ctx->i2c_addr = i2c_addr;
    fe->tuner_priv = ctx;

    memcpy(&fe->ops.tuner_ops, 
           &max3541_tuner_ops, 
           sizeof(max3541_tuner_ops));

    printk("max3541_attached\n");

    return fe;
}

EXPORT_SYMBOL(max3541_attach);

/*============================================================================*/
/* I2C_IWriteBlock                                                            */
/*============================================================================*/

void
I2C_IWriteBlock(u8 offset,
                unsigned char * regs,
                u8 len)
{
    u8 i;

    for(i=0; i<len; i++){
        I2C_IWriteByte(offset + i, *(regs + i));
    }
}

void
I2C_IWriteByte(u8 offset,
               u8 data)
{
    u8 buf[2] = {offset, data};
    
    struct i2c_msg tuner_write_msg = {.addr = ctx->i2c_addr, .buf = buf, .len = 2, .flags = 0};

    if (i2c_transfer(ctx->i2c, &tuner_write_msg, 1) != 1){
        printk("i2c_transfer error in I2C_IWriteBlock\n");
    }
}

void
I2C_IReadBlock(u8 offset,
               unsigned char * buf,
               u8 len)
{
    u8 b[] = {offset};
    int ret;
    
    struct i2c_msg msg[] = {{ .addr=ctx->i2c_addr, .flags=0, .buf=b, .len=1},
                            { .addr=ctx->i2c_addr, .flags=I2C_M_RD, .buf=buf, .len = len}};

    ret = i2c_transfer(ctx->i2c, msg, 2);

    if (ret != 2) {
        printk("%s: error reg=0x%x, ret=%i\n", 
                __FUNCTION__, 
                offset,
                ret);
    }

    return;
}

/*============================================================================*/
/*                   STATIC FUNCTIONS DEFINTIONS                              */
/*============================================================================*/


