/******************************************************************************
 * drivers/wireless/spirit/lib/spirit_management.c
 *
 *   Copyright(c) 2015 STMicroelectronics
 *   Author: VMA division - AMS
 *   Version 3.2.2 08-July-2015
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *   3. Neither the name of STMicroelectronics nor the names of its
 *      contributors may be used to endorse or promote products derived from
 *      this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************/

/******************************************************************************
 * Included Files
 ******************************************************************************/

#include <sys/types.h>
#include <assert.h>
#include <errno.h>

#include "spirit_commands.h"
#include "spirit_spi.h"
#include "spirit_radio.h"
#include "spirit_calibration.h"
#include "spirit_management.h"

/******************************************************************************
 * Pre-processor Definitions
 ******************************************************************************/

#define COMMUNICATION_STATE_TX          0
#define COMMUNICATION_STATE_RX          1
#define COMMUNICATION_STATE_NONE        2

/******************************************************************************
 * Private Data
 ******************************************************************************/

/* Factor is: B/2 used in the formula for SYNTH word calculation */

static const uint8_t g_vectc_bhalf[4] =
{
  (HIGH_BAND_FACTOR / 2),
  (MIDDLE_BAND_FACTOR / 2),
  (LOW_BAND_FACTOR / 2),
  (VERY_LOW_BAND_FACTOR / 2)
};

/* BS value to write in the SYNT0 register according to the selected band */

static const uint8_t g_vectc_bandreg[4] =
{
  SYNT0_BS_6, SYNT0_BS_12, SYNT0_BS_16, SYNT0_BS_32
};

/* BS value to write in the SYNT0 register according to the selected band */

/******************************************************************************
 * Private Functions
 ******************************************************************************/

/******************************************************************************
 * Name: spirit_management_set_basefrequency
 *
 * Description:
 *   spirit_management_set_basefrequency function only used in
 *   spirit_managment_wavco_calibration.
 *
 * Input Parameters:
 *   spirit - Reference to a Spirit library state structure instance
 *   fbase  - the base carrier frequency expressed in Hz as unsigned word.
 *
 * Returned Value:
 *   Zero (OK) is returned on success; A negated errno value is returned on
 *   any failure.
 *
 ******************************************************************************/

static int
  spirit_management_set_basefrequency(FAR struct spirit_library_s *spirit,
                                      uint32_t fbase)
{
  int32_t foffset;
  uint32_t synthword;
  uint32_t chspace;
  uint32_t fc;
  uint8_t band = 0;
  uint8_t anaregs[4];
  uint8_t wcp;
  uint8_t chnum;
  uint8_t refdiv;

  /* Check the parameter */

  DEBUGASSERT(IS_FREQUENCY_BAND(fbase));

  /* Search the operating band */

  if (IS_FREQUENCY_BAND_HIGH(fbase))
    {
      band = HIGH_BAND;
    }
  else if (IS_FREQUENCY_BAND_MIDDLE(fbase))
    {
      band = MIDDLE_BAND;
    }
  else if (IS_FREQUENCY_BAND_LOW(fbase))
    {
      band = LOW_BAND;
    }
  else if (IS_FREQUENCY_BAND_VERY_LOW(fbase))
    {
      band = VERY_LOW_BAND;
    }

  foffset = spirit_radio_get_foffset(spirit);
  chspace = spirit_radio_get_chspace(spirit);
  chnum   = spirit_radio_get_channel(spirit);

  /* Calculates the channel center frequency */

  fc = fbase + foffset + chspace * chnum;

  /* Reads the reference divider */

  refdiv = (uint8_t)spirit_radio_get_refdiv(spirit) + 1;

  switch (band)
    {
    case VERY_LOW_BAND:
      if (fc < 161281250)
        {
          spirit_calib_select_vco(spirit, VCO_L);
        }
      else
        {
          spirit_calib_select_vco(spirit, VCO_H);
        }
      break;

    case LOW_BAND:
      if (fc < 322562500)
        {
          spirit_calib_select_vco(spirit, VCO_L);
        }
      else
        {
          spirit_calib_select_vco(spirit, VCO_H);
        }
      break;

    case MIDDLE_BAND:
      if (fc < 430083334)
        {
          spirit_calib_select_vco(spirit, VCO_L);
        }
      else
        {
          spirit_calib_select_vco(spirit, VCO_H);
        }
      break;

    case HIGH_BAND:
      if (fc < 860166667)
        {
          spirit_calib_select_vco(spirit, VCO_L);
        }
      else
        {
          spirit_calib_select_vco(spirit, VCO_H);
        }
    }

  /* Search the VCO charge pump word and set the corresponding register */

  wcp = spirit_radio_search_wcp(spirit, fc);

  synthword = (uint32_t)
    (fbase * ((double)(FBASE_DIVIDER * refdiv * g_vectc_bhalf[band]) /
     spirit->xtal_frequency));

  /* Build the array of registers values for the analog part */

  anaregs[0] = (uint8_t)(((synthword >> 21) & 0x0000001f) | (wcp << 5));
  anaregs[1] = (uint8_t)((synthword >> 13) & 0x000000ff);
  anaregs[2] = (uint8_t)((synthword >> 5) & 0x000000ff);
  anaregs[3] = (uint8_t)(((synthword & 0x0000001f) << 3) |
                           g_vectc_bandreg[band]);

  /* Configures the needed Analog Radio registers */

  return spirit_reg_write(spirit, SYNT3_BASE, anaregs, 4);
}

