/* -*- c++ -*- */
/*
 * Copyright 2004,2008,2009 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 version 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.
 */

#ifndef INCLUDED_USRP_STANDARD_H
#define INCLUDED_USRP_STANDARD_H

#include <usrp/usrp_basic.h>
#include <boost/shared_ptr.hpp>
#include <usrp/usrp_tune_result.h>

class usrp_standard_tx;
class usrp_standard_rx;

typedef boost::shared_ptr<usrp_standard_tx> usrp_standard_tx_sptr;
typedef boost::shared_ptr<usrp_standard_rx> usrp_standard_rx_sptr;

/*!
 * \ingroup usrp
 */
class usrp_standard_common
{
  int	d_fpga_caps;		// capability register val

protected:
  usrp_standard_common(usrp_basic *parent);

public:
  /*!
   *\brief does the FPGA implement the final Rx half-band filter?
   * If it doesn't, the maximum decimation factor with proper gain 
   * is 1/2 of what it would otherwise be.
   */
  bool has_rx_halfband() const;

  /*!
   * \brief number of digital downconverters implemented in the FPGA
   * This will be 0, 1, 2 or 4.
   */
  int nddcs() const;

  /*!
   *\brief does the FPGA implement the initial Tx half-band filter?
   */
  bool has_tx_halfband() const;

  /*!
   * \brief number of digital upconverters implemented in the FPGA
   * This will be 0, 1, or 2.
   */
  int nducs() const;

  /*!
   * \brief Calculate the frequency to use for setting the digital up or down converter.
   *
   * \param target_freq is the desired RF frequency (Hz).
   * \param baseband_freq is the RF frequency that corresponds to DC in the IF coming from the d'board.
   * \param  fs is the sampling frequency.
   * \param[out] dxc_freq the frequency to program into the DDC (or DUC).
   * \param[out] inverted is true if we're operating in an inverted Nyquist zone.
   */
  static void calc_dxc_freq(double target_freq, double baseband_freq, double fs,
			    double *dxc_freq, bool *inverted);
};

/*!
 * \brief The C++ interface the receive side of the USRP
 * \ingroup usrp
 *
 * This is the recommended interface to USRP receive functionality
 * for applications that use the USRP but not GNU Radio.
 */
class usrp_standard_rx : public usrp_basic_rx, public usrp_standard_common
{
 private:
  static const int	MAX_CHAN = 4;
  unsigned int		d_decim_rate;
  int			d_nchan;
  int			d_sw_mux;
  int			d_hw_mux;
  double		d_rx_freq[MAX_CHAN];

 protected:
  usrp_standard_rx (int which_board,
		    unsigned int decim_rate,
		    int nchan = 1,
		    int mux = -1,
		    int mode = 0,
		    int fusb_block_size = 0,
		    int fusb_nblocks = 0,
		    const std::string fpga_filename = "",
		    const std::string firmware_filename = ""
		    );  // throws if trouble

  bool write_hw_mux_reg ();

 public:

  enum {
    FPGA_MODE_NORMAL     = 0x00,
    FPGA_MODE_LOOPBACK   = 0x01,
    FPGA_MODE_COUNTING   = 0x02,
    FPGA_MODE_COUNTING_32BIT   = 0x04
  };

  ~usrp_standard_rx ();

  /*!
   * \brief invokes constructor, returns shared_ptr or shared_ptr equivalent of 0 if trouble
   *
   * \param which_board	     Which USRP board on usb (not particularly useful; use 0)
   * \param decim_rate	     decimation factor
   * \param nchan	     number of channels
   * \param mux		     Rx mux setting, \sa set_mux
   * \param mode	     mode
   * \param fusb_block_size  fast usb xfer block size.  Must be a multiple of 512. 
   *                         Use zero for a reasonable default.
   * \param fusb_nblocks     number of fast usb URBs to allocate.  Use zero for a reasonable default. 
   * \param fpga_filename    Name of rbf file to load
   * \param firmware_filename Name of ihx file to load
   */
  static usrp_standard_rx_sptr make(int which_board,
				    unsigned int decim_rate,
				    int nchan = 1,
				    int mux = -1,
				    int mode = 0,
				    int fusb_block_size = 0,
				    int fusb_nblocks = 0,
				    const std::string fpga_filename = "",
				    const std::string firmware_filename = ""
				    );
  /*!
   * \brief Set decimator rate.  \p rate MUST BE EVEN and in [8, 256].
   *
   * The final complex sample rate across the USB is
   *   adc_freq () / decim_rate () * nchannels ()
   */
  bool set_decim_rate  (unsigned int rate);

  /*!
   * \brief Set number of active channels.  \p nchannels must be 1, 2 or 4.
   *
   * The final complex sample rate across the USB is
   *   adc_freq () / decim_rate () * nchannels ()
   */
  bool set_nchannels (int nchannels);

