/*
 * 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.
 *
 * Copyright (C) 2002,2003 Florian Schirmer <jolt@tuxbox.org>
 *
 * Copyright (C) 2007,2008 NXP B.V.
 * All Rights Reserved.
 *
 * NXP based DVB adapter driver.
 * Based on the bt8xx driver by Florian Schirmer <jolt@tuxbox.org>
 *
*//*

Rev Date        Author        Comments
--------------------------------------------------------------------------------
  1             Ch. Grosse    First version
SOUsb225#15 20081210 batelaan Merged in cx24117 prototype version
SOUsb225#16 20081210 batelaan Merged in cx24116p
SOUsb225#17 20090127 batelaan Merged in GYX1D
SOUsb225#18 20090219 ocourson Merged in TDA10086/TDA8260
CFR_STB#17.3 20090302 cgrosse Reworked frontend_init function
CFR_STB#17.4 20090310 guo     Add Samsung ISDB-T FE driver (ie tdhl5 dvb4linux API compliant)
CFR_STB#17.5 20090313 guo     Add Thomson ISDB-T FE drivers (NuTune, dvb4linux API compliant)
CFR_STB#17.6 20090323 cgrosse Add NXP TDA18218 Silicon Tuner
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

#include "phStbDemux_common.h"
#include "phStbDemux_frontend.h"

#include <linux/ctype.h>

// For tuner configuration
//#include <prjconfig.h>

#include "tda1002x.h"

#define I2C_NAME(s) (s)->name

#define MAX_SUPPORTED_TUNER 2

#define DEMOD_I2C_ADDRESS_TDA10021_1 0x1A
#define DEMOD_I2C_ADDRESS_TDA10021_2 0x18

#define KHZ   1000

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                     [p]phStbDbg_<Word>+_t  *
*******************************************************************/
typedef enum {
  Demod_Model_TDA10021,
  Demod_Model_NONE,
  Demod_Model_COUNT
} Demod_Model_t;

typedef enum {
  Tuner_Model_CU1216,
  Tuner_Model_NONE,
  Tuner_Model_COUNT
} Tuner_Model_t;

typedef struct Demod_Model_Desc {
    Demod_Model_t  demod_model;
    char *         demod_name;        // e.g. "TDA10048"
    unsigned short * normal_i2c;      // list of poss. I2C addresses (demod addresses)
} Demod_Model_Desc_t;

typedef struct Tuner_Model_Desc {
    Tuner_Model_t  tuner_model;
    char *         tuner_name;        // e.g. "TD1316"
} Tuner_Model_Desc_t;

/******************************************************************
* STATIC FUNCTION PROTOTYPES                  <Module>_<Word>+    *
*******************************************************************/

static int dvb_phStb_attach_adapter (struct i2c_adapter *adapter);
static int dvb_phStb_detach_client (struct i2c_client *client);

static void gate_tuner_bus(struct dvb_frontend* fe, int gate_on);

/******************************************************************
* STATIC DATA                  g[k|p|kp|pk|kpk]<Module>_<Word>+   *
*******************************************************************/
/* Note:
   - If we have only one frontend connected (on TS0 or on TS1) it will be attached to adapter0.
   - If we have two identical frontends, the first one detected will be attached to adapter0,
   the second one will be attached to adapter1. (in respect normal_i2c_xxx list)
   - If we have two different frontends, demod0/tuner0 will be adapter0, and demod1/tuner1 will
   be adapter1.
*/

int debug;   // module parameter
char * demod0 = "TDA10021"; // module parameter - default value
char * demod1 = "TDA10021"; // module parameter - default value
char * tuner0 = "CU1216"; // module parameter - default value
char * tuner1 = "CU1216"; // module parameter - default value

static char * NoneString = "NONE";

/* gDemod_Model[0] will be installed on /dev/dvb/adapter0 */
/* gDemod_Model[1] will be installed on /dev/dvb/adapter1 */
static Demod_Model_t gDemod_Model[2] = {Demod_Model_NONE, Demod_Model_NONE};
static Tuner_Model_t gTuner_Model[2] = {Tuner_Model_NONE, Tuner_Model_NONE};


static unsigned short normal_i2c_TDA10021[] = {DEMOD_I2C_ADDRESS_TDA10021_1 >> 1, DEMOD_I2C_ADDRESS_TDA10021_2 >> 1, I2C_CLIENT_END};
static unsigned short normal_i2c_NONE[]     = {I2C_CLIENT_END};

#define NORMAL_I2C_LIST_SIZE (12)
static unsigned short normal_i2c_List[NORMAL_I2C_LIST_SIZE]   = {I2C_CLIENT_END};

