/***************************************************************************//**
 * @file em_adc.c
 * @brief Analog to Digital Converter (ADC) Peripheral API
 * @version 4.2.1
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
 * obligation to support this Software. Silicon Labs is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Silicon Labs will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 ******************************************************************************/

#include "em_adc.h"
#if defined( ADC_COUNT ) && ( ADC_COUNT > 0 )

#include "em_cmu.h"
#include "em_assert.h"
#include <stddef.h>

/***************************************************************************//**
 * @addtogroup EM_Library
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup ADC
 * @brief Analog to Digital Converter (ADC) Peripheral API
 * @{
 ******************************************************************************/

/*******************************************************************************
 *******************************   DEFINES   ***********************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/** Validation of ADC register block pointer reference for assert statements. */
#define ADC_REF_VALID(ref)    ((ref) == ADC0)

/** Max ADC clock */
#if defined( _SILICON_LABS_32B_PLATFORM_1 )
#define ADC_MAX_CLOCK    13000000
#else
#define ADC_MAX_CLOCK    16000000
#endif

/** Min ADC clock */
#define ADC_MIN_CLOCK    32000

/** Helper defines for selecting ADC calibration and DEVINFO register fields. */
#if defined( _DEVINFO_ADC0CAL0_1V25_GAIN_MASK )
#define DEVINFO_ADC0_GAIN1V25_MASK _DEVINFO_ADC0CAL0_1V25_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL0_GAIN1V25_MASK )
#define DEVINFO_ADC0_GAIN1V25_MASK _DEVINFO_ADC0CAL0_GAIN1V25_MASK
#endif

#if defined( _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT )
#define DEVINFO_ADC0_GAIN1V25_SHIFT _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_GAIN1V25_SHIFT )
#define DEVINFO_ADC0_GAIN1V25_SHIFT _DEVINFO_ADC0CAL0_GAIN1V25_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET1V25_MASK _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL0_OFFSET1V25_MASK )
#define DEVINFO_ADC0_OFFSET1V25_MASK _DEVINFO_ADC0CAL0_OFFSET1V25_MASK
#endif

#if defined( _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET1V25_SHIFT _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_OFFSET1V25_SHIFT )
#define DEVINFO_ADC0_OFFSET1V25_SHIFT _DEVINFO_ADC0CAL0_OFFSET1V25_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL0_2V5_GAIN_MASK )
#define DEVINFO_ADC0_GAIN2V5_MASK _DEVINFO_ADC0CAL0_2V5_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL0_GAIN2V5_MASK )
#define DEVINFO_ADC0_GAIN2V5_MASK _DEVINFO_ADC0CAL0_GAIN2V5_MASK
#endif

#if defined( _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT )
#define DEVINFO_ADC0_GAIN2V5_SHIFT _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_GAIN2V5_SHIFT )
#define DEVINFO_ADC0_GAIN2V5_SHIFT _DEVINFO_ADC0CAL0_GAIN2V5_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET2V5_MASK _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL0_OFFSET2V5_MASK )
#define DEVINFO_ADC0_OFFSET2V5_MASK _DEVINFO_ADC0CAL0_OFFSET2V5_MASK
#endif

#if defined( _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET2V5_SHIFT _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_OFFSET2V5_SHIFT )
#define DEVINFO_ADC0_OFFSET2V5_SHIFT _DEVINFO_ADC0CAL0_OFFSET2V5_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL1_VDD_GAIN_MASK )
#define DEVINFO_ADC0_GAINVDD_MASK _DEVINFO_ADC0CAL1_VDD_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL1_GAINVDD_MASK )
#define DEVINFO_ADC0_GAINVDD_MASK _DEVINFO_ADC0CAL1_GAINVDD_MASK
#endif

#if defined( _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT )
#define DEVINFO_ADC0_GAINVDD_SHIFT _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_GAINVDD_SHIFT )
#define DEVINFO_ADC0_GAINVDD_SHIFT _DEVINFO_ADC0CAL1_GAINVDD_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSETVDD_MASK _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL1_OFFSETVDD_MASK )
#define DEVINFO_ADC0_OFFSETVDD_MASK _DEVINFO_ADC0CAL1_OFFSETVDD_MASK
#endif

#if defined( _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSETVDD_SHIFT _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_OFFSETVDD_SHIFT )
#define DEVINFO_ADC0_OFFSETVDD_SHIFT _DEVINFO_ADC0CAL1_OFFSETVDD_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK )
#define DEVINFO_ADC0_GAIN5VDIFF_MASK _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL1_GAIN5VDIFF_MASK )
#define DEVINFO_ADC0_GAIN5VDIFF_MASK _DEVINFO_ADC0CAL1_GAIN5VDIFF_MASK
#endif

