added prescaler for 16 bit pwm in LPC1347 target
Fork of mbed-dev by
Diff: targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/emlib/src/em_adc.c
- Revision:
- 144:ef7eb2e8f9f7
- Parent:
- 50:a417edff4437
diff -r 423e1876dc07 -r ef7eb2e8f9f7 targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/emlib/src/em_adc.c --- a/targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/emlib/src/em_adc.c Tue Aug 02 14:07:36 2016 +0000 +++ b/targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/emlib/src/em_adc.c Fri Sep 02 15:07:44 2016 +0100 @@ -1,1082 +1,1082 @@ -/***************************************************************************//** - * @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) */ +/***************************************************************************//** + * @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) */