  /*!
   * \brief Set input mux configuration.
   *
   * This determines which ADC (or constant zero) is connected to 
   * each DDC input.  There are 4 DDCs.  Each has two inputs.
   *
   * <pre>
   * Mux value:
   *
   *    3                   2                   1                       
   *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
   * +-------+-------+-------+-------+-------+-------+-------+-------+
   * |   Q3  |   I3  |   Q2  |   I2  |   Q1  |   I1  |   Q0  |   I0  |
   * +-------+-------+-------+-------+-------+-------+-------+-------+
   *
   * Each 4-bit I field is either 0,1,2,3
   * Each 4-bit Q field is either 0,1,2,3 or 0xf (input is const zero)
   * All Q's must be 0xf or none of them may be 0xf
   * </pre>
   */
  bool set_mux  (int mux);

  /*!
   * Determine the appropriate Rx mux value as a function of the subdevice choosen
   * and the characteristics of the respective daughterboard.
   */
  int determine_rx_mux_value(const usrp_subdev_spec &ss);
  int determine_rx_mux_value(const usrp_subdev_spec &ss_a, const usrp_subdev_spec &ss_b);

  /*!
   * \brief set the frequency of the digital down converter.
   *
   * \p channel must be in the range [0,3].  \p freq is the center
   * frequency in Hz.  \p freq may be either negative or postive.
   * The frequency specified is quantized.  Use rx_freq to retrieve
   * the actual value used.
   */
  bool set_rx_freq (int channel, double freq);  

  /*!
   * \brief set fpga mode
   */
  bool set_fpga_mode (int mode);

  /*!
   * \brief Set the digital down converter phase register.
   *
   * \param channel	which ddc channel [0, 3]
   * \param phase	32-bit integer phase value.
   */
  bool set_ddc_phase(int channel, int phase);

  /*!
   * \brief Specify Rx data format.
   *
   * \param format	format specifier
   *
   * Rx data format control register
   *
   *     3                   2                   1                       
   *   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
   *  +-----------------------------------------+-+-+---------+-------+
   *  |          Reserved (Must be zero)        |B|Q|  WIDTH  | SHIFT |
   *  +-----------------------------------------+-+-+---------+-------+
   *
   *  SHIFT specifies arithmetic right shift [0, 15]
   *  WIDTH specifies bit-width of I & Q samples across the USB [1, 16] (not all valid)
   *  Q     if set deliver both I & Q, else just I
   *  B     if set bypass half-band filter.
   *
   * Right now the acceptable values are:
   *
   *   B  Q  WIDTH  SHIFT
   *   0  1    16     0
   *   0  1     8     8
   *
   * More valid combos to come.
   *
   * Default value is 0x00000300  16-bits, 0 shift, deliver both I & Q.
   */
  bool set_format(unsigned int format);

  static unsigned int make_format(int width=16, int shift=0,
				  bool want_q=true, bool bypass_halfband=false);
  static int format_width(unsigned int format);
  static int format_shift(unsigned int format);
  static bool format_want_q(unsigned int format);
  static bool format_bypass_halfband(unsigned int format);

  /*!
   * \brief High-level "tune" method.  Works for the single channel case.
   *
   * This method adjusts both the daughterboard LO and the DDC so that
   * target_freq ends up at DC in the complex baseband samples.
   *
   * \param chan  which DDC channel we're controlling (almost always 0).
   * \param db    the daughterboard we're controlling.
   * \param target_freq the RF frequency we want at DC in the complex baseband.
   * \param[out] result details how the hardware was configured.
   *
   * \returns true iff everything was successful.
   */
  bool tune(int chan, db_base_sptr db, double target_freq, usrp_tune_result *result);
  

  // ACCESSORS
  unsigned int decim_rate () const;
  double rx_freq (int channel) const;
  int nchannels () const;
  int mux () const;
  unsigned int format () const;

  // called in base class to derived class order
  bool start ();
  bool stop ();
};

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

/*!
 * \brief The C++ interface the transmit side of the USRP
 * \ingroup usrp
 *
 * This is the recommended interface to USRP transmit functionality
 * for applications that use the USRP but not GNU Radio.
 *
 * Uses digital upconverter (coarse & fine modulators) in AD9862...
 */
class usrp_standard_tx : public usrp_basic_tx, public usrp_standard_common
{
 public:
  enum coarse_mod_t {
    CM_NEG_FDAC_OVER_4,		// -32 MHz
    CM_NEG_FDAC_OVER_8,		// -16 MHz
    CM_OFF,
    CM_POS_FDAC_OVER_8,		// +16 MHz
    CM_POS_FDAC_OVER_4		// +32 MHz
  };

 protected:
  static const int	MAX_CHAN = 2;
  unsigned int		d_interp_rate;
  int			d_nchan;
  int			d_sw_mux;
  int			d_hw_mux;
  double		d_tx_freq[MAX_CHAN];
  coarse_mod_t		d_coarse_mod[MAX_CHAN];
  unsigned char		d_tx_modulator_shadow[MAX_CHAN];