#if defined( _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT )
#define DEVINFO_ADC0_GAIN5VDIFF_SHIFT _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_GAIN5VDIFF_SHIFT )
#define DEVINFO_ADC0_GAIN5VDIFF_SHIFT _DEVINFO_ADC0CAL1_GAIN5VDIFF_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET5VDIFF_MASK _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL1_OFFSET5VDIFF_MASK )
#define DEVINFO_ADC0_OFFSET5VDIFF_MASK _DEVINFO_ADC0CAL1_OFFSET5VDIFF_MASK
#endif

#if defined( _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET5VDIFF_SHIFT _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_OFFSET5VDIFF_SHIFT )
#define DEVINFO_ADC0_OFFSET5VDIFF_SHIFT _DEVINFO_ADC0CAL1_OFFSET5VDIFF_SHIFT
#endif

#if defined( _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET2XVDD_MASK _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL2_OFFSET2XVDD_MASK )
#define DEVINFO_ADC0_OFFSET2XVDD_MASK _DEVINFO_ADC0CAL2_OFFSET2XVDD_MASK
#endif

#if defined( _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET2XVDD_SHIFT _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL2_OFFSET2XVDD_SHIFT )
#define DEVINFO_ADC0_OFFSET2XVDD_SHIFT _DEVINFO_ADC0CAL2_OFFSET2XVDD_SHIFT
#endif

/** @endcond */


/*******************************************************************************
 ***************************   LOCAL FUNCTIONS   *******************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/***************************************************************************//**
 * @brief
 *   Load ADC calibration register for a selected reference and conversion mode.
 *
 * @details
 *   During production, calibration values are stored in the device
 *   information page for internal references. Notice that for external references,
 *   calibration values must be determined explicitly, and this function
 *   will not modify the calibration register for external references.
 *
 * @param[in] adc
 *   Pointer to ADC peripheral register block.
 *
 * @param[in] ref
 *   Reference to load calibrated values for. No values are loaded for
 *   external references.
 *
 * @param[in] setScanCal
 *   Select scan mode (true) or single mode (false) calibration load.
 ******************************************************************************/
static void ADC_LoadDevinfoCal(ADC_TypeDef *adc,
                               ADC_Ref_TypeDef ref,
                               bool setScanCal)
{
  uint32_t calReg;
  uint32_t newCal;
  uint32_t mask;
  uint32_t shift;

  if (setScanCal)
  {
    shift = _ADC_CAL_SCANOFFSET_SHIFT;
    mask  = ~(_ADC_CAL_SCANOFFSET_MASK
#if defined( _ADC_CAL_SCANOFFSETINV_MASK )
              | _ADC_CAL_SCANOFFSETINV_MASK
#endif
              | _ADC_CAL_SCANGAIN_MASK);
  }
  else
  {
    shift = _ADC_CAL_SINGLEOFFSET_SHIFT;
    mask  = ~(_ADC_CAL_SINGLEOFFSET_MASK
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
              | _ADC_CAL_SINGLEOFFSETINV_MASK
#endif
              | _ADC_CAL_SINGLEGAIN_MASK);
  }

  calReg = adc->CAL & mask;
  newCal = 0;

  switch (ref)
  {
    case adcRef1V25:
      newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_GAIN1V25_MASK)
                 >> DEVINFO_ADC0_GAIN1V25_SHIFT)
                << _ADC_CAL_SINGLEGAIN_SHIFT;
      newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_OFFSET1V25_MASK)
                 >> DEVINFO_ADC0_OFFSET1V25_SHIFT)
                << _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
      newCal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_NEGSEOFFSET1V25_MASK)
                 >> _DEVINFO_ADC0CAL0_NEGSEOFFSET1V25_SHIFT)
                << _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
      break;

    case adcRef2V5:
      newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_GAIN2V5_MASK)
                 >> DEVINFO_ADC0_GAIN2V5_SHIFT)
                << _ADC_CAL_SINGLEGAIN_SHIFT;
      newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_OFFSET2V5_MASK)
                 >> DEVINFO_ADC0_OFFSET2V5_SHIFT)
                << _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
      newCal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_NEGSEOFFSET2V5_MASK)
                 >> _DEVINFO_ADC0CAL0_NEGSEOFFSET2V5_SHIFT)
                << _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
      break;

    case adcRefVDD:
      newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_GAINVDD_MASK)
                 >> DEVINFO_ADC0_GAINVDD_SHIFT)
                << _ADC_CAL_SINGLEGAIN_SHIFT;
      newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_OFFSETVDD_MASK)
                 >> DEVINFO_ADC0_OFFSETVDD_SHIFT)
                 << _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
      newCal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_MASK)
                 >> _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_SHIFT)
                << _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
      break;

    case adcRef5VDIFF:
      newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_GAIN5VDIFF_MASK)
                 >> DEVINFO_ADC0_GAIN5VDIFF_SHIFT)
                << _ADC_CAL_SINGLEGAIN_SHIFT;
      newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_OFFSET5VDIFF_MASK)
                 >> DEVINFO_ADC0_OFFSET5VDIFF_SHIFT)
                << _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
      newCal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_NEGSEOFFSET5VDIFF_MASK)
                 >> _DEVINFO_ADC0CAL1_NEGSEOFFSET5VDIFF_SHIFT)
                << _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
      break;

    case adcRef2xVDD:
      /* There is no gain calibration for this reference */
      newCal |= ((DEVINFO->ADC0CAL2 & DEVINFO_ADC0_OFFSET2XVDD_MASK)
                 >> DEVINFO_ADC0_OFFSET2XVDD_SHIFT)
                << _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
      newCal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_NEGSEOFFSET2XVDD_MASK)
                 >> _DEVINFO_ADC0CAL2_NEGSEOFFSET2XVDD_SHIFT)
                << _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
      break;