/* normal_i2c_List is dynamically filled in phStbFrontend_init() */
/* example : if only one demod tda10048 -> normal_i2c_List = normal_i2c_TDA10048 */
/*           if two demods tda10048 -> normal_i2c_List = normal_i2c_TDA10048 */
/*           if demod tda10048 + tda10086 -> normal_i2c_List = normal_i2c_TDA10048 + normal_i2c_TDA10086*/

/* Info: normal_i2c_List[] is a list of possible i2c address for the demodulator chip */
/* i2c_probe() will try every address in this list */

static const Demod_Model_Desc_t supported_demods [Demod_Model_COUNT] = {
     { Demod_Model_TDA10021,   "TDA10021"  , normal_i2c_TDA10021},
     { Demod_Model_NONE,       "NONE"      , normal_i2c_NONE}
};

static const Tuner_Model_Desc_t supported_tuners [Tuner_Model_COUNT] = {
     { Tuner_Model_CU1216,         "CU1216"},
     { Tuner_Model_NONE,           "NONE"}
};

/* These values override the min/max capability returned by the demod and/or tuner driver */
/* These values are ignored with a sat frontend */
long gMin_Freq = (174000 * KHZ); // TODO: shouldn't this be 30MHz (lowest VHF) !
long gMax_Freq = (862000 * KHZ); // TODO: shouldn't this be 3GHz  (Highest UHF) !

/* I2C detection variables */
static int dvb_phStb_frontend_count = 0;
static struct i2c_driver dvb_phStb_i2c_driver;
struct i2c_client *dvb_phStb_i2c_client[MAX_SUPPORTED_TUNER];

/*
 * Generic i2c probe
 * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
 */

static unsigned short probe[2]           = { I2C_CLIENT_END, I2C_CLIENT_END };
static unsigned short ignore[2]          = { I2C_CLIENT_END, I2C_CLIENT_END };

static struct i2c_client_address_data dvb_phStb_address_data = {
        .normal_i2c             = 0, /* dynamically assigned */
        .probe                  = probe,
        .ignore                 = ignore,
};

static struct i2c_driver dvb_phStb_i2c_driver = {
        .driver = {
            .name = "dvb_phStb",
         },
        .id = I2C_DRIVERID_DVB,
        .attach_adapter = dvb_phStb_attach_adapter,
        .detach_client = dvb_phStb_detach_client,
};

void ConcatAddressList(unsigned short* dest, size_t size_dest, unsigned short* src, size_t size_src)
{
  unsigned int dest_cpt = 0;

  /* Check size */
    if (size_src > size_dest)
    return;
  /* find end of destination list */
  while (*dest != I2C_CLIENT_END)
  {
    dest++;
    dest_cpt++;
  }

  /* Check available space in destination */
  if ( (size_dest/sizeof(unsigned short))-dest_cpt < size_src/sizeof(unsigned short))
    return;

  /* copy source list in destination list */
  while (*src != I2C_CLIENT_END)
  {
      *dest = *src;
    dest++;
    src++;
  }
  /* set the end of list marker */
  *dest = I2C_CLIENT_END;
}

Demod_Model_t DemodModelFromString(char * string, unsigned short* i2c_address_list, size_t size_of_list)
{
	 int i;
   
	 for (i=0; i< Demod_Model_COUNT; i++)
   {
        if (0 == strcmp(string, supported_demods[i].demod_name))
        {
            ConcatAddressList(i2c_address_list, size_of_list, supported_demods[i].normal_i2c, sizeof(supported_demods[i].normal_i2c));
            break;
        }
   }
   if (i == Demod_Model_COUNT)
      return Demod_Model_NONE;
   else
      return supported_demods[i].demod_model;
}

Tuner_Model_t TunerModelFromString(char * string)
{
	 int i;
   
	 for (i=0; i< Tuner_Model_COUNT; i++)
   {
        if (0 == strcmp(string, supported_tuners[i].tuner_name))
        {
            break;
        }
   }
   if (i == Tuner_Model_COUNT)
      return Tuner_Model_NONE;
   else
      return supported_tuners[i].tuner_model;
}

