//
// Copyright 2008 Free Software Foundation, Inc.
// 
// This file is part of GNU Radio
// 
// GNU Radio 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 asversion 3, or (at your option)
// any later version.
// 
// GNU Radio 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 GNU Radio; see the file COPYING.  If not, write to
// the Free Software Foundation, Inc., 51 Franklin Street,
// Boston, MA 02110-1301, USA.

#include <usrp/db_wbxng.h>
#include <usrp/db_wbxng_adf4350.h>
#include <db_base_impl.h>

// d'board i/o pin defs
// Tx and Rx have shared defs, but different i/o regs
#define ENABLE_5        (1 << 7)         // enables 5.0V power supply
#define ENABLE_33       (1 << 6)         // enables 3.3V supply
#define RX_TXN          (1 << 5)         // Tx only: T/R antenna switch for TX/RX port
#define RX2_RX1N        (1 << 5)         // Rx only: antenna switch between RX2 and TX/RX port
#define BBAMP_EN        (1 << 4)
#define PLL_CE          (1 << 3)
#define PLL_PDBRF       (1 << 2)
#define PLL_MUXOUT      (1 << 1)
#define PLL_LOCK_DETECT (1 << 0)

wbxng_base::wbxng_base(usrp_basic_sptr _usrp, int which, int _power_on)
  : db_base(_usrp, which), d_power_on(_power_on)
{
  /*
    @param usrp: instance of usrp.source_c
    @param which: which side: 0 or 1 corresponding to side A or B respectively
    @type which: int
  */

  usrp()->_write_oe(d_which, 0, 0xffff);   // turn off all outputs

  d_first = true;
  d_spi_format = SPI_FMT_MSB | SPI_FMT_HDR_0;

  _enable_refclk(false);                // disable refclk

  set_auto_tr(false);
}

wbxng_base::~wbxng_base()
{
  delete d_common;
}

void
wbxng_base::_write_all(int R, int control, int N)
{
  /*
    Write R counter latch, control latch and N counter latch to VCO.
    
    Adds 10ms delay between writing control and N if this is first call.
    This is the required power-up sequence.
    
    @param R: 24-bit R counter latch
    @type R: int
    @param control: 24-bit control latch
    @type control: int
    @param N: 24-bit N counter latch
    @type N: int
  */
  timespec t;
  t.tv_sec = 0;
  t.tv_nsec = 10000000;

  /*
  _write_R(R);
  _write_control(control);
  if(d_first) {
    //time.sleep(0.010);
    nanosleep(&t, NULL);
    d_first = false;
  }
  _write_N(N);
  */
}

void
wbxng_base::_write_control(int control)
{
  //_write_it((control & ~0x3) | 0);
}

void
wbxng_base::_write_R(int R)
{
  //_write_it((R & ~0x3) | 1);
}

void
wbxng_base::_write_N(int N)
{
  //_write_it((N & ~0x3) | 2);
}

void
wbxng_base::_write_it(int v)
{
  char s[3];
  s[0] = (char)((v >> 16) & 0xff);
  s[1] = (char)((v >>  8) & 0xff);
  s[2] = (char)(v & 0xff);
  std::string str(s, 3);
  //usrp()->_write_spi(0, d_spi_enable, d_spi_format, str);
}
        
bool
wbxng_base::_lock_detect()
{
  /*
    @returns: the value of the VCO/PLL lock detect bit.
    @rtype: 0 or 1
  */
  
  if(d_common->_get_locked()){
    return true;
  }
  else {      // Give it a second chance
    return false;
    /*
    // FIXME: make portable sleep
    timespec t;
    t.tv_sec = 0;
    t.tv_nsec = 100000000;
    nanosleep(&t, NULL);
    
    if(usrp()->read_io(d_which) & PLL_LOCK_DETECT) {
      return true;
    }
    else {
      return false;
    }
    */
  }
 
  throw std::runtime_error("_lock_detect called from wbxng_base\n");
}

/*
bool
wbxng_base::_compute_regs(double freq, int &retR, int &retcontrol,
			   int &retN, double &retfreq)
{
  **COMMENT**
    Determine values of R, control, and N registers, along with actual freq.
    
    @param freq: target frequency in Hz
    @type freq: float
    @returns: (R, control, N, actual_freq)
    @rtype: tuple(int, int, int, float)
    
    Override this in derived classes.
  **COMMENT**
  
  //raise NotImplementedError;
  throw std::runtime_error("_compute_regs called from wbxng_base\n");
}
*/