#if defined( _ADC_SINGLECTRLX_VREFSEL_VDDXWATT )
    case adcRefVddxAtt:
      newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_GAINVDD_MASK)
                 >> DEVINFO_ADC0_GAINVDD_SHIFT)
                << _ADC_CAL_SINGLEGAIN_SHIFT;
      newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_OFFSETVDD_MASK)
                 >> DEVINFO_ADC0_OFFSETVDD_SHIFT)
                << _ADC_CAL_SINGLEOFFSET_SHIFT;
      newCal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_MASK)
                 >> _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_SHIFT)
                << _ADC_CAL_SINGLEOFFSETINV_SHIFT;
      break;
#endif

    /* For external references, the calibration must be determined for the
       specific application and set by the user. Calibration data is also not
       available for the internal references adcRefVBGR, adcRefVEntropy and
       adcRefVBGRlow. */
    default:
      newCal = 0;
      break;
  }

  adc->CAL = calReg | (newCal << shift);
}

/** @endcond */

/*******************************************************************************
 **************************   GLOBAL FUNCTIONS   *******************************
 ******************************************************************************/

/***************************************************************************//**
 * @brief
 *   Initialize ADC.
 *
 * @details
 *   Initializes common parts for both single conversion and scan sequence.
 *   In addition, single and/or scan control configuration must be done, please
 *   refer to ADC_InitSingle() and ADC_InitScan() respectively.
 *
 *   On ADC architectures with the ADCn->SCANCHCONF register,
 *   ADC_ScanSingleEndedInit() and ADC_ScanDifferentialInit() can be used to
 *   assist scan conversion input setup.
 *
 * @note
 *   This function will stop any ongoing conversion.
 *
 * @param[in] adc
 *   Pointer to ADC peripheral register block.
 *
 * @param[in] init
 *   Pointer to ADC initialization structure.
 ******************************************************************************/
void ADC_Init(ADC_TypeDef *adc, const ADC_Init_TypeDef *init)
{
  uint32_t tmp;

  EFM_ASSERT(ADC_REF_VALID(adc));

  /* Make sure conversion is not in progress */
  adc->CMD = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP;

  tmp = ((uint32_t)(init->ovsRateSel) << _ADC_CTRL_OVSRSEL_SHIFT)
        | (((uint32_t)(init->timebase) << _ADC_CTRL_TIMEBASE_SHIFT)
          & _ADC_CTRL_TIMEBASE_MASK)
        | (((uint32_t)(init->prescale) << _ADC_CTRL_PRESC_SHIFT)
          & _ADC_CTRL_PRESC_MASK)
#if defined ( _ADC_CTRL_LPFMODE_MASK )
        | ((uint32_t)(init->lpfMode) << _ADC_CTRL_LPFMODE_SHIFT)
#endif
        | ((uint32_t)(init->warmUpMode) << _ADC_CTRL_WARMUPMODE_SHIFT);

  if (init->tailgate)
  {
    tmp |= ADC_CTRL_TAILGATE;
  }
  adc->CTRL = tmp;

  /* Set ADC EM2 clock configuration */
#if defined( _ADC_CTRL_ADCCLKMODE_MASK )
  BUS_RegMaskedWrite(&ADC0->CTRL,
                     _ADC_CTRL_ADCCLKMODE_MASK | _ADC_CTRL_ASYNCCLKEN_MASK,
                     init->em2ClockConfig << _ADC_CTRL_ASYNCCLKEN_SHIFT);
#endif

#if defined( _SILICON_LABS_32B_PLATFORM_2 )
  /* Fix for errata ADC_EXXX */
  ADC_IntClear(adc, ADC_IFC_SCANUF);
#endif
}


