/**************************************************************************** * arch/arm/src/efm32/efm32_adc.c * * Copyright (C) 2014 Bouteville Pierre-Noel. All rights reserved. * Authors: Bouteville Pierre-Noel * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "up_internal.h" #include "up_arch.h" #include "chip.h" #include "efm32.h" #include "efm32_adc.h" /* ADC "upper half" support must be enabled */ #ifdef CONFIG_ADC /* Some ADC peripheral must be enabled */ #if defined(CONFIG_EFM32_ADC1) /* This implementation is for the EFM32GG Only */ #if defined(CONFIG_EFM32_EFM32GG) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* ADC interrupts ***********************************************************/ /* The maximum number of channels that can be sampled. If dma support is * not enabled, then only a single channel can be sampled. Otherwise, * data overruns would occur. */ #ifdef CONFIG_ADC_DMA # define ADC_MAX_SAMPLES 16 # warning "not tested !" #else # define ADC_MAX_SAMPLES 1 #endif /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes the state of one ADC block */ struct efm32_dev_s { uint8_t irq; /* Interrupt generated by this ADC block */ uint8_t nchannels; /* Number of channels */ uint8_t current; /* Current ADC channel being converted */ xcpt_t isr; /* Interrupt handler for this ADC block */ uint32_t base; /* Base address of registers unique to this ADC block */ uint8_t chanlist[ADC_MAX_SAMPLES]; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* ADC Register access */ static uint32_t adc_getreg( struct efm32_dev_s *priv, int offset); static void adc_putreg( struct efm32_dev_s *priv, int offset, uint32_t value); static void adc_hw_reset(struct efm32_dev_s *priv, bool reset); /* ADC Interrupt Handler */ static int adc_interrupt(FAR struct adc_dev_s *dev); /* ADC Driver Methods */ static void adc_reset(FAR struct adc_dev_s *dev); static int adc_setup(FAR struct adc_dev_s *dev); static void adc_shutdown(FAR struct adc_dev_s *dev); static void adc_rxint(FAR struct adc_dev_s *dev, bool enable); static int adc_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg); static void adc_enable(FAR struct efm32_dev_s *priv, bool enable); #ifdef ADC_HAVE_TIMER static void adc_timstart(FAR struct efm32_dev_s *priv, bool enable); static int adc_timinit(FAR struct efm32_dev_s *priv); #endif #if defined(CONFIG_EFM32_EFM32GG) static void adc_startconv(FAR struct efm32_dev_s *priv, bool enable); #endif /**************************************************************************** * Private Data ****************************************************************************/ /* ADC interface operations */ static const struct adc_ops_s g_adcops = { .ao_reset = adc_reset, .ao_setup = adc_setup, .ao_shutdown = adc_shutdown, .ao_rxint = adc_rxint, .ao_ioctl = adc_ioctl, }; /* ADC1 state */ #ifdef CONFIG_EFM32_ADC1 static struct efm32_dev_s g_adcpriv1 = { .irq = EFM32_IRQ_ADC0, .isr = adc_interrupt, .base = EFM32_ADC1_BASE, }; static struct adc_dev_s g_adcdev1 = { .ad_ops = &g_adcops, .ad_priv= &g_adcpriv1, }; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: adc_getreg * * Description: * Read the value of an ADC register. * * Input Parameters: * priv - A reference to the ADC block status * offset - The offset to the register to read * * Returned Value: * ****************************************************************************/ static uint32_t adc_getreg(struct efm32_dev_s *priv, int offset) { return getreg32(priv->base + offset); } /**************************************************************************** * Name: adc_getreg * * Description: * Read the value of an ADC register. * * Input Parameters: * priv - A reference to the ADC block status * offset - The offset to the register to read * * Returned Value: * ****************************************************************************/ static void adc_putreg(struct efm32_dev_s *priv, int offset, uint32_t value) { putreg32(value, priv->base + offset); } /***************************************************************************//** * Name: ADC_CalibrateLoadScan * * Description: * Load SCAN calibrate register with predefined values for a certain * reference. * * During production, calibration values are made and stored in the device * information page for known references. Notice that for external references, * calibration values must be determined explicitly, and this function * will not modify the calibration register. * * Input Parameters: * adc - Pointer to ADC peripheral register block. * ref - Reference to load calibrated values for. No values are loaded for * external references. * ******************************************************************************/ static void ADC_CalibrateLoadScan(ADC_TypeDef *adc, ADC_Ref_TypeDef ref) { uint32_t cal; /* Load proper calibration data depending on selected reference * NOTE: We use ...SCAN... defines below, they are the same as * similar ...SINGLE... defines. */ switch (ref) { case adcRef1V25: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >> _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2V5: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_GAIN_MASK) >> _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRefVDD: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_GAIN_MASK) >> _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRef5VDIFF: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2xVDD: /* Gain value not of relevance for this reference, leave as is */ cal = adc->CAL & ~_ADC_CAL_SCANOFFSET_MASK; cal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK) >> _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; /* For external references, the calibration must be determined for the * specific application and set explicitly. */ default: break; } } /***************************************************************************//** * Name: ADC_CalibrateLoadSingle * * Description: * Load SINGLE calibrate register with predefined values for a certain * reference. * * During production, calibration values are made and stored in the device * information page for known references. Notice that for external references, * calibration values must be determined explicitly, and this function * will not modify the calibration register. * * Input Parameters: * adc - Pointer to ADC peripheral register block. * ref - Reference to load calibrated values for. No values are loaded for * external references. * ******************************************************************************/ static void ADC_CalibrateLoadSingle(ADC_TypeDef *adc, ADC_Ref_TypeDef ref) { uint32_t cal; /* Load proper calibration data depending on selected reference * NOTE: We use ...SCAN... defines below, they are the same as * similar ...SINGLE... defines. */ switch (ref) { case adcRef1V25: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >> _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2V5: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_GAIN_MASK) >> _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRefVDD: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_GAIN_MASK) >> _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRef5VDIFF: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2xVDD: /* Gain value not of relevance for this reference, leave as is */ cal = adc->CAL & ~_ADC_CAL_SINGLEOFFSET_MASK; cal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK) >> _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; /* For external references, the calibration must be determined for the * specific application and set explicitly. */ default: break; } } /******************************************************************************* * Public Functions ******************************************************************************/ /***************************************************************************//** * Name: ADC_Init * Initialize ADC. * * Description: * 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. * * NOTE: This function will stop any ongoing conversion. * * Input Parameters: * adc- Pointer to ADC peripheral register block. * int - 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) | ((uint32_t)(init->lpfMode) << _ADC_CTRL_LPFMODE_SHIFT) | ((uint32_t)(init->warmUpMode) << _ADC_CTRL_WARMUPMODE_SHIFT); if (init->tailgate) { tmp |= ADC_CTRL_TAILGATE; } adc->CTRL = tmp; } /***************************************************************************//** * Name: ADC_InitScan * * Description: * Initialize ADC scan sequence. * * 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. * * Input Parameters: * adc - Pointer to ADC peripheral register block. * 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 proper calibration data depending on selected reference */ ADC_CalibrateLoadScan(adc, init->reference); tmp = ((uint32_t)(init->prsSel) << _ADC_SCANCTRL_PRSSEL_SHIFT) | ((uint32_t)(init->acqTime) << _ADC_SCANCTRL_AT_SHIFT) | ((uint32_t)(init->reference) << _ADC_SCANCTRL_REF_SHIFT) | init->input | ((uint32_t)(init->resolution) << _ADC_SCANCTRL_RES_SHIFT); if (init->prsEnable) { tmp |= ADC_SCANCTRL_PRSEN; } if (init->leftAdjust) { tmp |= ADC_SCANCTRL_ADJ_LEFT; } if (init->diff) { tmp |= ADC_SCANCTRL_DIFF; } if (init->rep) { tmp |= ADC_SCANCTRL_REP; } adc->SCANCTRL = tmp; } /***************************************************************************//** * Name: ADC_InitSingle * * Description: * Initialize single ADC sample conversion. * * 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. * * Input Parameters: * adc - Pointer to ADC peripheral register block. * 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 proper calibration data depending on selected reference */ ADC_CalibrateLoadSingle(adc, init->reference); tmp = ((uint32_t)(init->prsSel) << _ADC_SINGLECTRL_PRSSEL_SHIFT) | ((uint32_t)(init->acqTime) << _ADC_SINGLECTRL_AT_SHIFT) | ((uint32_t)(init->reference) << _ADC_SINGLECTRL_REF_SHIFT) | ((uint32_t)(init->input) << _ADC_SINGLECTRL_INPUTSEL_SHIFT) | ((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; } adc->SINGLECTRL = tmp; } /***************************************************************************//** * Name: ADC_PrescaleCalc * * Description: * Calculate prescaler value used to determine ADC clock. * * The ADC clock is given by: HFPERCLK / (prescale + 1). * * Input Parameters: * adcFreq ADC frequency wanted. The frequency will automatically * be adjusted to be within valid range according to reference manual. * hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to * use currently defined HFPER clock setting. * * Returned Value: * 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; } /***************************************************************************//** * Name: ADC_Reset * * Description: * 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. * * Input Parameters: * 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; adc->SCANCTRL = _ADC_SCANCTRL_RESETVALUE; adc->CTRL = _ADC_CTRL_RESETVALUE; adc->IEN = _ADC_IEN_RESETVALUE; adc->IFC = _ADC_IFC_MASK; adc->BIASPROG = _ADC_BIASPROG_RESETVALUE; /* Load calibration values for the 1V25 internal reference. */ ADC_CalibrateLoadSingle(adc, adcRef1V25); ADC_CalibrateLoadScan(adc, adcRef1V25); /* Do not reset route register, setting should be done independently */ } /***************************************************************************//** * Name: ADC_TimebaseCalc * * Description: * Calculate timebase value in order to get a timebase providing at least 1us. * * Input Parameters: * hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to * use currently defined HFPER clock setting. * * Returned Value: * 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); } endif /* defined(ADC_COUNT) && (ADC_COUNT > 0) */ /**************************************************************************** * Name: adc_tim_dumpregs * * Description: * Dump all timer registers. * * Input parameters: * priv - A reference to the ADC block status * * Returned Value: * None * ****************************************************************************/ #ifdef ADC_HAVE_TIMER static void adc_tim_dumpregs(struct efm32_dev_s *priv, FAR const char *msg) { #if defined(CONFIG_DEBUG_ANALOG) && defined(CONFIG_DEBUG_VERBOSE) avdbg("%s:\n", msg); avdbg(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", tim_getreg(priv, EFM32_GTIM_CR1_OFFSET), tim_getreg(priv, EFM32_GTIM_CR2_OFFSET), tim_getreg(priv, EFM32_GTIM_SMCR_OFFSET), tim_getreg(priv, EFM32_GTIM_DIER_OFFSET)); avdbg(" SR: %04x EGR: 0000 CCMR1: %04x CCMR2: %04x\n", tim_getreg(priv, EFM32_GTIM_SR_OFFSET), tim_getreg(priv, EFM32_GTIM_CCMR1_OFFSET), tim_getreg(priv, EFM32_GTIM_CCMR2_OFFSET)); avdbg(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", tim_getreg(priv, EFM32_GTIM_CCER_OFFSET), tim_getreg(priv, EFM32_GTIM_CNT_OFFSET), tim_getreg(priv, EFM32_GTIM_PSC_OFFSET), tim_getreg(priv, EFM32_GTIM_ARR_OFFSET)); avdbg(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", tim_getreg(priv, EFM32_GTIM_CCR1_OFFSET), tim_getreg(priv, EFM32_GTIM_CCR2_OFFSET), tim_getreg(priv, EFM32_GTIM_CCR3_OFFSET), tim_getreg(priv, EFM32_GTIM_CCR4_OFFSET)); if (priv->tbase == EFM32_TIM1_BASE || priv->tbase == EFM32_TIM8_BASE) { avdbg(" RCR: %04x BDTR: %04x DCR: %04x DMAR: %04x\n", tim_getreg(priv, EFM32_ATIM_RCR_OFFSET), tim_getreg(priv, EFM32_ATIM_BDTR_OFFSET), tim_getreg(priv, EFM32_ATIM_DCR_OFFSET), tim_getreg(priv, EFM32_ATIM_DMAR_OFFSET)); } else { avdbg(" DCR: %04x DMAR: %04x\n", tim_getreg(priv, EFM32_GTIM_DCR_OFFSET), tim_getreg(priv, EFM32_GTIM_DMAR_OFFSET)); } #endif } #endif /**************************************************************************** * Name: adc_startconv * * Description: * Start (or stop) the ADC conversion process in DMA mode * * Input Parameters: * priv - A reference to the ADC block status * enable - True: Start conversion * * Returned Value: * ****************************************************************************/ #if defined(CONFIG_EFM32_EFM32GG) static void adc_startconv(struct efm32_dev_s *priv, bool enable) { uint32_t regval; avdbg("enable: %d\n", enable); regval = adc_getreg(priv, EFM32_ADC_CR2_OFFSET); if (enable) { /* Start conversion of regular channles */ regval |= ADC_CR2_SWSTART; } else { /* Disable the conversion of regular channels */ regval &= ~ADC_CR2_SWSTART; } adc_putreg(priv, EFM32_ADC_CR2_OFFSET,regval); } #endif /**************************************************************************** * Name: adc_reset * * Description: * Deinitializes the ADCx peripheral registers to their default * reset values. It could set all the ADCs configured. * * Input Parameters: * regaddr - The register to read * reset - Condition, set or reset * * Returned Value: * ****************************************************************************/ static void adc_hw_reset(struct efm32_dev_s *priv, bool reset) { irqstate_t flags; uint32_t regval; uint32_t adcbit; /* Pick the appropriate bit in the APB2 reset register */ /* Disable interrupts. This is necessary because the APB2RTSR register * is used by several different drivers. */ flags = irqsave(); /* Set or clear the selected bit in the APB2 reset register */ regval = getreg32(EFM32_RCC_APB2RSTR); if (reset) { /* Enable ADC reset state */ regval |= adcbit; } else { /* Release ADC from reset state */ regval &= ~adcbit; } putreg32(regval, EFM32_RCC_APB2RSTR); irqrestore(flags); } /******************************************************************************* * Name: adc_enable * * Description : Enables or disables the specified ADC peripheral. * Also, starts a conversion when the ADC is not * triggered by timers * * Input Parameters: * * enable - true: enable ADC conversion * false: disable ADC conversion * * Returned Value: * *******************************************************************************/ static void adc_enable(FAR struct efm32_dev_s *priv, bool enable) { uint32_t regval; avdbg("enable: %d\n", enable); regval = adc_getreg(priv, EFM32_ADC_CR2_OFFSET); if (enable) { regval |= ADC_CR2_ADON; } else { regval &= ~ADC_CR2_ADON; } adc_putreg(priv, EFM32_ADC_CR2_OFFSET, regval); } /**************************************************************************** * Name: adc_reset * * Description: * Reset the ADC device. Called early to initialize the hardware. This * is called, before adc_setup() and on error conditions. * * Input Parameters: * * Returned Value: * ****************************************************************************/ static void adc_reset(FAR struct adc_dev_s *dev) { FAR struct efm32_dev_s *priv = (FAR struct efm32_dev_s *)dev->ad_priv; irqstate_t flags; uint32_t regval; int offset; int i; #ifdef ADC_HAVE_TIMER int ret; #endif avdbg("intf: ADC%d\n", priv->intf); flags = irqsave(); /* Enable ADC reset state */ adc_hw_reset(priv, true); /* Release ADC from reset state */ adc_hw_reset(priv, false); /* Initialize the ADC data structures */ /* Initialize the watchdog high threshold register */ adc_putreg(priv, EFM32_ADC_HTR_OFFSET, 0x00000fff); /* Initialize the watchdog low threshold register */ adc_putreg(priv, EFM32_ADC_LTR_OFFSET, 0x00000000); /* Initialize the same sample time for each ADC 55.5 cycles * * During sample cycles channel selection bits must remain unchanged. * * 000: 1.5 cycles * 001: 7.5 cycles * 010: 13.5 cycles * 011: 28.5 cycles * 100: 41.5 cycles * 101: 55.5 cycles * 110: 71.5 cycles * 111: 239.5 cycles */ adc_putreg(priv, EFM32_ADC_SMPR1_OFFSET, 0x00b6db6d); adc_putreg(priv, EFM32_ADC_SMPR2_OFFSET, 0x00b6db6d); /* ADC CR1 Configuration */ regval = adc_getreg(priv, EFM32_ADC_CR1_OFFSET); /* Initialize the Analog watchdog enable */ regval |= ADC_CR1_AWDEN; regval |= (priv->chanlist[0] << ADC_CR1_AWDCH_SHIFT); /* Enable interrupt flags */ regval |= ADC_CR1_ALLINTS; adc_putreg(priv, EFM32_ADC_CR1_OFFSET, regval); /* ADC CR2 Configuration */ regval = adc_getreg(priv, EFM32_ADC_CR2_OFFSET); /* Clear CONT, continuous mode disable */ regval &= ~ADC_CR2_CONT; /* Set ALIGN (Right = 0) */ regval &= ~ADC_CR2_ALIGN; adc_putreg(priv, EFM32_ADC_CR2_OFFSET, regval); /* Configuration of the channel conversions */ regval = adc_getreg(priv, EFM32_ADC_SQR3_OFFSET) & ADC_SQR3_RESERVED; for (i = 0, offset = 0; i < priv->nchannels && i < 6; i++, offset += 5) { regval |= (uint32_t)priv->chanlist[i] << offset; } adc_putreg(priv, EFM32_ADC_SQR3_OFFSET, regval); regval = adc_getreg(priv, EFM32_ADC_SQR2_OFFSET) & ADC_SQR2_RESERVED; for (i = 6, offset = 0; i < priv->nchannels && i < 12; i++, offset += 5) { regval |= (uint32_t)priv->chanlist[i] << offset; } adc_putreg(priv, EFM32_ADC_SQR2_OFFSET, regval); regval = adc_getreg(priv, EFM32_ADC_SQR1_OFFSET) & ADC_SQR1_RESERVED; for (i = 12, offset = 0; i < priv->nchannels && i < 16; i++, offset += 5) { regval |= (uint32_t)priv->chanlist[i] << offset; } /* ADC CCR configuration */ regval = getreg32(EFM32_ADC_CCR); regval &= ~(ADC_CCR_MULTI_MASK | ADC_CCR_DELAY_MASK | ADC_CCR_DDS | ADC_CCR_DMA_MASK | ADC_CCR_ADCPRE_MASK | ADC_CCR_VBATE | ADC_CCR_TSVREFE); regval |= (ADC_CCR_MULTI_NONE | ADC_CCR_DMA_DISABLED | ADC_CCR_ADCPRE_DIV2); putreg32(regval, EFM32_ADC_CCR); /* Set the number of conversions */ DEBUGASSERT(priv->nchannels <= ADC_MAX_SAMPLES); regval |= (((uint32_t)priv->nchannels-1) << ADC_SQR1_L_SHIFT); adc_putreg(priv, EFM32_ADC_SQR1_OFFSET, regval); /* Set the channel index of the first conversion */ priv->current = 0; /* Set ADON to wake up the ADC from Power Down state. */ adc_enable(priv, true); adc_startconv(priv, true); irqrestore(flags); avdbg("SR: 0x%08x CR1: 0x%08x CR2: 0x%08x\n", adc_getreg(priv, EFM32_ADC_SR_OFFSET), adc_getreg(priv, EFM32_ADC_CR1_OFFSET), adc_getreg(priv, EFM32_ADC_CR2_OFFSET)); avdbg("SQR1: 0x%08x SQR2: 0x%08x SQR3: 0x%08x\n", adc_getreg(priv, EFM32_ADC_SQR1_OFFSET), adc_getreg(priv, EFM32_ADC_SQR2_OFFSET), adc_getreg(priv, EFM32_ADC_SQR3_OFFSET)); } /**************************************************************************** * Name: adc_setup * * Description: * Configure the ADC. This method is called the first time that the ADC * device is opened. This will occur when the port is first opened. * This setup includes configuring and attaching ADC interrupts. Interrupts * are all disabled upon return. * * Input Parameters: * * Returned Value: * ****************************************************************************/ static int adc_setup(FAR struct adc_dev_s *dev) { FAR struct efm32_dev_s *priv = (FAR struct efm32_dev_s *)dev->ad_priv; int ret; /* Attach the ADC interrupt */ ret = irq_attach(priv->irq, priv->isr); if (ret == OK) { /* Make sure that the ADC device is in the powered up, reset state */ adc_reset(dev); /* Enable the ADC interrupt */ avdbg("Enable the ADC interrupt: irq=%d\n", priv->irq); up_enable_irq(priv->irq); } return ret; } /**************************************************************************** * Name: adc_shutdown * * Description: * Disable the ADC. This method is called when the ADC device is closed. * This method reverses the operation the setup method. * * Input Parameters: * * Returned Value: * ****************************************************************************/ static void adc_shutdown(FAR struct adc_dev_s *dev) { FAR struct efm32_dev_s *priv = (FAR struct efm32_dev_s *)dev->ad_priv; /* Disable ADC interrupts and detach the ADC interrupt handler */ up_disable_irq(priv->irq); irq_detach(priv->irq); /* Disable and reset the ADC module */ adc_hw_reset(priv, true); } /**************************************************************************** * Name: adc_rxint * * Description: * Call to enable or disable RX interrupts. * * Input Parameters: * * Returned Value: * ****************************************************************************/ static void adc_rxint(FAR struct adc_dev_s *dev, bool enable) { FAR struct efm32_dev_s *priv = (FAR struct efm32_dev_s *)dev->ad_priv; uint32_t regval; avdbg("intf: %d enable: %d\n", priv->intf, enable); regval = adc_getreg(priv, EFM32_ADC_CR1_OFFSET); if (enable) { /* Enable the end-of-conversion ADC and analog watchdog interrupts */ regval |= ADC_CR1_ALLINTS; } else { /* Disable all ADC interrupts */ regval &= ~ADC_CR1_ALLINTS; } adc_putreg(priv, EFM32_ADC_CR1_OFFSET, regval); } /**************************************************************************** * Name: adc_ioctl * * Description: * All ioctl calls will be routed through this method. * * Input Parameters: * * Returned Value: * ****************************************************************************/ static int adc_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg) { return -ENOTTY; } /**************************************************************************** * Name: adc_interrupt * * Description: * Common ADC interrupt handler. * * Input Parameters: * * Returned Value: * ****************************************************************************/ static int adc_interrupt(FAR struct adc_dev_s *dev) { FAR struct efm32_dev_s *priv = (FAR struct efm32_dev_s *)dev->ad_priv; uint32_t adcsr; int32_t value; /* Identifies the interruption AWD, OVR or EOC */ adcsr = adc_getreg(priv, EFM32_ADC_SR_OFFSET); if ((adcsr & ADC_SR_AWD) != 0) { alldbg("WARNING: Analog Watchdog, Value converted out of range!\n"); } /* EOC: End of conversion */ if ((adcsr & ADC_SR_EOC) != 0) { /* Read the converted value and clear EOC bit * (It is cleared by reading the ADC_DR) */ value = adc_getreg(priv, EFM32_ADC_DR_OFFSET); value &= ADC_DR_DATA_MASK; /* Give the ADC data to the ADC driver. adc_receive accepts 3 parameters: * * 1) The first is the ADC device instance for this ADC block. * 2) The second is the channel number for the data, and * 3) The third is the converted data for the channel. */ adc_receive(dev, priv->chanlist[priv->current], value); /* Set the channel number of the next channel that will complete conversion */ priv->current++; if (priv->current >= priv->nchannels) { /* Restart the conversion sequence from the beginning */ priv->current = 0; } } return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: efm32_adcinitialize * * Description: * Initialize the ADC. * * The logic is, save nchannels : # of channels (conversions) in ADC_SQR1_L * Then, take the chanlist array and store it in the SQR Regs, * chanlist[0] -> ADC_SQR3_SQ1 * chanlist[1] -> ADC_SQR3_SQ2 * ... * chanlist[15]-> ADC_SQR1_SQ16 * * up to * chanlist[nchannels] * * Input Parameters: * intf - Could be {1,2,3} for ADC1, ADC2, or ADC3 * chanlist - The list of channels * nchannels - Number of channels * * Returned Value: * Valid ADC device structure reference on succcess; a NULL on failure * ****************************************************************************/ struct adc_dev_s *efm32_adcinitialize(int intf, const uint8_t *chanlist, int nchannels) { FAR struct adc_dev_s *dev; FAR struct efm32_dev_s *priv; avdbg("intf: %d nchannels: %d\n", intf, nchannels); #ifdef CONFIG_EFM32_ADC1 if (intf == 1) { avdbg("ADC1 Selected\n"); dev = &g_adcdev1; } else #endif #ifdef CONFIG_EFM32_ADC2 if (intf == 2) { avdbg("ADC2 Selected\n"); dev = &g_adcdev2; } else #endif #ifdef CONFIG_EFM32_ADC3 if (intf == 3) { avdbg("ADC3 Selected\n"); dev = &g_adcdev3; } else #endif { adbg("No ADC interface defined\n"); return NULL; } /* Configure the selected ADC */ priv = dev->ad_priv; DEBUGASSERT(nchannels <= ADC_MAX_SAMPLES); priv->nchannels = nchannels; memcpy(priv->chanlist, chanlist, nchannels); return dev; } #endif /* CONFIG_EFM32_EFM32GG */ #endif /* CONFIG_EFM32_ADC1 */ #endif /* CONFIG_ADC */