void phStbFrontend_init(void)
{
            dprintk("dvb-phStb: entered phStbFrontend_init...\n"); 
   int i,j;

   dvb_phStb_frontend_count = 0;
   dvb_phStb_i2c_client[0]  = NULL;
   dvb_phStb_i2c_client[1]  = NULL;

   for (i=0;i<NORMAL_I2C_LIST_SIZE;i++)
   {
       normal_i2c_List[i] = I2C_CLIENT_END;
   }

   /* demod0 string to uppercase */
   for (j=0; demod0[j]!=0; j++)
   {
       demod0[j] = toupper(demod0[j]);
   }

   /* demod1 string to uppercase */
   for (j=0; demod1[j]!=0; j++)
   {
       demod1[j] = toupper(demod1[j]);
   }

   /* usually gDemod_Model[0]=demod0 and gDemod_Model[1]=demod1
      except in one case : demod0="NONE" demod1!="NONE".
      in that case gDemod_Model[0]=demod1 and gDemod_Model[1]=demod0 */
      
   if ((0 == strcmp(demod0, NoneString)) && (0 != strcmp(demod1, NoneString)))
   {
      gDemod_Model[0] = DemodModelFromString(demod1, &normal_i2c_List, sizeof(normal_i2c_List));
      gTuner_Model[0] = TunerModelFromString(tuner1);
   }
   else
   {
      gDemod_Model[0] = DemodModelFromString(demod0, &normal_i2c_List, sizeof(normal_i2c_List));
      gTuner_Model[0] = TunerModelFromString(tuner0);
      
      gDemod_Model[1] = DemodModelFromString(demod1, &normal_i2c_List, sizeof(normal_i2c_List));
      gTuner_Model[1] = TunerModelFromString(tuner1);
   }
   
   dvb_phStb_address_data.normal_i2c = normal_i2c_List;
   
   dprintk("gFE_Model[0]=%d \n", gDemod_Model[0]);
   dprintk("gTuner_Model[0]=%d \n", gTuner_Model[0]);
   dprintk("gFE_Model[1]=%d \n", gDemod_Model[1]);
   dprintk("gTuner_Model[1]=%d \n", gTuner_Model[1]);

   i2c_add_driver(&dvb_phStb_i2c_driver);
   
   return;

}

void phStbFrontend_exit(void)
{

   i2c_del_driver(&dvb_phStb_i2c_driver);

   return;
}

/**
 Gate tuner I2c bus on/off - bus should be disabled when not actively tuning

 @param fe frontend device
 @param gate_on Enable or disable tuner bus - 0: disable bus; !0: enable bus
*/
static void gate_tuner_bus(struct dvb_frontend* fe, int gate_on)
{
   if (fe != NULL && fe->ops.i2c_gate_ctrl)
   {
      fe->ops.i2c_gate_ctrl(fe, gate_on);
   }
}

static int philips_cu1216_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params)
{
            dprintk("dvb-phStb: entered philips_cu1216_pll_set...\n"); 
 struct dvb_phStb_card *this_device = (struct dvb_phStb_card *) fe->dvb->priv;
 u8 buf[4];
 struct i2c_msg msg = {.addr = 0x60,.flags = 0,.buf = buf,.len = sizeof(buf) };

#define TUNER_MUL 62500

 u32 div = (params->frequency + 36125000 + TUNER_MUL / 2) / TUNER_MUL;

 buf[0] = (div >> 8) & 0x7f;
 buf[1] = div & 0xff;
 buf[2] = 0xCe;
 buf[3] = (params->frequency < 174500000 ? 0x01 :
    params->frequency < 454000000 ? 0x02 : 0x04);
 if (i2c_transfer(this_device->i2c_adapter, &msg, 1) != 1)
  return -EIO;
 return 0;
}


//static struct tda10021_config cu1216_config[MAX_SUPPORTED_TUNER];
static struct tda10021_config tda10021_conf[MAX_SUPPORTED_TUNER];

static u8 read_pwm_cu1216(struct dvb_phStb_card *card)
{
            dprintk("dvb-phStb: entered read_pwm_cu1216...\n"); 
   u8 b = 0x34;
   u8 pwm;
   struct i2c_msg msg[] = { {.addr = 0x0c,.flags = 0,       .buf = &b,  .len = 1},
                            {.addr = 0x0c,.flags = I2C_M_RD,.buf = &pwm,.len = 1}
                          };

   if ((i2c_transfer(card->i2c_adapter, msg, 2) != 2) || (pwm == 0xff))
   {
  pwm = 0x48;
   }
   return pwm;
}