#if defined( _ADC_SCANINPUTSEL_MASK )
/***************************************************************************//**
 * @brief
 *   Clear ADC scan input configuration.
 *
 * @param[in] scanInit
 *   Struct to hold the scan configuration, input configuration.
 ******************************************************************************/
void ADC_ScanInputClear(ADC_InitScan_TypeDef *scanInit)
{
  /* Clear input configuration */

  /* Select none */
  scanInit->scanInputConfig.scanInputSel = 0xFFFFFFFF;
  scanInit->scanInputConfig.scanInputEn = 0;

  /* Default alternative negative inputs */
  scanInit->scanInputConfig.scanNegSel = _ADC_SCANNEGSEL_RESETVALUE;
}


/***************************************************************************//**
 * @brief
 *   Initialize ADC scan single-ended input configuration.
 *
 * @details
 *   Set configuration for ADC scan conversion with single-ended inputs. The
 *   ADC_InitScan_TypeDef struct updated from this function should be passed to
 *   ADC_InitScan().
 *
 * @param[in] inputGroup
 *   ADC scan input group. See section 25.3.4 in the reference manual for
 *   more information.
 *
 * @param[in] singleEndedSel
 *   APORT select.
 *
 * @return
 *   Scan ID of selected ADC input. ee section 25.3.4 in the reference manual for
 *   more information. Note that the returned integer represents the bit position
 *   in ADCn_SCANMASK set by this function. The accumulated mask is stored in
 *   scanInit->scanInputConfig->scanInputEn.
 ******************************************************************************/
uint32_t ADC_ScanSingleEndedInputAdd(ADC_InitScan_TypeDef *scanInit,
                                     ADC_ScanInputGroup_TypeDef inputGroup,
                                     ADC_PosSel_TypeDef singleEndedSel)
{
  uint32_t currentSel;
  uint32_t newSel;
  uint32_t scanId;

  scanInit->diff = false;

  /* Check for unsupported APORTs */
  EFM_ASSERT((singleEndedSel <= adcPosSelAPORT0YCH0) || (singleEndedSel >= adcPosSelAPORT0YCH15));

  /* Decode the input group select by shifting right by 3 */
  newSel = singleEndedSel >> 3;

  currentSel = (scanInit->scanInputConfig.scanInputSel >> (inputGroup * 8)) & 0xFF;

  /* If none selected */
  if (currentSel == 0xFF)
  {
    scanInit->scanInputConfig.scanInputSel &= ~(0xFF << (inputGroup * 8));
    scanInit->scanInputConfig.scanInputSel |= (newSel << (inputGroup * 8));
  }
  else if (currentSel == newSel)
  {
    /* Ok, but do nothing.  */
  }
  else
  {
    /* Invalid channel range. A range is already selected for this group. */
    EFM_ASSERT(false);
  }

  /* Update and return scan input enable mask (SCANMASK) */
  scanId = (inputGroup * 8) + (singleEndedSel & 0x7);
  EFM_ASSERT(scanId < 32);
  scanInit->scanInputConfig.scanInputEn |= 0x1 << scanId;
  return scanId;
}


/***************************************************************************//**
 * @brief
 *   Initialize ADC scan differential input configuration.
 *
 * @details
 *   Set configuration for ADC scan conversion with differential inputs. The
 *   ADC_InitScan_TypeDef struct updated by this function should be passed to
 *   ADC_InitScan().
 *
 * @param[in] scanInit
 *   Struct to hold the scan and input configuration.
 *
 * @param[in] inputGroup
 *   ADC scan input group. See section 25.3.4 in the reference manual for
 *   more information.
 *
 * @param[in] posSel
 *   APORT bus pair select. The negative terminal is implicitly selected by
 *   the positive terminal.
 *
 * @param[in] negInput
 *   ADC scan alternative negative input. Set to adcScanNegInputDefault to select
 *   default negative input (implicit from posSel).
 *
 * @return
 *   Scan ID of selected ADC input. ee section 25.3.4 in the reference manual for
 *   more information. Note that the returned integer represents the bit position
 *   in ADCn_SCANMASK set by this function. The accumulated mask is stored in
 *   scanInit->scanInputConfig->scanInputEn.
 ******************************************************************************/
