From 18e9167c7379dbeb3b847b2d7beff8f3bee827ae Mon Sep 17 00:00:00 2001 From: Juergen Fitschen Date: Fri, 18 Nov 2022 16:45:12 +0100 Subject: [PATCH] cpu/efm32/adc: add support for Gecko Series 2 Series 2 features IADCs instead of ADCs. --- cpu/efm32/include/periph_cpu.h | 232 ++++++++++++++++----- cpu/efm32/periph/Makefile | 9 + cpu/efm32/periph/{adc.c => adc_series01.c} | 0 cpu/efm32/periph/adc_series2.c | 148 +++++++++++++ 4 files changed, 338 insertions(+), 51 deletions(-) rename cpu/efm32/periph/{adc.c => adc_series01.c} (100%) create mode 100644 cpu/efm32/periph/adc_series2.c diff --git a/cpu/efm32/include/periph_cpu.h b/cpu/efm32/include/periph_cpu.h index 17dea58ac1d8..60f7ec551454 100644 --- a/cpu/efm32/include/periph_cpu.h +++ b/cpu/efm32/include/periph_cpu.h @@ -25,7 +25,11 @@ #include "cpu_conf.h" +#if defined(_SILICON_LABS_32B_SERIES_2) +#include "em_iadc.h" +#else #include "em_adc.h" +#endif #include "em_cmu.h" #include "em_device.h" #include "em_gpio.h" @@ -57,57 +61,6 @@ typedef struct { CMU_ClkDiv_TypeDef div; /**< Divisor */ } clk_div_t; -#if (defined(ADC_COUNT) && (ADC_COUNT > 0)) || defined(DOXYGEN) -/** - * @brief Internal macro for combining ADC resolution (x) with number of - * shifts (y). - */ -#define ADC_MODE(x, y) ((y << 4) | x) - -/** - * @brief Internal define to note that resolution is not supported. - */ -#define ADC_MODE_UNDEF(x) (ADC_MODE(x, 15)) - -#ifndef DOXYGEN -/** - * @brief Possible ADC resolution settings - * @{ - */ -#define HAVE_ADC_RES_T -typedef enum { - ADC_RES_6BIT = ADC_MODE(adcRes6Bit, 0), /**< ADC resolution: 6 bit */ - ADC_RES_8BIT = ADC_MODE(adcRes8Bit, 0), /**< ADC resolution: 8 bit */ - ADC_RES_10BIT = ADC_MODE(adcRes12Bit, 2), /**< ADC resolution: 10 bit (shifted from 12 bit) */ - ADC_RES_12BIT = ADC_MODE(adcRes12Bit, 0), /**< ADC resolution: 12 bit */ - ADC_RES_14BIT = ADC_MODE_UNDEF(0), /**< ADC resolution: 14 bit (unsupported) */ - ADC_RES_16BIT = ADC_MODE_UNDEF(1), /**< ADC resolution: 16 bit (unsupported) */ -} adc_res_t; -/** @} */ -#endif /* ndef DOXYGEN */ - -/** - * @brief ADC device configuration - */ -typedef struct { - ADC_TypeDef *dev; /**< ADC device used */ - CMU_Clock_TypeDef cmu; /**< the device CMU channel */ -} adc_conf_t; - -/** - * @brief ADC channel configuration - */ -typedef struct { - uint8_t dev; /**< device index */ -#if defined(_SILICON_LABS_32B_SERIES_0) - ADC_SingleInput_TypeDef input; /**< input channel */ -#elif defined(_SILICON_LABS_32B_SERIES_1) - ADC_PosSel_TypeDef input; /**< input channel */ -#endif - ADC_Ref_TypeDef reference; /**< channel voltage reference */ - ADC_AcqTime_TypeDef acq_time; /**< channel acquisition time */ -} adc_chan_conf_t; -#endif /** * @brief Length of CPU ID in octets. @@ -245,6 +198,183 @@ typedef enum { /** @} */ #endif /* ndef DOXYGEN */ +#if defined(_SILICON_LABS_32B_SERIES_2) +/** + * @brief Internal macro for combining over-sampling rate (osr), digital + * averaging count (avg) and output resolution (res). + * + * @note The efr32xg23 reference manual provides this folumar: + * res = 11 bit + log_2(osr * avg) bit + */ +#if defined(_IADC_CFG_DIGAVG_MASK) +#define ADC_MODE(osr, avg, res) ((osr << 16) | (avg << 8) | res) +#else +#define ADC_MODE(osr, res) ((osr << 16) | res) +#endif + +/** + * @brief Internal macro to extract averaging count + */ +#define ADC_MODE_OSR(mode) ((mode & 0xff0000) >> 16) + +#if defined(_IADC_CFG_DIGAVG_MASK) +/** + * @brief Internal macro to extract over-sampling rate + */ +#define ADC_MODE_AVG(mode) ((mode & 0x00ff00) >> 8) +#endif + +/** + * @brief Internal macro to extract output resolution + */ +#define ADC_MODE_RES(mode) ((mode & 0x0000ff) >> 0) + +/** + * @brief Possible ADC resolution settings + * @{ + */ +#define HAVE_ADC_RES_T +#if defined(_IADC_CFG_DIGAVG_MASK) +typedef enum { + ADC_RES_6BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, iadcDigitalAverage1, 6), + ADC_RES_8BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, iadcDigitalAverage1, 8), + ADC_RES_10BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, iadcDigitalAverage1, 10), + ADC_RES_12BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, iadcDigitalAverage1, 12), + ADC_RES_14BIT = ADC_MODE(iadcCfgOsrHighSpeed8x, iadcDigitalAverage1, 14), + ADC_RES_16BIT = ADC_MODE(iadcCfgOsrHighSpeed16x, iadcDigitalAverage2, 16), +} adc_res_t; +#else +typedef enum { + ADC_RES_6BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, 6), + ADC_RES_8BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, 8), + ADC_RES_10BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, 10), + ADC_RES_12BIT = ADC_MODE(iadcCfgOsrHighSpeed2x, 12), + ADC_RES_14BIT = ADC_MODE(iadcCfgOsrHighSpeed8x, 14), + ADC_RES_16BIT = ADC_MODE(iadcCfgOsrHighSpeed32x, 16), +} adc_res_t; +#endif + +/** + * @brief ADC device configuration + */ +typedef struct { + /** + * IADC device configuration + */ + IADC_TypeDef *dev; + + /** + * CMU gate for the IADC device + */ + CMU_Clock_TypeDef cmu; + + /** + * Voltage reference to use + */ + IADC_CfgReference_t reference; + + /** + * Voltage of the reference in mV + * + * @note Required internally for offset correction. + */ + uint32_t reference_mV; + + /** + * Ampilfication of the analog input signal + * + * @note The maximum input voltage is + * \ref adc_conf_t.gain * \ref adc_conf_t.reference_mV + */ + IADC_CfgAnalogGain_t gain; + + /** + * Available resoltions + * + * @note Resolutions made available to the applications have to be + * specified during \ref adc_init. This will configure the IADC + * accordingly and allows for quick \ref adc_sample calls. + */ + adc_res_t available_res[IADC0_CONFIGNUM]; +} adc_conf_t; + +/** + * @brief ADC channel configuration + */ +typedef struct { + /** + * \ref adc_conf_t device index + */ + uint8_t dev; + + /** + * Positive analog input + */ + gpio_t input_pos; + + /** + * Negative analog input. + * Can be set to \ref GPIO_UNDEF for single-ended ADC lines. + * + * @note For differential inputs make sure that + * \ref adc_chan_conf_t.input_pos is an even pin number and + * \ref adc_chan_conf_t.input_neg is an odd pin number or the other + * way around. + */ + gpio_t input_neg; +} adc_chan_conf_t; +#else /* defined(_SILICON_LABS_32B_SERIES_2) */ +/** + * @brief Internal macro for combining ADC resolution (x) with number of + * shifts (y). + */ +#define ADC_MODE(x, y) ((y << 4) | x) + +/** + * @brief Internal define to note that resolution is not supported. + */ +#define ADC_MODE_UNDEF(x) (ADC_MODE(x, 15)) + +#ifndef DOXYGEN +/** + * @brief Possible ADC resolution settings + * @{ + */ +#define HAVE_ADC_RES_T +typedef enum { + ADC_RES_6BIT = ADC_MODE(adcRes6Bit, 0), /**< ADC resolution: 6 bit */ + ADC_RES_8BIT = ADC_MODE(adcRes8Bit, 0), /**< ADC resolution: 8 bit */ + ADC_RES_10BIT = ADC_MODE(adcRes12Bit, 2), /**< ADC resolution: 10 bit (shifted from 12 bit) */ + ADC_RES_12BIT = ADC_MODE(adcRes12Bit, 0), /**< ADC resolution: 12 bit */ + ADC_RES_14BIT = ADC_MODE_UNDEF(0), /**< ADC resolution: 14 bit (unsupported) */ + ADC_RES_16BIT = ADC_MODE_UNDEF(1), /**< ADC resolution: 16 bit (unsupported) */ +} adc_res_t; +/** @} */ +#endif /* ndef DOXYGEN */ + +/** + * @brief ADC device configuration + */ +typedef struct { + ADC_TypeDef *dev; /**< ADC device used */ + CMU_Clock_TypeDef cmu; /**< the device CMU channel */ +} adc_conf_t; + +/** + * @brief ADC channel configuration + */ +typedef struct { + uint8_t dev; /**< device index */ +#if defined(_SILICON_LABS_32B_SERIES_0) + ADC_SingleInput_TypeDef input; /**< input channel */ +#elif defined(_SILICON_LABS_32B_SERIES_1) + ADC_PosSel_TypeDef input; /**< input channel */ +#endif + ADC_Ref_TypeDef reference; /**< channel voltage reference */ + ADC_AcqTime_TypeDef acq_time; /**< channel acquisition time */ +} adc_chan_conf_t; +#endif /* !defined(_SILICON_LABS_32B_SERIES_2) */ + /** * @brief Override hardware crypto supported methods. * @{ diff --git a/cpu/efm32/periph/Makefile b/cpu/efm32/periph/Makefile index 23860d37131c..d412acba8287 100644 --- a/cpu/efm32/periph/Makefile +++ b/cpu/efm32/periph/Makefile @@ -1,5 +1,14 @@ include $(RIOTCPU)/efm32/efm32-info.mk +# Select the correct implementation for `periph_adc` +ifneq (,$(filter periph_adc,$(USEMODULE))) + ifeq (2,$(EFM32_SERIES)) + SRC += adc_series2.c + else + SRC += adc_series01.c + endif +endif + # Select the correct implementation for `periph_timer` ifneq (,$(filter periph_hwrng,$(USEMODULE))) ifeq (1,$(EFM32_SERIES)) diff --git a/cpu/efm32/periph/adc.c b/cpu/efm32/periph/adc_series01.c similarity index 100% rename from cpu/efm32/periph/adc.c rename to cpu/efm32/periph/adc_series01.c diff --git a/cpu/efm32/periph/adc_series2.c b/cpu/efm32/periph/adc_series2.c new file mode 100644 index 000000000000..b8b902322b00 --- /dev/null +++ b/cpu/efm32/periph/adc_series2.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 SSV Software Systems GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_efm32 + * @ingroup drivers_periph_adc + * @{ + * + * @file + * @brief Low-level ADC driver implementation + * + * @author Juergen Fitschen + * + * @} + */ + +#include + +#include "cpu.h" +#include "mutex.h" + +#include "periph_conf.h" +#include "periph/adc.h" +#include "periph/gpio.h" + +#include "em_cmu.h" +#include "em_gpio.h" +#include "em_iadc.h" + +static mutex_t adc_lock[ADC_DEV_NUMOF]; + +int adc_init(adc_t line) +{ + assert(line < ADC_NUMOF); + uint8_t dev = adc_channel_config[line].dev; + assert(dev < ADC_DEV_NUMOF); + + /* initialize lock */ + mutex_init(&adc_lock[dev]); + + /* enable clock */ + CMU_ClockEnable(adc_config[dev].cmu, true); + + /* make sure we're in a known state */ + IADC_reset(adc_config[dev].dev); + + /* init IADC periph */ + const IADC_Init_t init = IADC_INIT_DEFAULT; + IADC_AllConfigs_t configs = { 0 }; + for (size_t i = 0; i < IADC0_CONFIGNUM; i++) { + configs.configs[i].adcMode = iadcCfgModeNormal; + configs.configs[i].osrHighSpeed = ADC_MODE_OSR(adc_config[dev].available_res[i]); + configs.configs[i].digAvg = ADC_MODE_AVG(adc_config[dev].available_res[i]); + configs.configs[i].analogGain = adc_config[dev].gain; + configs.configs[i].reference = adc_config[dev].reference; + configs.configs[i].vRef = adc_config[dev].reference_mV; + configs.configs[i].twosComplement = iadcCfgTwosCompUnipolar; + } + IADC_init(adc_config[dev].dev, &init, &configs); + + return 0; +} + +static inline GPIO_Port_TypeDef _port_num(gpio_t pin) +{ + return ((pin & 0xf0) >> 4); +} + +static inline uint8_t _pin_num(gpio_t pin) +{ + return (pin & 0x0f); +} + +static void _setup_abus(gpio_t pin) { + const uint32_t alloc = GPIO_ABUSALLOC_AEVEN0_ADC0 + | GPIO_ABUSALLOC_AODD0_ADC0; + switch (_port_num(pin)) { + case gpioPortA: + GPIO->ABUSALLOC = alloc; + break; + case gpioPortB: + GPIO->BBUSALLOC = alloc; + break; + case gpioPortC: + case gpioPortD: + GPIO->CDBUSALLOC = alloc; + break; + } +} + +int32_t adc_sample(adc_t line, adc_res_t res) +{ + assert(line < ADC_NUMOF); + uint8_t dev = adc_channel_config[line].dev; + + /* find config to given resolution */ + uint8_t config; + for (config = 0; config < IADC0_CONFIGNUM; config++) { + if (adc_config[dev].available_res[config] == res) { + /* we found the corresponding config */ + break; + } + } + /* unsopported resolution */ + if (config >= IADC0_CONFIGNUM) { + return -1; + } + + /* lock device */ + mutex_lock(&adc_lock[dev]); + + /* setup channel and start sampling */ + static const IADC_InitSingle_t init = { + .alignment = iadcAlignRight20, + .showId = false, + .dataValidLevel = iadcFifoCfgDvl4, + .fifoDmaWakeup = false, + .triggerSelect = iadcTriggerSelImmediate, + .triggerAction = iadcTriggerActionOnce, + .start = true + }; + IADC_SingleInput_t input = IADC_SINGLEINPUT_DEFAULT; + input.configId = config; + input.posInput = IADC_portPinToPosInput(_port_num(adc_channel_config[line].input_pos), + _pin_num(adc_channel_config[line].input_pos)); + _setup_abus(adc_channel_config[line].input_pos); + if (adc_channel_config[line].input_neg != GPIO_UNDEF) { + input.negInput = IADC_portPinToNegInput(_port_num(adc_channel_config[line].input_neg), + _pin_num(adc_channel_config[line].input_neg)); + _setup_abus(adc_channel_config[line].input_neg); + } + + IADC_initSingle(adc_config[dev].dev, &init, &input); + + /* wait for the conservation to provide the result in the fifo */ + while ((IADC_getStatus(adc_config[dev].dev) & IADC_STATUS_SINGLEFIFODV) == 0) {} + uint32_t result = IADC_pullSingleFifoData(adc_config[dev].dev); + + /* unlock device */ + mutex_unlock(&adc_lock[dev]); + + return result >> (20 - ADC_MODE_RES(res)); +}