int
wbxng_base::_compute_control_reg()
{
  throw std::runtime_error("_compute_control_reg called from wbxng_base\n");
  //return d_common->_compute_control_reg();
}

int
wbxng_base::_refclk_divisor()
{
  throw std::runtime_error("_refclk_divisor called from wbxng_base\n");
  //return d_common->_refclk_divisor();
}

double
wbxng_base::_refclk_freq()
{
  throw std::runtime_error("_refclk_divisor called from wbxng_base\n");
  // *** TODO *** Magic Number 64e6?
  //return 64e6/_refclk_divisor();
}

struct freq_result_t
wbxng_base::set_freq(double freq)
{
  /*
    @returns (ok, actual_baseband_freq) where:
    ok is True or False and indicates success or failure,
    actual_baseband_freq is the RF frequency that corresponds to DC in the IF.
  */

  freq_t int_freq = (freq_t) (freq/1000);
  bool ok = d_common->_set_freq(int_freq);
  double freq_result = (double) d_common->_get_freq()*1000;
  struct freq_result_t args = {ok, freq_result};

  fprintf(stderr,"Setting WBXNG frequency, requested %d, obtained %f, lock_detect %d\n", 
          int_freq*1000, freq_result, _lock_detect());

  // Offsetting the LO helps get the Tx carrier leakage out of the way.
  // This also ensures that on Rx, we're not getting hosed by the
  // FPGA's DC removal loop's time constant.  We were seeing a
  // problem when running with discontinuous transmission.
  // Offsetting the LO made the problem go away.
  //freq += d_lo_offset;
  
  //int R, control, N;
  //double actual_freq;
  //_compute_regs(freq, R, control, N, actual_freq);

  //if(R==0) {
  //  return args;
  //}
   
  //_write_all(R, control, N);
  //args.ok = _lock_detect();
  //args.baseband_freq = actual_freq;
  return args;
}

bool
wbxng_base::_set_pga(float pga_gain)
{
  /*
  if(d_which == 0) {
    usrp()->set_pga(0, pga_gain);
    usrp()->set_pga(1, pga_gain);
  }
  else {
    usrp()->set_pga(2, pga_gain);
    usrp()->set_pga(3, pga_gain);
  }
  */
  return true;
}

bool
wbxng_base::is_quadrature()
{
  /*
    Return True if this board requires both I & Q analog channels.
    
    This bit of info is useful when setting up the USRP Rx mux register.
  */
  return true;
}

double
wbxng_base::freq_min()
{
  return (double) d_common->_get_min_freq();
}

double
wbxng_base::freq_max()
{
  return (double) d_common->_get_max_freq();
}

// ----------------------------------------------------------------

wbxng_base_tx::wbxng_base_tx(usrp_basic_sptr _usrp, int which, int _power_on)
  : wbxng_base(_usrp, which, _power_on)
{
  /*
    @param usrp: instance of usrp.sink_c
    @param which: 0 or 1 corresponding to side TX_A or TX_B respectively.
  */
  
  if(which == 0) {
    d_spi_enable = SPI_ENABLE_TX_A;
  }
  else {
    d_spi_enable = SPI_ENABLE_TX_B;
  }

  d_common = new adf4350(_usrp, d_which, d_spi_enable);
  
  // power up the transmit side, but don't enable the mixer
  usrp()->_write_oe(d_which,(PLL_CE|RX_TXN|ENABLE_33|ENABLE_5), (PLL_CE|RX_TXN|ENABLE_33|ENABLE_5));
  usrp()->write_io(d_which, (power_on()|PLL_CE|RX_TXN|ENABLE_33|ENABLE_5), (PLL_CE|RX_TXN|ENABLE_33|ENABLE_5));
  //set_lo_offset(4e6);

  //set_gain((gain_min() + gain_max()) / 2.0);  // initialize gain
}

wbxng_base_tx::~wbxng_base_tx()
{
  shutdown();
}