uint32_t ADC_ScanDifferentialInputAdd(ADC_InitScan_TypeDef *scanInit,
                                      ADC_ScanInputGroup_TypeDef inputGroup,
                                      ADC_PosSel_TypeDef posSel,
                                      ADC_ScanNegInput_TypeDef negInput)
{
  uint32_t negInputRegMask = 0;
  uint32_t negInputRegShift = 0;
  uint32_t negInputRegVal = 0;
  uint32_t scanId = 0;

  /* Do a single ended init, then update for differential scan. */
  scanId = ADC_ScanSingleEndedInputAdd(scanInit, inputGroup, posSel);

  /* Reset to differential mode */
  scanInit->diff = true;

  /* Set negative ADC input, unless the default is selected. */
  if (negInput != adcScanNegInputDefault)
  {
    if (scanId == 0)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT0NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT0NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 0);
    }
    else if (scanId == 2)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT2NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT2NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 0);
    }
    else if (scanId == 4)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT4NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT4NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 0);
    }
    else if (scanId == 6)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT6NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT6NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 0);
    }
    else if (scanId == 9)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT9NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT9NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 1);
    }
    else if (scanId == 11)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT11NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT11NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 1);
    }
    else if (scanId == 13)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT13NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT13NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 1);
    }
    else if (scanId == 15)
    {
      negInputRegMask  = _ADC_SCANNEGSEL_INPUT15NEGSEL_MASK;
      negInputRegShift = _ADC_SCANNEGSEL_INPUT15NEGSEL_SHIFT;
      EFM_ASSERT(inputGroup == 1);
    }
    else
    {
      /* There is not negative input option for this positive input (negInput is posInput + 1). */
      EFM_ASSERT(false);
    }

    /* Find ADC_SCANNEGSEL_CHxNSEL value for positive input 0, 2, 4 and 6 */
    if (inputGroup == 0)
    {
      switch (negInput)
      {
        case adcScanNegInput1:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT1;
          break;

        case adcScanNegInput3:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT3;
          break;

        case adcScanNegInput5:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT5;
          break;

        case adcScanNegInput7:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT7;
          break;

        default:
          /* Invalid selection. Options are input 1, 3, 5 and 7. */
          EFM_ASSERT(false);
          break;
      }
    }
    else if (inputGroup == 1)
    {
      /* Find ADC_SCANNEGSEL_CHxNSEL value for positive input 9, 11, 13 and 15 */
      switch (negInput)
      {
        case adcScanNegInput8:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT8;
          break;

        case adcScanNegInput10:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT10;
          break;

        case adcScanNegInput12:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT12;
          break;

        case adcScanNegInput14:
          negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT14;
          break;

        default:
          /* Invalid selection. Options are input 8, 10, 12 and 14. */
          EFM_ASSERT(false);
          break;
      }
    }
    else
    {
      /* No alternative negative input for input group > 1 */
      EFM_ASSERT(false);
    }

    /* Update config */
    scanInit->scanInputConfig.scanNegSel &= ~negInputRegMask;
    scanInit->scanInputConfig.scanNegSel |= negInputRegVal << negInputRegShift;
  }
  return scanId;
}
#endif


/***************************************************************************//**
 * @brief
 *   Initialize ADC scan sequence.
 *
 * @details
 *   Please refer to ADC_Start() for starting scan sequence.
 *
 *   When selecting an external reference, the gain and offset calibration
 *   must be set explicitly (CAL register). For other references, the
 *   calibration is updated with values defined during manufacturing.
 *
 * @note
 *   This function will stop any ongoing scan sequence.
 *
 * @param[in] adc
 *   Pointer to ADC peripheral register block.
 *
 * @param[in] init
 *   Pointer to ADC initialization structure.
 ******************************************************************************/