/******************************************************************************
 * Public Functions
 ******************************************************************************/

/******************************************************************************
 * Name: spirit_managment_wavco_calibration
 *
 * Description:
 *   Perform VCO calbration WA.
 *
 * Input Parameters:
 *   spirit - Reference to a Spirit library state structure instance
 *
 * Returned Value:
 *   Zero (OK) is returned on success; A negated errno value is returned on
 *   any failure.
 *
 ******************************************************************************/

uint8_t spirit_managment_wavco_calibration(FAR struct spirit_library_s *spirit)
{
  uint32_t basefreq;
  uint8_t vco_rxword;
  uint8_t vco_txword;
  uint8_t tmp;
  bool restore = false;
  bool standby = false;
  int ret;

  /* Enable the reference divider if the XTAL is between 48 and 52 MHz */

  if (spirit->xtal_frequency > DOUBLE_XTAL_THR)
    {
      if (spirit_radio_get_refdiv(spirit) == S_DISABLE)
        {
          restore  = true;
          basefreq = spirit_radio_get_basefrequency(spirit);
          ret = spirit_radio_set_refdiv(spirit, S_ENABLE);

          ret = spirit_management_set_basefrequency(spirit, basefreq);
          if (ret < 0)
            {
              return ret;
            }
        }
    }

  basefreq = spirit_radio_get_basefrequency(spirit);

  /* Increase the VCO current */

  tmp = 0x19;
  ret = spirit_reg_write(spirit, 0xa1, &tmp, 1);
  if (ret < 0)
    {
      return ret;
    }

  ret = spirit_calib_enable_vco(spirit, S_ENABLE);
  if (ret < 0)
    {
      return ret;
    }

  ret = spirit_update_status(spirit);
  if (ret < 0)
    {
      return ret;
    }

  if (spirit->u.state.MC_STATE == MC_STATE_STANDBY)
    {
      standby = true;
      ret = spirit_command(spirit, CMD_READY);
      if (ret < 0)
        {
          return ret;
        }

      do
        {
          ret = spirit_update_status(spirit);
          if (ret < 0)
            {
              return ret;
            }

          if (spirit->u.state.MC_STATE == 0x13)
            {
              return -EIO;
            }
        }
      while (spirit->u.state.MC_STATE != MC_STATE_READY);
    }

  ret = spirit_command(spirit, CMD_LOCKTX);
  if (ret < 0)
    {
      return ret;
    }

  do
    {
      ret = spirit_update_status(spirit);
      if (ret < 0)
        {
          return ret;
        }

      if (spirit->u.state.MC_STATE == 0x13)
        {
          return -EIO;
        }
    }
  while (spirit->u.state.MC_STATE != MC_STATE_LOCK);

  vco_txword = spirit_calib_get_vcocal(spirit);

  ret = spirit_command(spirit, CMD_READY);
  if (ret < 0)
    {
      return ret;
    }

  ret = spirit_waitstatus(spirit, MC_STATE_READY, 5000);
  if (ret < 0)
    {
      return ret;
    }

  ret = spirit_command(spirit, CMD_LOCKRX);
  if (ret < 0)
    {
      return ret;
    }

  do
    {
      ret = spirit_update_status(spirit);
      if (ret < 0)
        {
          return ret;
        }

      if (spirit->u.state.MC_STATE == 0x13)
        {
          return -EIO;
        }
    }
  while (spirit->u.state.MC_STATE != MC_STATE_LOCK);

  vco_rxword = spirit_calib_get_vcocal(spirit);

  ret = spirit_command(spirit, CMD_READY);
  if (ret < 0)
    {
      return ret;
    }

  do
    {
      ret = spirit_update_status(spirit);
      if (ret < 0)
        {
          return ret;
        }

      if (spirit->u.state.MC_STATE == 0x13)
        {
          return 1;
        }
    }
  while (spirit->u.state.MC_STATE != MC_STATE_READY);

  if (standby)
    {
      ret = spirit_command(spirit, CMD_STANDBY);
      if (ret < 0)
        {
          return ret;
        }
    }

  ret = spirit_calib_enable_vco(spirit, S_DISABLE);
  if (ret < 0)
    {
      return ret;
    }

  /* Disable the reference divider if the XTAL is between 48 and 52 MHz */

  if (restore)
    {
      ret = spirit_radio_set_refdiv(spirit, S_DISABLE);
      if (ret < 0)
        {
          return ret;
        }

      ret = spirit_management_set_basefrequency(spirit, basefreq);
      if (ret < 0)
        {
          return ret;
        }
    }

  /* Restore the VCO current */

  tmp = 0x11;
  ret = spirit_reg_write(spirit, 0xa1, &tmp, 1);
  if (ret < 0)
    {
      return ret;
    }

  spirit_calib_set_vcotxcal(spirit, vco_txword);
  spirit_calib_set_vcorxcal(spirit, vco_rxword);

  return OK;
}