void
wbxng_base_tx::shutdown()
{
  // fprintf(stderr, "wbxng_base_tx::shutdown  d_is_shutdown = %d\n", d_is_shutdown);

  if (!d_is_shutdown){
    d_is_shutdown = true;
    // do whatever there is to do to shutdown

    // Power down and leave the T/R switch in the R position
    usrp()->write_io(d_which, (power_off()|RX_TXN), (PLL_CE|RX_TXN|ENABLE_33|ENABLE_5));

    /*
    // Power down VCO/PLL
    d_PD = 3;
  
    _write_control(_compute_control_reg());
    */
    _enable_refclk(false);                       // turn off refclk
    set_auto_tr(false);
  }
}

bool
wbxng_base_tx::set_auto_tr(bool on)
{
  bool ok = true;
  /*
  if(on) {
    ok &= set_atr_mask (RX_TXN | ENABLE_33 | ENABLE_5);
    ok &= set_atr_txval(0      | ENABLE_33 | ENABLE_5);
    ok &= set_atr_rxval(RX_TXN | 0);
  }
  else {
    ok &= set_atr_mask (0);
    ok &= set_atr_txval(0);
    ok &= set_atr_rxval(0);
  }
  */
  return ok;
}

bool
wbxng_base_tx::set_enable(bool on)
{
  /*
    Enable transmitter if on is true
  */

  int v;
  int mask = RX_TXN | ENABLE_5 | ENABLE_33;
  if(on) {
    v = PLL_CE | ENABLE_5 | ENABLE_33;
  }
  else {
    v = RX_TXN;
  }
  return usrp()->write_io(d_which, v, mask);
}

float
wbxng_base_tx::gain_min()
{
  return usrp()->pga_max();
}

float
wbxng_base_tx::gain_max()
{
  return usrp()->pga_max();
}

float
wbxng_base_tx::gain_db_per_step()
{
  return 1;
}

bool
wbxng_base_tx::set_gain(float gain)
{
  /*
    Set the gain.
    
    @param gain:  gain in decibels
    @returns True/False
  */
  return _set_pga(usrp()->pga_max());
}


/**************************************************************************/


wbxng_base_rx::wbxng_base_rx(usrp_basic_sptr _usrp, int which, int _power_on)
  : wbxng_base(_usrp, which, _power_on)
{
  /*
    @param usrp: instance of usrp.source_c
    @param which: 0 or 1 corresponding to side RX_A or RX_B respectively.
  */

  if(which == 0) {
    d_spi_enable = SPI_ENABLE_RX_A;
  }
  else {
    d_spi_enable = SPI_ENABLE_RX_B;
  }

  d_common = new adf4350(_usrp, d_which, d_spi_enable);

  usrp()->_write_oe(d_which, (RX2_RX1N|ENABLE_33|ENABLE_5), (RX2_RX1N|ENABLE_33|ENABLE_5));
  usrp()->write_io(d_which,  (power_on()|RX2_RX1N|ENABLE_33|ENABLE_5), (RX2_RX1N|ENABLE_33|ENABLE_5));
  
  // set up for RX on TX/RX port
  select_rx_antenna("TX/RX");
  
  bypass_adc_buffers(true);

  /*  
  set_lo_offset(-4e6);
  */
}

wbxng_base_rx::~wbxng_base_rx()
{
  shutdown();
}

void
wbxng_base_rx::shutdown()
{
  // fprintf(stderr, "wbxng_base_rx::shutdown  d_is_shutdown = %d\n", d_is_shutdown);

  if (!d_is_shutdown){
    d_is_shutdown = true;
    // do whatever there is to do to shutdown

    // Power down
    usrp()->common_write_io(C_RX, d_which, power_off(), (ENABLE_33|ENABLE_5));

    // Power down VCO/PLL
    d_PD = 3;
  

    // fprintf(stderr, "wbxng_base_rx::shutdown  before _write_control\n");
    //_write_control(_compute_control_reg());

    // fprintf(stderr, "wbxng_base_rx::shutdown  before _enable_refclk\n");
    _enable_refclk(false);                       // turn off refclk

    // fprintf(stderr, "wbxng_base_rx::shutdown  before set_auto_tr\n");
    set_auto_tr(false);

    // fprintf(stderr, "wbxng_base_rx::shutdown  after set_auto_tr\n");
  }
}