void ADC_InitScan(ADC_TypeDef *adc, const ADC_InitScan_TypeDef *init)
{
  uint32_t tmp;

  EFM_ASSERT(ADC_REF_VALID(adc));

  /* Make sure scan sequence is not in progress */
  adc->CMD = ADC_CMD_SCANSTOP;

  /* Load calibration data for selected reference */
  ADC_LoadDevinfoCal(adc, init->reference, true);

  tmp = 0
#if defined ( _ADC_SCANCTRL_PRSSEL_MASK )
        | (init->prsSel << _ADC_SCANCTRL_PRSSEL_SHIFT)
#endif
        | (init->acqTime << _ADC_SCANCTRL_AT_SHIFT)
#if defined ( _ADC_SCANCTRL_INPUTMASK_MASK )
        | init->input
#endif
        | (init->resolution << _ADC_SCANCTRL_RES_SHIFT);

  if (init->prsEnable)
  {
    tmp |= ADC_SCANCTRL_PRSEN;
  }

  if (init->leftAdjust)
  {
    tmp |= ADC_SCANCTRL_ADJ_LEFT;
  }

#if defined( _ADC_SCANCTRL_INPUTMASK_MASK )
  if (init->diff)
#elif defined( _ADC_SCANINPUTSEL_MASK )
  if (init->diff)
#endif
  {
    tmp |= ADC_SCANCTRL_DIFF;
  }

  if (init->rep)
  {
#if defined( _SILICON_LABS_32B_PLATFORM_2 )
  /* Scan repeat mode does not work on platform 2 as described in errata  ADC_EXXX. */
  EFM_ASSERT(false);
#endif
    tmp |= ADC_SCANCTRL_REP;
  }

  /* Set scan reference. Check if reference configuraion is extended to SCANCTRLX. */
#if defined ( _ADC_SCANCTRLX_VREFSEL_MASK )
  if (init->reference & ADC_CTRLX_VREFSEL_REG)
  {
    /* Select extension register */
    tmp |= ADC_SCANCTRL_REF_CONF;
  }
  else
  {
    tmp |= init->reference << _ADC_SCANCTRL_REF_SHIFT;
  }
#else
  tmp |= init->reference << _ADC_SCANCTRL_REF_SHIFT;
#endif

#if defined( _ADC_SCANCTRL_INPUTMASK_MASK )
  tmp |= init->input;
#endif

  adc->SCANCTRL = tmp;

  /* Update SINGLECTRLX for reference select and PRS select */
#if defined ( _ADC_SCANCTRLX_MASK )
  tmp = adc->SCANCTRLX & ~(_ADC_SCANCTRLX_VREFSEL_MASK
                         | _ADC_SCANCTRLX_PRSSEL_MASK
                         | _ADC_SCANCTRLX_FIFOOFACT_MASK);
  if (init->reference & ADC_CTRLX_VREFSEL_REG)
  {
    tmp |= (init->reference & ~ADC_CTRLX_VREFSEL_REG) << _ADC_SCANCTRLX_VREFSEL_SHIFT;
  }

  tmp |= init->prsSel << _ADC_SCANCTRLX_PRSSEL_SHIFT;

  if (init->fifoOverwrite)
  {
    tmp |= ADC_SCANCTRLX_FIFOOFACT_OVERWRITE;
  }

  adc->SCANCTRLX = tmp;
#endif

#if defined( _ADC_CTRL_SCANDMAWU_MASK )
  BUS_RegBitWrite(&adc->CTRL, _ADC_CTRL_SCANDMAWU_SHIFT, init->scanDmaEm2Wu);
#endif

  /* Write scan input configuration */
#if defined( _ADC_SCANINPUTSEL_MASK )
  adc->SCANINPUTSEL = init->scanInputConfig.scanInputSel;
  adc->SCANMASK     = init->scanInputConfig.scanInputEn;
  adc->SCANNEGSEL   = init->scanInputConfig.scanNegSel;
#endif

  /* Assert for any APORT bus conflicts programming errors */
#if defined( _ADC_BUSCONFLICT_MASK )
  tmp = adc->BUSREQ;
  EFM_ASSERT(!(tmp & adc->BUSCONFLICT));
  EFM_ASSERT(!(adc->STATUS & _ADC_STATUS_PROGERR_MASK));
#endif
}


/***************************************************************************//**
 * @brief
 *   Initialize single ADC sample conversion.
 *
 * @details
 *   Please refer to ADC_Start() for starting single conversion.
 *
 *   When selecting an external reference, the gain and offset calibration
 *   must be set explicitly (CAL register). For other references, the
 *   calibration is updated with values defined during manufacturing.
 *
 * @note
 *   This function will stop any ongoing single conversion.
 *
 * @param[in] adc
 *   Pointer to ADC peripheral register block.
 *
 * @param[in] init
 *   Pointer to ADC initialization structure.
 ******************************************************************************/