  virtual bool set_coarse_modulator (int channel, coarse_mod_t cm);
  usrp_standard_tx::coarse_mod_t coarse_modulator (int channel) const;

 protected:
  usrp_standard_tx (int which_board,
		    unsigned int interp_rate,
		    int nchan = 1,
		    int	mux = -1,
		    int fusb_block_size = 0,
		    int fusb_nblocks = 0,
		    const std::string fpga_filename = "",
		    const std::string firmware_filename = ""
		    );	// throws if trouble

  bool write_hw_mux_reg ();

 public:
  ~usrp_standard_tx ();

  /*!
   * \brief invokes constructor, returns shared_ptr or shared_ptr equivalent of 0 if trouble
   *
   * \param which_board	     Which USRP board on usb (not particularly useful; use 0)
   * \param interp_rate	     interpolation factor
   * \param nchan	     number of channels
   * \param mux		     Tx mux setting, \sa set_mux
   * \param fusb_block_size  fast usb xfer block size.  Must be a multiple of 512. 
   *                         Use zero for a reasonable default.
   * \param fusb_nblocks     number of fast usb URBs to allocate.  Use zero for a reasonable default. 
   * \param fpga_filename    Name of rbf file to load
   * \param firmware_filename Name of ihx file to load
   */
  static usrp_standard_tx_sptr make(int which_board,
				    unsigned int interp_rate,
				    int nchan = 1,
				    int mux = -1,
				    int fusb_block_size = 0,
				    int fusb_nblocks = 0,
				    const std::string fpga_filename = "",
				    const std::string firmware_filename = ""
				    );

  /*!
   * \brief Set interpolator rate.  \p rate must be in [4, 512] and a multiple of 4.
   *
   * The final complex sample rate across the USB is
   *   dac_freq () / interp_rate () * nchannels ()
   */
  virtual bool set_interp_rate (unsigned int rate);

  /*!
   * \brief Set number of active channels.  \p nchannels must be 1 or 2.
   *
   * The final complex sample rate across the USB is
   *   dac_freq () / decim_rate () * nchannels ()
   */
  bool set_nchannels  (int nchannels);

  /*!
   * \brief Set output mux configuration.
   *
   * <pre>
   *     3                   2                   1                       
   *   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
   *  +-------------------------------+-------+-------+-------+-------+
   *  |                               | DAC3  | DAC2  | DAC1  |  DAC0 |
   *  +-------------------------------+-------+-------+-------+-------+
   * 
   *  There are two interpolators with complex inputs and outputs.
   *  There are four DACs.
   * 
   *  Each 4-bit DACx field specifies the source for the DAC and
   *  whether or not that DAC is enabled.  Each subfield is coded
   *  like this: 
   * 
   *     3 2 1 0
   *    +-+-----+
   *    |E|  N  |
   *    +-+-----+
   * 
   *  Where E is set if the DAC is enabled, and N specifies which
   *  interpolator output is connected to this DAC.
   * 
   *   N   which interp output
   *  ---  -------------------
   *   0   chan 0 I
   *   1   chan 0 Q
   *   2   chan 1 I
   *   3   chan 1 Q
   * </pre>
   */
  bool set_mux  (int mux);

  /*!
   * Determine the appropriate Tx mux value as a function of the subdevice choosen
   * and the characteristics of the respective daughterboard.
   */
  int determine_tx_mux_value(const usrp_subdev_spec &ss);
  int determine_tx_mux_value(const usrp_subdev_spec &ss_a, const usrp_subdev_spec &ss_b);

  /*!
   * \brief set the frequency of the digital up converter.
   *
   * \p channel must be in the range [0,1].  \p freq is the center
   * frequency in Hz.  It must be in the range [-44M, 44M].
   * The frequency specified is quantized.  Use tx_freq to retrieve
   * the actual value used.
   */
  virtual bool set_tx_freq (int channel, double freq);  // chan: [0,1]

  // ACCESSORS
  unsigned int interp_rate () const;
  double tx_freq (int channel) const;
  int nchannels () const;
  int mux () const;

  /*!
   * \brief High-level "tune" method.  Works for the single channel case.
   *
   * This method adjusts both the daughterboard LO and the DUC so that
   * DC in the complex baseband samples ends up at RF target_freq.
   *
   * \param chan  which DUC channel we're controlling (usually == which_side).
   * \param db    the daughterboard we're controlling.
   * \param target_freq the RF frequency we want our baseband translated to.
   * \param[out] result details how the hardware was configured.
   *
   * \returns true iff everything was successful.
   */
  bool tune(int chan, db_base_sptr db, double target_freq, usrp_tune_result *result);


  // called in base class to derived class order
  bool start ();
  bool stop ();
};

#endif /* INCLUDED_USRP_STANDARD_H */