bool
wbxng_base_rx::set_auto_tr(bool on)
{
  //bool ok = true;
  /*
  if(on) {
    ok &= set_atr_mask (ENABLE_33|ENABLE_5);
    ok &= set_atr_txval(     0);
    ok &= set_atr_rxval(ENABLE_33|ENABLE_5);
  }
  else {
    ok &= set_atr_mask (0);
    ok &= set_atr_txval(0);
    ok &= set_atr_rxval(0);
  }
  */
  return true;
}

bool
wbxng_base_rx::select_rx_antenna(int which_antenna)
{
  /*
    Specify which antenna port to use for reception.
    @param which_antenna: either 'TX/RX' or 'RX2'
  */

  if(which_antenna == 0) {
    usrp()->write_io(d_which, 0,RX2_RX1N);
  }
  else if(which_antenna == 1) {
    usrp()->write_io(d_which, RX2_RX1N, RX2_RX1N);
  }
  else {
    return false;
    // throw std::invalid_argument("which_antenna must be either 'TX/RX' or 'RX2'\n");
  }
  return true;
}

bool
wbxng_base_rx::select_rx_antenna(const std::string &which_antenna)
{
  /*
    Specify which antenna port to use for reception.
    @param which_antenna: either 'TX/RX' or 'RX2'
  */

  
  if(which_antenna == "TX/RX") {
    usrp()->write_io(d_which, 0, RX2_RX1N);
  }
  else if(which_antenna == "RX2") {
    usrp()->write_io(d_which, RX2_RX1N, RX2_RX1N);
  }
  else {
    // throw std::invalid_argument("which_antenna must be either 'TX/RX' or 'RX2'\n");
    return false;
  }
  
  return true;
}

bool
wbxng_base_rx::set_gain(float gain)
{
  /*
    Set the gain.
    
    @param gain:  gain in decibels
    @returns True/False
  */
  
  /*
  // clamp gain
  gain = std::max(gain_min(), std::min(gain, gain_max()));

  float pga_gain, agc_gain;
  float V_maxgain, V_mingain, V_fullscale, dac_value;

  float maxgain = gain_max() - usrp()->pga_max();
  float mingain = gain_min();
  if(gain > maxgain) {
    pga_gain = gain-maxgain;
    assert(pga_gain <= usrp()->pga_max());
    agc_gain = maxgain;
  }
  else {
    pga_gain = 0;
    agc_gain = gain;
  }
  
  V_maxgain = .2;
  V_mingain = 1.2;
  V_fullscale = 3.3;
  dac_value = (agc_gain*(V_maxgain-V_mingain)/(maxgain-mingain) + V_mingain)*4096/V_fullscale;

  assert(dac_value>=0 && dac_value<4096);

  return (usrp()->write_aux_dac(d_which, 0, int(dac_value))
	  && _set_pga(int(pga_gain)));
  */
  return false;
}

// ----------------------------------------------------------------

db_wbxng_tx::db_wbxng_tx(usrp_basic_sptr usrp, int which)
  : wbxng_base_tx(usrp, which)
{
}

db_wbxng_tx::~db_wbxng_tx()
{
}

/*
bool
db_wbxng_tx::_compute_regs(double freq, int &retR, int &retcontrol,
				 int &retN, double &retfreq)
{
  return d_common->_compute_regs(_refclk_freq(), freq, retR,
				 retcontrol, retN, retfreq);
}
*/


db_wbxng_rx::db_wbxng_rx(usrp_basic_sptr usrp, int which)
  : wbxng_base_rx(usrp, which)
{
  set_gain((gain_min() + gain_max()) / 2.0);  // initialize gain
}

db_wbxng_rx::~db_wbxng_rx()
{
}

float
db_wbxng_rx::gain_min()
{
  return usrp()->pga_min();
}

float
db_wbxng_rx::gain_max()
{
  return usrp()->pga_max()+70;
}

float
db_wbxng_rx::gain_db_per_step()
{
  return 0.05;
}


bool
db_wbxng_rx::i_and_q_swapped()
{
  return true;
}

/*
bool
db_wbxng_rx::_compute_regs(double freq, int &retR, int &retcontrol,
				 int &retN, double &retfreq)
{
  return d_common->_compute_regs(_refclk_freq(), freq, retR,
				 retcontrol, retN, retfreq);
}
*/