void ADC_InitSingle(ADC_TypeDef *adc, const ADC_InitSingle_TypeDef *init)
{
  uint32_t tmp;

  EFM_ASSERT(ADC_REF_VALID(adc));

  /* Make sure single conversion is not in progress */
  adc->CMD = ADC_CMD_SINGLESTOP;

  /* Load calibration data for selected reference */
  ADC_LoadDevinfoCal(adc, init->reference, false);

  tmp = 0
#if defined( _ADC_SINGLECTRL_PRSSEL_MASK )
        | (init->prsSel << _ADC_SINGLECTRL_PRSSEL_SHIFT)
#endif
        | (init->acqTime << _ADC_SINGLECTRL_AT_SHIFT)
#if defined( _ADC_SINGLECTRL_INPUTSEL_MASK )
        | (init->input << _ADC_SINGLECTRL_INPUTSEL_SHIFT)
#endif
#if defined( _ADC_SINGLECTRL_POSSEL_MASK )
        | (init->posSel << _ADC_SINGLECTRL_POSSEL_SHIFT)
#endif
#if defined( _ADC_SINGLECTRL_NEGSEL_MASK )
        | (init->negSel << _ADC_SINGLECTRL_NEGSEL_SHIFT)
#endif
        | ((uint32_t)(init->resolution) << _ADC_SINGLECTRL_RES_SHIFT);

  if (init->prsEnable)
  {
    tmp |= ADC_SINGLECTRL_PRSEN;
  }

  if (init->leftAdjust)
  {
    tmp |= ADC_SINGLECTRL_ADJ_LEFT;
  }

  if (init->diff)
  {
    tmp |= ADC_SINGLECTRL_DIFF;
  }

  if (init->rep)
  {
    tmp |= ADC_SINGLECTRL_REP;
  }

  /* Set single reference. Check if reference configuraion is extended to SINGLECTRLX. */
#if defined ( _ADC_SINGLECTRLX_MASK )
  if (init->reference & ADC_CTRLX_VREFSEL_REG)
  {
    /* Select extension register */
    tmp |= ADC_SINGLECTRL_REF_CONF;
  }
  else
  {
    tmp |= (init->reference << _ADC_SINGLECTRL_REF_SHIFT);
  }
#else
  tmp |= (init->reference << _ADC_SINGLECTRL_REF_SHIFT);
#endif
  adc->SINGLECTRL = tmp;

  /* Update SINGLECTRLX for reference select and PRS select */
#if defined ( _ADC_SINGLECTRLX_VREFSEL_MASK )
  tmp = adc->SINGLECTRLX & (_ADC_SINGLECTRLX_VREFSEL_MASK
                          | _ADC_SINGLECTRLX_PRSSEL_MASK
                          | _ADC_SINGLECTRLX_FIFOOFACT_MASK);
  if (init->reference & ADC_CTRLX_VREFSEL_REG)
  {
    tmp |= ((init->reference & ~ADC_CTRLX_VREFSEL_REG) << _ADC_SINGLECTRLX_VREFSEL_SHIFT);
  }

  tmp |= ((init->prsSel << _ADC_SINGLECTRLX_PRSSEL_SHIFT));

  if (init->fifoOverwrite)
  {
    tmp |= ADC_SINGLECTRLX_FIFOOFACT_OVERWRITE;
  }

  adc->SINGLECTRLX = tmp;
#endif

  /* Set DMA availability in EM2 */
#if defined( _ADC_CTRL_SINGLEDMAWU_MASK )
  BUS_RegBitWrite(&ADC0->CTRL, _ADC_CTRL_SINGLEDMAWU_SHIFT, init->singleDmaEm2Wu);
#endif

  /* Assert for any APORT bus conflicts programming errors */
#if defined( _ADC_BUSCONFLICT_MASK )
  tmp = adc->BUSREQ;
  EFM_ASSERT(!(tmp & adc->BUSCONFLICT));
  EFM_ASSERT(!(adc->STATUS & _ADC_STATUS_PROGERR_MASK));
#endif
}


#if defined( _ADC_SCANDATAX_MASK )
/***************************************************************************//**
 * @brief
 *   Get scan result and scan select ID.
 *
 * @note
 *   Only use if scan data valid. This function does not check the DV flag.
 *   The return value is intended to be used as a index for the scan select ID.
 *
 * @param[in] adc
 *   Pointer to ADC peripheral register block.
 *
 * @param[out] scanId
 *   Scan select ID of first data in scan FIFO.
 *
 * @return
 *   First scan data in scan FIFO.
 ******************************************************************************/
uint32_t ADC_DataIdScanGet(ADC_TypeDef *adc, uint32_t *scanId)
{
  uint32_t scanData;

  /* Pop data FIFO with scan ID */
  scanData = adc->SCANDATAX;
  *scanId = (scanData & _ADC_SCANDATAX_SCANINPUTID_MASK) >> _ADC_SCANDATAX_SCANINPUTID_SHIFT;
  return (scanData & _ADC_SCANDATAX_DATA_MASK) >> _ADC_SCANDATAX_DATA_SHIFT;
}
#endif


/***************************************************************************//**
 * @brief
 *   Calculate prescaler value used to determine ADC clock.
 *
 * @details
 *   The ADC clock is given by: HFPERCLK / (prescale + 1).
 *
 * @param[in] adcFreq ADC frequency wanted. The frequency will automatically
 *   be adjusted to be within valid range according to reference manual.
 *
 * @param[in] hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to
 *   use currently defined HFPER clock setting.
 *
 * @return
 *   Prescaler value to use for ADC in order to achieve a clock value
 *   <= @p adcFreq.
 ******************************************************************************/