void frontend_init(struct dvb_phStb_card *card, int instance_count)
{
            dprintk("dvb-phStb: entered frontend_init...\n");
//   enum td1316_type        td1316type;

   /* This function is always called twice, one time with instance_count = 0     */
   /* another time with instance_count = 1                                       */
   /* If i2c_probe detect only one channel decoder, we will have                 */
   /* dvb_phStb_i2c_client[0] != 0 and dvb_phStb_i2c_client[1] = 0               */
   /* and the following code will be executed once                               */
   /* If i2c_probe detect two channel decoders, we will have                     */
   /* dvb_phStb_i2c_client[0] != 0 and dvb_phStb_i2c_client[1] != 0              */
   /* and the following code will be executed twice                              */

   if ((card->i2c_adapter) && (dvb_phStb_i2c_client[instance_count]))
   {
      switch (gDemod_Model[instance_count])
      {

      case Demod_Model_TDA10021:
          /* Set up the I2C address */
          tda10021_conf[instance_count].demod_address = dvb_phStb_i2c_client[instance_count]->addr;
//          card->fe = dvb_attach(tda10021_attach, &tda10021_conf[instance_count], card->i2c_adapter, read_pwm_tdhe1(card));
          card->fe = dvb_attach(tda10021_attach, &tda10021_conf[instance_count], card->i2c_adapter, read_pwm_cu1216(card));	  
          if (card->fe) {
              if (gTuner_Model[instance_count] == Tuner_Model_CU1216)
                  card->fe->ops.tuner_ops.set_params = philips_cu1216_pll_set;
          }
          break;
        default:
            card->fe = NULL;
            break;
      }

      if (card->fe != NULL)
      {

         /* if not sat frontend */
         card->fe->ops.info.frequency_min = gMin_Freq;
         card->fe->ops.info.frequency_max = gMax_Freq;


         if (dvb_register_frontend(&card->dvb_adapter, card->fe))
         {
            dprintk("dvb-phStb: Frontend registration failed!\n");
            dvb_frontend_detach(card->fe);
            card->fe = NULL;
         }
      }
   }
}


/* -------------------------- I2C Detection Functions ------------------------- */

static int dvb_phStb_detect_client (struct i2c_adapter *adapter, int address, int kind)
{
              dprintk("dvb-phStb: entered dvb_phStb_detect_client...\n");
    int rv;
    int tuner_nr = dvb_phStb_frontend_count;

    dprintk("%s: detecting dvb phStb client on address 0x%x (adapter %d)\n",
            THIS_MODULE_DESCRIPTION, address << 1, adapter->nr);

    /* Check for the maximum number of frontends connected and that they */
    /* have been found on the currect buses */
    if (dvb_phStb_frontend_count == MAX_SUPPORTED_TUNER)
    {
        return 0;
    }

    /* Check if the adapter supports the needed features */
    if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
    {
        return 0;
    }

    /* Create the I2C client data structure */
    dvb_phStb_i2c_client[tuner_nr] = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
    if (dvb_phStb_i2c_client[tuner_nr] == 0)
    {
        return -ENOMEM;
    }
    memset(dvb_phStb_i2c_client[tuner_nr], 0, sizeof(struct i2c_client));
    dvb_phStb_i2c_client[tuner_nr]->addr    = address; /* (address of the demod) */
    dvb_phStb_i2c_client[tuner_nr]->adapter = adapter;
    dvb_phStb_i2c_client[tuner_nr]->driver  = &dvb_phStb_i2c_driver;
    snprintf(I2C_NAME(dvb_phStb_i2c_client[tuner_nr]), sizeof(I2C_NAME(dvb_phStb_i2c_client[tuner_nr])) - 1, "dvb_phStb");

    /* Attach the Demux to the I2C adapter */
    rv = i2c_attach_client(dvb_phStb_i2c_client[tuner_nr]);
    if (rv) {
        dprintk("%s: Failed to attach to client on adapter %s (0x%x) - 0x%02X\n",
                THIS_MODULE_DESCRIPTION,
                I2C_NAME(adapter), adapter->id, rv);
        kfree(dvb_phStb_i2c_client[tuner_nr]);
        return rv;
    }
    dvb_phStb_frontend_count++;

    dprintk("%s: found device on adapter %s (0x%x) - 0x%02X\n",
            THIS_MODULE_DESCRIPTION,
            I2C_NAME(adapter), adapter->id, rv);

    return 0;
}

static int dvb_phStb_attach_adapter (struct i2c_adapter *adapter)
{
            dprintk("dvb-phStb: entered dvb_phStb_attach_adapter...\n");
   return i2c_probe(adapter, &dvb_phStb_address_data, &dvb_phStb_detect_client);
}

static int dvb_phStb_detach_client (struct i2c_client *client)
{
   int err;

   err = i2c_detach_client(client);
   if (err)
   {
      return err;
   }

   kfree(client);

   return 0;
}