/******************************************************************************
 * Name: spirit_management_txstrobe
 *
 * Description:
 *
 * Input Parameters:
 *   spirit    - Reference to a Spirit library state structure instance
 *
 * Returned Value:
 *   Zero (OK) is returned on success; A negated errno value is returned on
 *   any failure.
 *
 ******************************************************************************/

int spirit_management_txstrobe(FAR struct spirit_library_s *spirit)
{
  if (spirit->commstate != COMMUNICATION_STATE_TX)
    {
      uint8_t tmp;
      int ret;

      /* To achieve the max output power */

      if (spirit->commfrequency >= 150000000 &&
          spirit->commfrequency <= 470000000)
        {
          /* Optimal setting for Tx mode only */

          ret = spirit_radio_set_outputload(spirit, LOAD_3_6_PF);
        }
      else
        {
          /* Optimal setting for Tx mode only */

          ret = spirit_radio_set_outputload(spirit, LOAD_0_PF);
        }

      if (ret < 0)
        {
          return ret;
        }

      /* Enable VCO_L buffer */

      tmp = 0x11;
      ret = spirit_reg_write(spirit, 0xa9, &tmp, 1);
      if (ret < 0)
        {
          return ret;
        }

      /* Set SMPS switching frequency */

      tmp = 0x20;
      ret = spirit_reg_write(spirit, PM_CONFIG1_BASE, &tmp, 1);
      if (ret < 0)
        {
          return ret;
        }

      spirit->commstate = COMMUNICATION_STATE_TX;
    }

  return OK;
}

/******************************************************************************
 * Name: spirit_management_rxstrobe
 *
 * Description:
 *
 * Input Parameters:
 *   spirit - Reference to a Spirit library state structure instance
 *
 * Returned Value:
 *   Zero (OK) is returned on success; A negated errno value is returned on
 *   any failure.
 *
 ******************************************************************************/

int spirit_management_rxstrobe(FAR struct spirit_library_s *spirit)
{
  uint8_t tmp;
  int ret;

  if (spirit->commstate != COMMUNICATION_STATE_RX)
    {
      /* Set SMPS switching frequency */

      tmp = 0x98;
      ret = spirit_reg_write(spirit, PM_CONFIG1_BASE, &tmp, 1);
      if (ret < 0)
        {
          return ret;
        }

      /* Set the correct CWC parameter */

      ret = spirit_radio_set_outputload(spirit, LOAD_0_PF);
      if (ret < 0)
        {
          return ret;
        }

      spirit->commstate = COMMUNICATION_STATE_RX;
    }

  return OK;
}

/******************************************************************************
 * Name: spirit_management_waextracurrent
 *
 * Description:
 *
 * Input Parameters:
 *   spirit - Reference to a Spirit library state structure instance
 *
 * Returned Value:
 *   Zero (OK) is returned on success; A negated errno value is returned on
 *   any failure.
 *
 ******************************************************************************/

int spirit_management_waextracurrent(FAR struct spirit_library_s *spirit)
{
  uint8_t tmp;
  int ret;

  tmp = 0xca;
  ret = spirit_reg_write(spirit, 0xb2, &tmp, 1);
  if (ret < 0)
    {
      return ret;
    }

  tmp = 0x04;
  ret = spirit_reg_write(spirit, 0xa8, &tmp, 1);
  if (ret < 0)
    {
      return ret;
    }

  /* Just a read to lose a few more microseconds */

  ret = spirit_reg_read(spirit, 0xa8, &tmp, 1);
  if (ret < 0)
    {
      return ret;
    }

  tmp = 0x00;
  ret = spirit_reg_write(spirit, 0xa8, &tmp, 1);
  return ret;
}

/******************************************************************************
 * Name: spirit_management_initcommstate
 *
 * Description:
 *   Initialize communication state
 *
 * Input Parameters:
 *   spirit    - Reference to a Spirit library state structure instance
 *   frequency - Desired communication frequency
 *
 * Returned Value:
 *   None
 *
 ******************************************************************************/

void spirit_management_initcommstate(FAR struct spirit_library_s *spirit,
                                     uint32_t frequency)
{
  spirit->commstate     = COMMUNICATION_STATE_NONE;
  spirit->commfrequency = frequency;
}