uint8_t ADC_PrescaleCalc(uint32_t adcFreq, uint32_t hfperFreq)
{
  uint32_t ret;

  /* Make sure selected ADC clock is within valid range */
  if (adcFreq > ADC_MAX_CLOCK)
  {
    adcFreq = ADC_MAX_CLOCK;
  }
  else if (adcFreq < ADC_MIN_CLOCK)
  {
    adcFreq = ADC_MIN_CLOCK;
  }

  /* Use current HFPER frequency? */
  if (!hfperFreq)
  {
    hfperFreq = CMU_ClockFreqGet(cmuClock_HFPER);
  }

  ret = (hfperFreq + adcFreq - 1) / adcFreq;
  if (ret)
  {
    ret--;
  }

  return (uint8_t)ret;
}


/***************************************************************************//**
 * @brief
 *   Reset ADC to same state as after a HW reset.
 *
 * @note
 *   The ROUTE register is NOT reset by this function, in order to allow for
 *   centralized setup of this feature.
 *
 * @param[in] adc
 *   Pointer to ADC peripheral register block.
 ******************************************************************************/
void ADC_Reset(ADC_TypeDef *adc)
{
  /* Stop conversions, before resetting other registers. */
  adc->CMD          = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP;
  adc->SINGLECTRL   = _ADC_SINGLECTRL_RESETVALUE;
#if defined( _ADC_SINGLECTRLX_MASK )
  adc->SINGLECTRLX  = _ADC_SINGLECTRLX_RESETVALUE;
#endif
  adc->SCANCTRL     = _ADC_SCANCTRL_RESETVALUE;
#if defined( _ADC_SCANCTRLX_MASK )
  adc->SCANCTRLX    = _ADC_SCANCTRLX_RESETVALUE;
#endif
  adc->CTRL         = _ADC_CTRL_RESETVALUE;
  adc->IEN          = _ADC_IEN_RESETVALUE;
  adc->IFC          = _ADC_IFC_MASK;
  adc->BIASPROG     = _ADC_BIASPROG_RESETVALUE;
#if defined( _ADC_SCANMASK_MASK )
  adc->SCANMASK     = _ADC_SCANMASK_RESETVALUE;
#endif
#if defined( _ADC_SCANINPUTSEL_MASK )
  adc->SCANINPUTSEL = _ADC_SCANINPUTSEL_RESETVALUE;
#endif
#if defined( _ADC_SCANNEGSEL_MASK )
  adc->SCANNEGSEL   = _ADC_SCANNEGSEL_RESETVALUE;
#endif

  /* Clear data FIFOs */
#if defined( _ADC_SINGLEFIFOCLEAR_MASK )
  adc->SINGLEFIFOCLEAR |= ADC_SINGLEFIFOCLEAR_SINGLEFIFOCLEAR;
  adc->SCANFIFOCLEAR   |= ADC_SCANFIFOCLEAR_SCANFIFOCLEAR;
#endif

  /* Load calibration values for the 1V25 internal reference. */
  ADC_LoadDevinfoCal(adc, adcRef1V25, false);
  ADC_LoadDevinfoCal(adc, adcRef1V25, true);

#if defined( _ADC_SCANINPUTSEL_MASK )
  /* Do not reset route register, setting should be done independently */
#endif
}


/***************************************************************************//**
 * @brief
 *   Calculate timebase value in order to get a timebase providing at least 1us.
 *
 * @param[in] hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to
 *   use currently defined HFPER clock setting.
 *
 * @return
 *   Timebase value to use for ADC in order to achieve at least 1 us.
 ******************************************************************************/
uint8_t ADC_TimebaseCalc(uint32_t hfperFreq)
{
  if (!hfperFreq)
  {
    hfperFreq = CMU_ClockFreqGet(cmuClock_HFPER);

    /* Just in case, make sure we get non-zero freq for below calculation */
    if (!hfperFreq)
    {
      hfperFreq = 1;
    }
  }
#if defined( _EFM32_GIANT_FAMILY ) || defined( _EFM32_WONDER_FAMILY )
  /* Handle errata on Giant Gecko, max TIMEBASE is 5 bits wide or max 0x1F */
  /* cycles. This will give a warmp up time of e.g. 0.645us, not the       */
  /* required 1us when operating at 48MHz. One must also increase acqTime  */
  /* to compensate for the missing clock cycles, adding up to 1us in total.*/
  /* See reference manual for details. */
  if ( hfperFreq > 32000000 )
  {
    hfperFreq = 32000000;
  }
#endif
  /* Determine number of HFPERCLK cycle >= 1us */
  hfperFreq += 999999;
  hfperFreq /= 1000000;

  /* Return timebase value (N+1 format) */
  return (uint8_t)(hfperFreq - 1);
}


/** @} (end addtogroup ADC) */
/** @} (end addtogroup EM_Library) */
#endif /* defined(ADC_COUNT) && (ADC_COUNT > 0) */
