From 809569cda92bb57cb2df5ddf3cca858e981f0c88 Mon Sep 17 00:00:00 2001 From: Juha Niskanen Date: Mon, 28 Aug 2017 07:05:04 -0600 Subject: [PATCH] STM32L4 ADC: implement peripheral --- arch/arm/src/stm32f7/stm32_adc.c | 2 +- arch/arm/src/stm32l4/README.txt | 2 +- arch/arm/src/stm32l4/chip/stm32l4_adc.h | 22 +- arch/arm/src/stm32l4/chip/stm32l4_tim.h | 15 +- arch/arm/src/stm32l4/stm32l4_adc.c | 1719 ++++++++++++++++++++++- 5 files changed, 1736 insertions(+), 24 deletions(-) diff --git a/arch/arm/src/stm32f7/stm32_adc.c b/arch/arm/src/stm32f7/stm32_adc.c index 1de1d91865..c4db6aab4c 100644 --- a/arch/arm/src/stm32f7/stm32_adc.c +++ b/arch/arm/src/stm32f7/stm32_adc.c @@ -1020,7 +1020,7 @@ static void adc_startconv(FAR struct stm32_dev_s *priv, bool enable) * reset values. It could set all the ADCs configured. * * Input Parameters: - * regaddr - The register to read + * priv - A reference to the ADC block status * reset - Condition, set or reset * * Returned Value: diff --git a/arch/arm/src/stm32l4/README.txt b/arch/arm/src/stm32l4/README.txt index 98182043ff..a892b74eb5 100644 --- a/arch/arm/src/stm32l4/README.txt +++ b/arch/arm/src/stm32l4/README.txt @@ -43,7 +43,7 @@ CRC : TODO (configurable polynomial) WWDG : TODO IWDG : works MMCSD : TODO -ADC : TODO +ADC : Code written, to be tested DAC : Code written, to be tested DMA2D : TODO (Chrom-Art Accelerator for image manipulation) diff --git a/arch/arm/src/stm32l4/chip/stm32l4_adc.h b/arch/arm/src/stm32l4/chip/stm32l4_adc.h index f133306146..b6d2997071 100644 --- a/arch/arm/src/stm32l4/chip/stm32l4_adc.h +++ b/arch/arm/src/stm32l4/chip/stm32l4_adc.h @@ -373,6 +373,10 @@ #define ADC_TR3_HT_SHIFT (16) /* Bits 16-23: Analog watchdog 3 higher threshold */ #define ADC_TR3_HT_MASK (0xff << ADC_TR3_HT_SHIFT) +/* Offset between SQ bits */ + +#define ADC_SQ_OFFSET (6) + /* ADC regular sequence register 1 */ #define ADC_SQR1_L_SHIFT (0) /* Bits 0-3: Regular channel sequence length */ @@ -385,6 +389,10 @@ #define ADC_SQR1_SQ3_MASK (0x1f << ADC_SQR1_SQ3_SHIFT) #define ADC_SQR1_SQ4_SHIFT (24) /* Bits 24-28: 4th conversion in regular sequence */ #define ADC_SQR1_SQ4_MASK (0x1f << ADC_SQR1_SQ4_SHIFT) +#define ADC_SQR1_RESERVED (0xe0820830) +#define ADC_SQR1_FIRST (1) +#define ADC_SQR1_LAST (4) +#define ADC_SQR1_SQ_OFFSET (1*ADC_SQ_OFFSET) /* ADC regular sequence register 2 */ @@ -398,6 +406,10 @@ #define ADC_SQR2_SQ8_MASK (0x1f << ADC_SQR2_SQ8_SHIFT) #define ADC_SQR2_SQ9_SHIFT (24) /* Bits 24-28: 9th conversion in regular sequence */ #define ADC_SQR2_SQ9_MASK (0x1f << ADC_SQR2_SQ9_SHIFT ) +#define ADC_SQR2_RESERVED (0xe0820820) +#define ADC_SQR2_FIRST (5) +#define ADC_SQR2_LAST (9) +#define ADC_SQR2_SQ_OFFSET (0) /* ADC regular sequence register 3 */ @@ -410,7 +422,11 @@ #define ADC_SQR3_SQ13_SHIFT (18) /* Bits 18-22: 13th conversion in regular sequence */ #define ADC_SQR3_SQ13_MASK (0x1f << ADC_SQR3_SQ13_SHIFT) #define ADC_SQR3_SQ14_SHIFT (24) /* Bits 24-28: 14th conversion in regular sequence */ -#define ADC_SQR3_SQ14_MASK (0x1f << ADC_SQR3_SQ14_SHIFT ) +#define ADC_SQR3_SQ14_MASK (0x1f << ADC_SQR3_SQ14_SHIFT) +#define ADC_SQR3_RESERVED (0xe0820820) +#define ADC_SQR3_FIRST (10) +#define ADC_SQR3_LAST (14) +#define ADC_SQR3_SQ_OFFSET (0) /* ADC regular sequence register 4 */ @@ -418,6 +434,10 @@ #define ADC_SQR4_SQ15_MASK (0x1f << ADC_SQR4_SQ15_SHIFT) #define ADC_SQR4_SQ16_SHIFT (6) /* Bits 6-10: 15th conversion in regular sequence */ #define ADC_SQR4_SQ16_MASK (0x1f << ADC_SQR4_SQ16_SHIFT) +#define ADC_SQR4_RESERVED (0xfffff820) +#define ADC_SQR4_FIRST (15) +#define ADC_SQR4_LAST (16) +#define ADC_SQR4_SQ_OFFSET (0) /* ADC regular data register */ diff --git a/arch/arm/src/stm32l4/chip/stm32l4_tim.h b/arch/arm/src/stm32l4/chip/stm32l4_tim.h index 83f6902aae..99c4f48e92 100644 --- a/arch/arm/src/stm32l4/chip/stm32l4_tim.h +++ b/arch/arm/src/stm32l4/chip/stm32l4_tim.h @@ -1,4 +1,4 @@ -/**************************************************************************************************** +/************************************************************************************ * arch/arm/src/stm32l4/chip/stm32l4_tim.h * * Copyright (C) 2009, 2011-2012 Gregory Nutt. All rights reserved. @@ -32,16 +32,16 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - ****************************************************************************************************/ + ************************************************************************************/ #ifndef __ARCH_ARM_SRC_STM32L4_CHIP_STM32L4_TIM_H #define __ARCH_ARM_SRC_STM32L4_CHIP_STM32L4_TIM_H -/**************************************************************************************************** +/************************************************************************************ * Pre-processor Definitions - ****************************************************************************************************/ + ************************************************************************************/ -/* Register Offsets *********************************************************************************/ +/* Register Offsets *****************************************************************/ /* Basic Timers - TIM6 and TIM7 */ @@ -127,7 +127,7 @@ #define STM32L4_LPTIM_CNT_OFFSET 0x001c /* Counter (16-bit) */ #define STM32L4_LPTIM_OR_OFFSET 0x001c /* Options Register */ -/* Register Addresses *******************************************************************************/ +/* Register Addresses ***************************************************************/ /* Advanced Timers - TIM1 and TIM8 */ @@ -341,7 +341,7 @@ #define STM32L4_TIM7_PSC (STM32L4_TIM7_BASE+STM32L4_BTIM_PSC_OFFSET) #define STM32L4_TIM7_ARR (STM32L4_TIM7_BASE+STM32L4_BTIM_ARR_OFFSET) -/* Register Bitfield Definitions ********************************************************************/ +/* Register Bitfield Definitions ****************************************************/ /* Control register 1 */ @@ -671,6 +671,7 @@ #define ATIM_CCER_CC3NP (1 << 11) /* Bit 11: Capture/Compare 3 Complementary output polarity */ #define ATIM_CCER_CC4E (1 << 12) /* Bit 12: Capture/Compare 4 output enable */ #define ATIM_CCER_CC4P (1 << 13) /* Bit 13: Capture/Compare 4 output Polarity */ +#define ATIM_CCER_CC4NP (1 << 15) /* Bit 15: Capture/Compare 4 Complementary output polarity */ #define ATIM_CCER_CC5E (1 << 16) /* Bit 16: Capture/Compare 5 output enable */ #define ATIM_CCER_CC5P (1 << 17) /* Bit 17: Capture/Compare 5 output Polarity */ #define ATIM_CCER_CC6E (1 << 20) /* Bit 20: Capture/Compare 6 output enable */ diff --git a/arch/arm/src/stm32l4/stm32l4_adc.c b/arch/arm/src/stm32l4/stm32l4_adc.c index e718a5ce31..619e0b17c6 100644 --- a/arch/arm/src/stm32l4/stm32l4_adc.c +++ b/arch/arm/src/stm32l4/stm32l4_adc.c @@ -2,7 +2,13 @@ * arch/arm/src/stm32l4/stm32l4_adc.c * * Copyright (C) 2017 Gregory Nutt. All rights reserved. - * Authors: Gregory Nutt + * Copyright (C) 2015 Motorola Mobility, LLC. All rights reserved. + * Copyright (C) 2015 Omni Hoverboards Inc. All rights reserved. + * Author: Gregory Nutt + * Diego Sanchez + * Paul Alexander Patience + * Mateusz Szafoni + * Juha Niskanen * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -45,14 +51,15 @@ #include #include #include +#include +#include +#include +#include #include #include #include -#include "up_internal.h" -#include "up_arch.h" - #include "chip.h" #include "stm32l4_rcc.h" #include "stm32l4_tim.h" @@ -68,16 +75,1688 @@ #if defined(CONFIG_STM32L4_ADC1) || defined(CONFIG_STM32L4_ADC2) || \ defined(CONFIG_STM32L4_ADC3) -/* This implementation is for the STM32 L4X3 and L4X6 only */ +/* This implementation is for the STM32L4X3 and STM32L4X6 only */ -#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X3) +#if defined(CONFIG_STM32L4_STM32L4X3) || defined(CONFIG_STM32L4_STM32L4X6) + +#if defined(CONFIG_STM32L4_STM32L4X3) +# if defined(CONFIG_STM32L4_ADC2) || defined(CONFIG_STM32L4_ADC3) +# error "Using non-existent ADC on STM32L4X3" +# endif +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* RCC reset ****************************************************************/ + +#define STM32L4_RCC_RSTR STM32L4_RCC_AHB2RSTR +#define RCC_RSTR_ADC1RST RCC_AHB2RSTR_ADCRST +#define RCC_RSTR_ADC2RST RCC_AHB2RSTR_ADCRST +#define RCC_RSTR_ADC3RST RCC_AHB2RSTR_ADCRST + +/* ADC interrupts ***********************************************************/ + +#if defined(CONFIG_STM32L4_STM32L4X3) +# define STM32L4_IRQ_ADC12 STM32L4_IRQ_ADC1 +#endif + +/* ADC Channels/DMA ********************************************************/ +/* The maximum number of channels that can be sampled. While DMA support is + * very nice for reliable multi-channel sampling, the STM32L4 can function + * without, although there is a risk of overrun. + */ + +#define ADC_MAX_CHANNELS_DMA 16 +#define ADC_MAX_CHANNELS_NODMA 16 + +#ifdef ADC_HAVE_DMA +# if !defined(CONFIG_STM32L4_DMA1) && !defined(CONFIG_STM32L4_DMA2) +# /* REVISIT: check accordingly to which one is configured in board.h */ +# error "STM32L4 ADC DMA support requires CONFIG_STM32L4_DMA1 or CONFIG_STM32L4_DMA2" +# endif +#endif + +#ifdef ADC_HAVE_DMA +# define ADC_MAX_SAMPLES ADC_MAX_CHANNELS_DMA +#else +# define ADC_MAX_SAMPLES ADC_MAX_CHANNELS_NODMA +#endif + +#define ADC_DMA_CONTROL_WORD (DMA_CCR_MSIZE_16BITS | \ + DMA_CCR_PSIZE_16BITS | \ + DMA_CCR_MINC | \ + DMA_CCR_CIRC) + +/* DMA channels and interface values */ + +#define ADC_SMPR_DEFAULT ADC_SMPR_640p5 +#define ADC_SMPR1_DEFAULT ((ADC_SMPR_DEFAULT << ADC_SMPR1_SMP0_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP1_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP2_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP3_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP4_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP5_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP6_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP7_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP8_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP9_SHIFT)) +#define ADC_SMPR2_DEFAULT ((ADC_SMPR_DEFAULT << ADC_SMPR2_SMP10_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP11_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP12_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP13_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP14_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP15_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP16_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP17_SHIFT) | \ + (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP18_SHIFT)) + +/* The channels not in this range are internal channels for reading Vref + * (ADC1 only), Vbat/3, Vts or DAC outputs. The DAC outputs on ADC3 are an + * exception and not classified as internal channels by this implementation, + * as no dedicated register bits are needed for reading those. + */ + +#define ADC_EXTERNAL_CHAN_MIN 1 +#define ADC_EXTERNAL_CHAN_MAX 16 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure describes the state of one ADC block */ + +struct stm32_dev_s +{ + FAR const struct adc_callback_s *cb; + uint8_t irq; /* Interrupt generated by this ADC block */ + uint8_t nchannels; /* Number of channels */ + uint8_t cchannels; /* Number of configured channels */ + uint8_t intf; /* ADC interface number */ + uint8_t current; /* Current ADC channel being converted */ +#ifdef ADC_HAVE_DMA + uint8_t dmachan; /* DMA channel needed by this ADC */ + bool hasdma; /* True: This channel supports DMA */ +#endif +#ifdef ADC_HAVE_TIMER + uint8_t trigger; /* Timer trigger channel: 0=CC1, 1=CC2, 2=CC3, + * 3=CC4, 4=TRGO */ +#endif + xcpt_t isr; /* Interrupt handler for this ADC block */ + uint32_t base; /* Base address of registers unique to this ADC + * block */ +#ifdef ADC_HAVE_TIMER + uint32_t tbase; /* Base address of timer used by this ADC block */ + uint32_t extsel; /* EXTSEL value used by this ADC block */ + uint32_t pclck; /* The PCLK frequency that drives this timer */ + uint32_t freq; /* The desired frequency of conversions */ +#endif + +#ifdef ADC_HAVE_DMA + DMA_HANDLE dma; /* Allocated DMA channel */ + + /* DMA transfer buffer */ + + uint16_t dmabuffer[ADC_MAX_SAMPLES]; +#endif + + /* List of selected ADC channels to sample */ + + uint8_t chanlist[ADC_MAX_SAMPLES]; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* ADC Register access */ + +static void stm32_modifyreg32(unsigned int addr, uint32_t clrbits, + uint32_t setbits); +static uint32_t adc_getreg(FAR struct stm32_dev_s *priv, int offset); +static void adc_putreg(FAR struct stm32_dev_s *priv, int offset, + uint32_t value); +static void adc_modifyreg(FAR struct stm32_dev_s *priv, int offset, + uint32_t clrbits, uint32_t setbits); + +#ifdef ADC_HAVE_TIMER +static uint16_t tim_getreg(FAR struct stm32_dev_s *priv, int offset); +static void tim_putreg(FAR struct stm32_dev_s *priv, int offset, + uint16_t value); +static void tim_modifyreg(FAR struct stm32_dev_s *priv, int offset, + uint16_t clrbits, uint16_t setbits); +static void tim_dumpregs(FAR struct stm32_dev_s *priv, + FAR const char *msg); +#endif + +/* ADC Miscellaneous Helpers */ + +static void adc_rccreset(FAR struct stm32_dev_s *priv, bool reset); +static void adc_enable(FAR struct stm32_dev_s *priv); +static uint32_t adc_sqrbits(FAR struct stm32_dev_s *priv, int first, int last, + int offset); +static int adc_set_ch(FAR struct adc_dev_s *dev, uint8_t ch); +static bool adc_internal(FAR struct stm32_dev_s * priv, uint32_t *adc_ccr); + +#ifdef ADC_HAVE_TIMER +static void adc_timstart(FAR struct stm32_dev_s *priv, bool enable); +static int adc_timinit(FAR struct stm32_dev_s *priv); +#endif + +#ifdef ADC_HAVE_DMA +static void adc_dmaconvcallback(DMA_HANDLE handle, uint8_t isr, + FAR void *arg); +#endif + +static void adc_startconv(FAR struct stm32_dev_s *priv, bool enable); + +/* ADC Interrupt Handler */ + +static int adc_interrupt(FAR struct adc_dev_s *dev, uint32_t regval); +#if defined(CONFIG_STM32L4_ADC1) || defined(CONFIG_STM32L4_ADC2) +static int adc12_interrupt(int irq, FAR void *context, FAR void *arg); +#endif +#if defined(CONFIG_STM32L4_ADC3) +static int adc3_interrupt(int irq, FAR void *context, FAR void *arg); +#endif + +/* ADC Driver Methods */ + +static int adc_bind(FAR struct adc_dev_s *dev, + FAR const struct adc_callback_s *callback); +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); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* ADC interface operations */ + +static const struct adc_ops_s g_adcops = +{ + .ao_bind = adc_bind, + .ao_reset = adc_reset, + .ao_setup = adc_setup, + .ao_shutdown = adc_shutdown, + .ao_rxint = adc_rxint, + .ao_ioctl = adc_ioctl, +}; + +/* ADC1 state */ + +#ifdef CONFIG_STM32L4_ADC1 +static struct stm32_dev_s g_adcpriv1 = +{ + .irq = STM32L4_IRQ_ADC12, + .isr = adc12_interrupt, + .intf = 1, + .base = STM32L4_ADC1_BASE, +#ifdef ADC1_HAVE_TIMER + .trigger = CONFIG_STM32L4_ADC1_TIMTRIG, + .tbase = ADC1_TIMER_BASE, + .extsel = ADC1_EXTSEL_VALUE, + .pclck = ADC1_TIMER_PCLK_FREQUENCY, + .freq = CONFIG_STM32L4_ADC1_SAMPLE_FREQUENCY, +#endif +#ifdef ADC1_HAVE_DMA + .dmachan = ADC1_DMA_CHAN, + .hasdma = true, +#endif +}; + +static struct adc_dev_s g_adcdev1 = +{ + .ad_ops = &g_adcops, + .ad_priv = &g_adcpriv1, +}; +#endif + +/* ADC2 state */ + +#ifdef CONFIG_STM32L4_ADC2 +static struct stm32_dev_s g_adcpriv2 = +{ + .irq = STM32L4_IRQ_ADC12, + .isr = adc12_interrupt, + .intf = 2, + .base = STM32L4_ADC2_BASE, +#ifdef ADC2_HAVE_TIMER + .trigger = CONFIG_STM32L4_ADC2_TIMTRIG, + .tbase = ADC2_TIMER_BASE, + .extsel = ADC2_EXTSEL_VALUE, + .pclck = ADC2_TIMER_PCLK_FREQUENCY, + .freq = CONFIG_STM32L4_ADC2_SAMPLE_FREQUENCY, +#endif +#ifdef ADC2_HAVE_DMA + .dmachan = ADC2_DMA_CHAN, + .hasdma = true, +#endif +}; + +static struct adc_dev_s g_adcdev2 = +{ + .ad_ops = &g_adcops, + .ad_priv = &g_adcpriv2, +}; +#endif + +/* ADC3 state */ + +#ifdef CONFIG_STM32L4_ADC3 +static struct stm32_dev_s g_adcpriv3 = +{ + .irq = STM32L4_IRQ_ADC3, + .isr = adc3_interrupt, + .intf = 3, + .base = STM32L4_ADC3_BASE, +#ifdef ADC3_HAVE_TIMER + .trigger = CONFIG_STM32L4_ADC3_TIMTRIG, + .tbase = ADC3_TIMER_BASE, + .extsel = ADC3_EXTSEL_VALUE, + .pclck = ADC3_TIMER_PCLK_FREQUENCY, + .freq = CONFIG_STM32L4_ADC3_SAMPLE_FREQUENCY, +#endif +#ifdef ADC3_HAVE_DMA + .dmachan = ADC3_DMA_CHAN, + .hasdma = true, +#endif +}; + +static struct adc_dev_s g_adcdev3 = +{ + .ad_ops = &g_adcops, + .ad_priv = &g_adcpriv3, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32_modifyreg32 + * + * Description: + * Modify the value of a 32-bit register (not atomic). + * + * Input Parameters: + * addr - The address of the register + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_modifyreg32(unsigned int addr, uint32_t clrbits, + uint32_t setbits) +{ + putreg32((getreg32(addr) & ~clrbits) | setbits, addr); +} + +/**************************************************************************** + * 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: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t adc_getreg(FAR struct stm32_dev_s *priv, int offset) +{ + return getreg32(priv->base + offset); +} + +/**************************************************************************** + * Name: adc_putreg + * + * Description: + * Write a value to an ADC register. + * + * Input Parameters: + * priv - A reference to the ADC block status + * offset - The offset to the register to write to + * value - The value to write to the register + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void adc_putreg(FAR struct stm32_dev_s *priv, int offset, + uint32_t value) +{ + putreg32(value, priv->base + offset); +} + +/**************************************************************************** + * Name: adc_modifyreg + * + * Description: + * Modify the value of an ADC register (not atomic). + * + * Input Parameters: + * priv - A reference to the ADC block status + * offset - The offset to the register to modify + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void adc_modifyreg(FAR struct stm32_dev_s *priv, int offset, + uint32_t clrbits, uint32_t setbits) +{ + adc_putreg(priv, offset, (adc_getreg(priv, offset) & ~clrbits) | setbits); +} + +/**************************************************************************** + * Name: tim_getreg + * + * Description: + * Read the value of an ADC timer register. + * + * Input Parameters: + * priv - A reference to the ADC block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +#ifdef ADC_HAVE_TIMER +static uint16_t tim_getreg(FAR struct stm32_dev_s *priv, int offset) +{ + return getreg16(priv->tbase + offset); +} +#endif + +/**************************************************************************** + * Name: tim_putreg + * + * Description: + * Write a value to an ADC timer register. + * + * Input Parameters: + * priv - A reference to the ADC block status + * offset - The offset to the register to write to + * value - The value to write to the register + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef ADC_HAVE_TIMER +static void tim_putreg(FAR struct stm32_dev_s *priv, int offset, + uint16_t value) +{ + putreg16(value, priv->tbase + offset); +} +#endif + +/**************************************************************************** + * Name: tim_modifyreg + * + * Description: + * Modify the value of an ADC timer register (not atomic). + * + * Input Parameters: + * priv - A reference to the ADC block status + * offset - The offset to the register to modify + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef ADC_HAVE_TIMER +static void tim_modifyreg(FAR struct stm32_dev_s *priv, int offset, + uint16_t clrbits, uint16_t setbits) +{ + tim_putreg(priv, offset, (tim_getreg(priv, offset) & ~clrbits) | setbits); +} +#endif + +/**************************************************************************** + * Name: 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 tim_dumpregs(FAR struct stm32_dev_s *priv, FAR const char *msg) +{ + ainfo("%s:\n", msg); + ainfo(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + tim_getreg(priv, STM32L4_GTIM_CR1_OFFSET), + tim_getreg(priv, STM32L4_GTIM_CR2_OFFSET), + tim_getreg(priv, STM32L4_GTIM_SMCR_OFFSET), + tim_getreg(priv, STM32L4_GTIM_DIER_OFFSET)); + ainfo(" SR: %04x EGR: 0000 CCMR1: %04x CCMR2: %04x\n", + tim_getreg(priv, STM32L4_GTIM_SR_OFFSET), + tim_getreg(priv, STM32L4_GTIM_CCMR1_OFFSET), + tim_getreg(priv, STM32L4_GTIM_CCMR2_OFFSET)); + ainfo(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + tim_getreg(priv, STM32L4_GTIM_CCER_OFFSET), + tim_getreg(priv, STM32L4_GTIM_CNT_OFFSET), + tim_getreg(priv, STM32L4_GTIM_PSC_OFFSET), + tim_getreg(priv, STM32L4_GTIM_ARR_OFFSET)); + ainfo(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + tim_getreg(priv, STM32L4_GTIM_CCR1_OFFSET), + tim_getreg(priv, STM32L4_GTIM_CCR2_OFFSET), + tim_getreg(priv, STM32L4_GTIM_CCR3_OFFSET), + tim_getreg(priv, STM32L4_GTIM_CCR4_OFFSET)); + + if (priv->tbase == STM32L4_TIM1_BASE || priv->tbase == STM32L4_TIM8_BASE) + { + ainfo(" RCR: %04x BDTR: %04x DCR: %04x DMAR: %04x\n", + tim_getreg(priv, STM32L4_ATIM_RCR_OFFSET), + tim_getreg(priv, STM32L4_ATIM_BDTR_OFFSET), + tim_getreg(priv, STM32L4_ATIM_DCR_OFFSET), + tim_getreg(priv, STM32L4_ATIM_DMAR_OFFSET)); + } + else + { + ainfo(" DCR: %04x DMAR: %04x\n", + tim_getreg(priv, STM32L4_GTIM_DCR_OFFSET), + tim_getreg(priv, STM32L4_GTIM_DMAR_OFFSET)); + } +} +#endif + +/**************************************************************************** + * Name: adc_timstart + * + * Description: + * Start (or stop) the timer counter + * + * Input Parameters: + * priv - A reference to the ADC block status + * enable - True: Start conversion + * + * Returned Value: + * + ****************************************************************************/ + +#ifdef ADC_HAVE_TIMER +static void adc_timstart(FAR struct stm32_dev_s *priv, bool enable) +{ + ainfo("enable: %d\n", enable ? 1 : 0); + + if (enable) + { + /* Start the counter */ + + tim_modifyreg(priv, STM32L4_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); + } + else + { + /* Disable the counter */ + + tim_modifyreg(priv, STM32L4_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + } +} +#endif + +/**************************************************************************** + * Name: adc_timinit + * + * Description: + * Initialize the timer that drivers the ADC sampling for this channel + * using the pre-calculated timer divider definitions. + * + * Input Parameters: + * priv - A reference to the ADC block status + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef ADC_HAVE_TIMER +static int adc_timinit(FAR struct stm32_dev_s *priv) +{ + uint32_t prescaler; + uint32_t reload; + uint32_t timclk; + + uint16_t clrbits = 0; + uint16_t setbits = 0; + uint16_t cr2; + uint16_t ccmr1; + uint16_t ccmr2; + uint16_t ocmode1; + uint16_t ocmode2; + uint16_t ccenable; + uint16_t ccer; + uint16_t egr; + + /* If the timer base address is zero, then this ADC was not configured to + * use a timer. + */ + + if (priv->tbase == 0) + { + return ERROR; + } + + /* EXTSEL selection: These bits select the external event used to trigger + * the start of conversion of a regular group. NOTE: + * + * - The position with of the EXTSEL field varies from one STM32 MCU + * to another. + * - The width of the EXTSEL field varies from one STM32 MCU to another. + * - The value in priv->extsel is already shifted into the correct bit + * position. + */ + + ainfo("Initializing timers extsel = 0x%08x\n", priv->extsel); + + adc_modifyreg(priv, STM32L4_ADC_CFGR_OFFSET, + ADC_CFGR_EXTEN_MASK | ADC_CFGR_EXTSEL_MASK, + ADC_CFGR_EXTEN_RISING | priv->extsel); + + /* Configure the timer channel to drive the ADC */ + + /* Caculate optimal values for the timer prescaler and for the timer + * reload register. If freq is the desired frequency, then + * + * reload = timclk / freq + * reload = (pclck / prescaler) / freq + * + * There are many solutions to do this, but the best solution will be the + * one that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= prescaler <= 65536 + * 1 <= reload <= 65535 + * + * So ( prescaler = pclck / 65535 / freq ) would be optimal. + */ + + prescaler = (priv->pclck / priv->freq + 65534) / 65535; + + /* We need to decrement the prescaler value by one, but only, the value + * does not underflow. + */ + + if (prescaler < 1) + { + awarn("WARNING: Prescaler underflowed.\n"); + prescaler = 1; + } + + /* Check for overflow */ + + else if (prescaler > 65536) + { + awarn("WARNING: Prescaler overflowed.\n"); + prescaler = 65536; + } + + timclk = priv->pclck / prescaler; + + reload = timclk / priv->freq; + if (reload < 1) + { + awarn("WARNING: Reload value underflowed.\n"); + reload = 1; + } + + else if (reload > 65535) + { + awarn("WARNING: Reload value overflowed.\n"); + reload = 65535; + } + + /* Disable the timer until we get it configured */ + + adc_timstart(priv, false); + + /* Set up the timer CR1 register. + * + * Select the Counter Mode == count up: + * + * ATIM_CR1_EDGE: The counter counts up or down depending on the + * direction bit(DIR). + * ATIM_CR1_DIR: 0: count up, 1: count down + * + * Set the clock division to zero for all + */ + + clrbits = GTIM_CR1_DIR | GTIM_CR1_CMS_MASK | GTIM_CR1_CKD_MASK; + setbits = GTIM_CR1_EDGE; + tim_modifyreg(priv, STM32L4_GTIM_CR1_OFFSET, clrbits, setbits); + + /* Set the reload and prescaler values */ + + tim_putreg(priv, STM32L4_GTIM_PSC_OFFSET, prescaler-1); + tim_putreg(priv, STM32L4_GTIM_ARR_OFFSET, reload); + + /* Clear the advanced timers repetition counter in TIM1 */ + + if (priv->tbase == STM32L4_TIM1_BASE || priv->tbase == STM32L4_TIM8_BASE) + { + tim_putreg(priv, STM32L4_ATIM_RCR_OFFSET, 0); + tim_putreg(priv, STM32L4_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE); /* Check me */ + } + + /* TIMx event generation: Bit 0 UG: Update generation */ + + tim_putreg(priv, STM32L4_GTIM_EGR_OFFSET, GTIM_EGR_UG); + + /* Handle channel specific setup */ + + ocmode1 = 0; + ocmode2 = 0; + + switch (priv->trigger) + { + case 0: /* TimerX CC1 event */ + { + ccenable = ATIM_CCER_CC1E; + ocmode1 = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT) | + (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR1_OC1M_SHIFT) | + ATIM_CCMR1_OC1PE; + + /* Set the event CC1 */ + + egr = ATIM_EGR_CC1G; + + /* Set the duty cycle by writing to the CCR register for this + * channel + */ + + tim_putreg(priv, STM32L4_GTIM_CCR1_OFFSET, (uint16_t)(reload >> 1)); + } + break; + + case 1: /* TimerX CC2 event */ + { + ccenable = ATIM_CCER_CC2E; + ocmode1 = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT) | + (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR1_OC2M_SHIFT) | + ATIM_CCMR1_OC2PE; + + /* Set the event CC2 */ + + egr = ATIM_EGR_CC2G; + + /* Set the duty cycle by writing to the CCR register for this + * channel + */ + + tim_putreg(priv, STM32L4_GTIM_CCR2_OFFSET, (uint16_t)(reload >> 1)); + } + break; + + case 2: /* TimerX CC3 event */ + { + ccenable = ATIM_CCER_CC3E; + ocmode2 = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR2_CC3S_SHIFT) | + (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR2_OC3M_SHIFT) | + ATIM_CCMR2_OC3PE; + + /* Set the event CC3 */ + + egr = ATIM_EGR_CC3G; + + /* Set the duty cycle by writing to the CCR register for this + * channel + */ + + tim_putreg(priv, STM32L4_GTIM_CCR3_OFFSET, (uint16_t)(reload >> 1)); + } + break; + + case 3: /* TimerX CC4 event */ + { + ccenable = ATIM_CCER_CC4E; + ocmode2 = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR2_CC4S_SHIFT) | + (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR2_OC4M_SHIFT) | + ATIM_CCMR2_OC4PE; + + /* Set the event CC4 */ + + egr = ATIM_EGR_CC4G; + + /* Set the duty cycle by writing to the CCR register for this + * channel + */ + + tim_putreg(priv, STM32L4_GTIM_CCR4_OFFSET, (uint16_t)(reload >> 1)); + } + break; + + case 4: /* TimerX TRGO event */ + { + /* TODO: TRGO support not yet implemented */ + /* Set the event TRGO */ + + ccenable = 0; + egr = GTIM_EGR_TG; + + /* Set the duty cycle by writing to the CCR register for this + * channel + */ + + tim_putreg(priv, STM32L4_GTIM_CCR4_OFFSET, (uint16_t)(reload >> 1)); + } + break; + + default: + aerr("ERROR: No such trigger: %d\n", priv->trigger); + return -EINVAL; + } + + /* Disable the Channel by resetting the CCxE Bit in the CCER register */ + + ccer = tim_getreg(priv, STM32L4_GTIM_CCER_OFFSET); + ccer &= ~ccenable; + tim_putreg(priv, STM32L4_GTIM_CCER_OFFSET, ccer); + + /* Fetch the CR2, CCMR1, and CCMR2 register (already have ccer) */ + + cr2 = tim_getreg(priv, STM32L4_GTIM_CR2_OFFSET); + ccmr1 = tim_getreg(priv, STM32L4_GTIM_CCMR1_OFFSET); + ccmr2 = tim_getreg(priv, STM32L4_GTIM_CCMR2_OFFSET); + + /* Reset the Output Compare Mode Bits and set the select output compare + * mode + */ + + ccmr1 &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | ATIM_CCMR1_OC1PE | + ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | ATIM_CCMR1_OC2PE); + ccmr2 &= ~(ATIM_CCMR2_CC3S_MASK | ATIM_CCMR2_OC3M_MASK | ATIM_CCMR2_OC3PE | + ATIM_CCMR2_CC4S_MASK | ATIM_CCMR2_OC4M_MASK | ATIM_CCMR2_OC4PE); + ccmr1 |= ocmode1; + ccmr2 |= ocmode2; + + /* Reset the output polarity level of all channels (selects high + * polarity) + */ + + ccer &= ~(ATIM_CCER_CC1P | ATIM_CCER_CC2P | + ATIM_CCER_CC3P | ATIM_CCER_CC4P); + + /* Enable the output state of the selected channel (only) */ + + ccer &= ~(ATIM_CCER_CC1E | ATIM_CCER_CC2E | + ATIM_CCER_CC3E | ATIM_CCER_CC4E); + ccer |= ccenable; + + if (priv->tbase == STM32L4_TIM1_BASE || priv->tbase == STM32L4_TIM8_BASE) + { + /* Reset output N polarity level, output N state, output compare state, + * output compare N idle state. + */ + + ccer &= ~(ATIM_CCER_CC1NE | ATIM_CCER_CC1NP | + ATIM_CCER_CC2NE | ATIM_CCER_CC2NP | + ATIM_CCER_CC3NE | ATIM_CCER_CC3NP | + ATIM_CCER_CC4NP); + + /* Reset the output compare and output compare N IDLE State */ + + cr2 &= ~(ATIM_CR2_OIS1 | ATIM_CR2_OIS1N | + ATIM_CR2_OIS2 | ATIM_CR2_OIS2N | + ATIM_CR2_OIS3 | ATIM_CR2_OIS3N | + ATIM_CR2_OIS4); + } + else + { + ccer &= ~(GTIM_CCER_CC1NP | GTIM_CCER_CC2NP | GTIM_CCER_CC3NP); + } + + /* Save the modified register values */ + + tim_putreg(priv, STM32L4_GTIM_CR2_OFFSET, cr2); + tim_putreg(priv, STM32L4_GTIM_CCMR1_OFFSET, ccmr1); + tim_putreg(priv, STM32L4_GTIM_CCMR2_OFFSET, ccmr2); + tim_putreg(priv, STM32L4_GTIM_CCER_OFFSET, ccer); + tim_putreg(priv, STM32L4_GTIM_EGR_OFFSET, egr); + + /* Set the ARR Preload Bit */ + + tim_modifyreg(priv, STM32L4_GTIM_CR1_OFFSET, 0, GTIM_CR1_ARPE); + + /* Enable the timer counter */ + + adc_timstart(priv, true); + + tim_dumpregs(priv, "After starting timers"); + + return OK; +} +#endif + +/**************************************************************************** + * Name: adc_wdog_enable + * + * Description: + * Enable the analog watchdog. + * + ****************************************************************************/ + +static void adc_wdog_enable(struct stm32_dev_s *priv) +{ + uint32_t regval; + + /* Initialize the Analog watchdog enable */ + + regval = adc_getreg(priv, STM32L4_ADC_CFGR_OFFSET); + regval |= ADC_CFGR_AWD1EN | ADC_CFGR_CONT | ADC_CFGR_OVRMOD; + adc_putreg(priv, STM32L4_ADC_CFGR_OFFSET, regval); + + /* Switch to analog watchdog interrupt */ + + regval = adc_getreg(priv, STM32L4_ADC_IER_OFFSET); + regval |= ADC_INT_AWD1; + regval &= ~ADC_INT_EOC; + adc_putreg(priv, STM32L4_ADC_IER_OFFSET, regval); +} + +/**************************************************************************** + * Name: adc_startconv + * + * Description: + * Start (or stop) the ADC conversion process + * + * Input Parameters: + * priv - A reference to the ADC block status + * enable - True: Start conversion + * + * Returned Value: + * + ****************************************************************************/ + +static void adc_startconv(FAR struct stm32_dev_s *priv, bool enable) +{ + uint32_t regval; + + ainfo("enable: %d\n", enable ? 1 : 0); + + regval = adc_getreg(priv, STM32L4_ADC_CR_OFFSET); + if (enable) + { + /* Start conversion of regular channels */ + + regval |= ADC_CR_ADSTART; + } + else + { + /* Disable the conversion of regular channels */ + + regval |= ADC_CR_ADSTP; + } + + adc_putreg(priv, STM32L4_ADC_CR_OFFSET, regval); +} + +/**************************************************************************** + * Name: adc_rccreset + * + * Description: + * Deinitializes the ADCx peripheral registers to their default + * reset values. It could set all the ADCs configured. + * + * Input Parameters: + * priv - A reference to the ADC block status + * reset - Condition, set or reset + * + * Returned Value: + * + ****************************************************************************/ + +static void adc_rccreset(FAR struct stm32_dev_s *priv, bool reset) +{ + irqstate_t flags; + uint32_t regval; + + /* Pick the appropriate bit in the AHB2 reset register. + * The STM32L4 is like STM32F2/F4: there is one common reset for all ADCs. + * + * First must disable interrupts because the AHB2RSTR register is used by + * several different drivers. + */ + + flags = enter_critical_section(); + + /* Set or clear the selected bit in the AHB2 reset register */ + + regval = getreg32(STM32L4_RCC_AHB2RSTR); + if (reset) + { + regval |= RCC_AHB2RSTR_ADCRST; + } + else + { + regval &= ~RCC_AHB2RSTR_ADCRST; + } + + putreg32(regval, STM32L4_RCC_AHB2RSTR); + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: adc_enable + * + * Description : Enables the specified ADC peripheral. + * + * Input Parameters: + * priv - A reference to the ADC block status + * + * Returned Value: + * + ****************************************************************************/ + +static void adc_enable(FAR struct stm32_dev_s *priv) +{ + uint32_t regval; + + regval = adc_getreg(priv, STM32L4_ADC_CR_OFFSET); + + /* Exit deep power down mode and enable voltage regulator */ + + regval &= ~ADC_CR_DEEPPWD; + regval |= ADC_CR_ADVREGEN; + adc_putreg(priv, STM32L4_ADC_CR_OFFSET, regval); + + /* Wait for voltage regulator to power up */ + + up_udelay(20); + + /* Enable ADC calibration. ADCALDIF == 0 so this is only for + * single-ended conversions, not for differential ones. + */ + + regval |= ADC_CR_ADCAL; + adc_putreg(priv, STM32L4_ADC_CR_OFFSET, regval); + + /* Wait for calibration to complete */ + + while (adc_getreg(priv, STM32L4_ADC_CR_OFFSET) & ADC_CR_ADCAL); + + /* Enable ADC + * Note: ADEN bit cannot be set during ADCAL=1 and 4 ADC clock cycle + * after the ADCAL bit is cleared by hardware. If we are using SYSCLK + * as ADC clock source, this is the same as time taken to execute 4 + * ARM instructions. + */ + + regval = adc_getreg(priv, STM32L4_ADC_CR_OFFSET); + regval |= ADC_CR_ADEN; + adc_putreg(priv, STM32L4_ADC_CR_OFFSET, regval); + + /* Wait for hardware to be ready for conversions */ + + while (!(adc_getreg(priv, STM32L4_ADC_ISR_OFFSET) & ADC_INT_ADRDY)); +} + +/**************************************************************************** + * Name: adc_bind + * + * Description: + * Bind the upper-half driver callbacks to the lower-half implementation. This + * must be called early in order to receive ADC event notifications. + * + ****************************************************************************/ + +static int adc_bind(FAR struct adc_dev_s *dev, + FAR const struct adc_callback_s *callback) +{ + FAR struct stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + + DEBUGASSERT(priv != NULL); + priv->cb = callback; + return OK; +} + +/**************************************************************************** + * 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 stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + + ainfo("intf: ADC%d\n", priv->intf); + + /* Enable ADC reset state */ + + adc_rccreset(priv, true); + + /* Release ADC from reset state */ + + adc_rccreset(priv, false); +} + +/**************************************************************************** + * 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 stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + int ret; + irqstate_t flags; + uint32_t clrbits; + uint32_t setbits; + + /* Attach the ADC interrupt */ + + ret = irq_attach(priv->irq, priv->isr, NULL); + if (ret < 0) + { + ainfo("irq_attach failed: %d\n", ret); + return ret; + } + + flags = enter_critical_section(); + + /* Make sure that the ADC device is in the powered up, reset state. */ + + adc_reset(dev); + + /* Initialize the same sample time for each ADC. + * During sample cycles channel selection bits must remain unchanged. + */ + + adc_putreg(priv, STM32L4_ADC_SMPR1_OFFSET, ADC_SMPR1_DEFAULT); + adc_putreg(priv, STM32L4_ADC_SMPR2_OFFSET, ADC_SMPR2_DEFAULT); + + /* Set the resolution of the conversion. */ + + clrbits = ADC_CFGR_RES_MASK; + setbits = ADC_CFGR_RES_12BIT; + +#ifdef ADC_HAVE_DMA + if (priv->hasdma) + { + /* Set DMA one shot mode */ + + clrbits |= ADC_CFGR_DMACFG; + + /* Enable DMA */ + + setbits |= ADC_CFGR_DMAEN; + } +#endif + + /* Disable continuous mode and set align to right */ + + clrbits |= ADC_CFGR_CONT | ADC_CFGR_ALIGN; + + /* Disable external trigger for regular channels */ + + clrbits |= ADC_CFGR_EXTEN_MASK; + setbits |= ADC_CFGR_EXTEN_NONE; + + /* Set CFGR configuration */ + + adc_modifyreg(priv, STM32L4_ADC_CFGR_OFFSET, clrbits, setbits); + + /* Configuration of the channel conversions */ + + adc_set_ch(dev, 0); + + /* ADC CCR configuration */ + + clrbits = ADC_CCR_PRESC_MASK | ADC_CCR_VREFEN | + ADC_CCR_TSEN | ADC_CCR_VBATEN; + setbits = ADC_CCR_PRESC_NOT_DIV; + + /* REVISIT: there is no way to select DAC1 or DAC2 output here on + * STM32L4X3 devices where they are multiplexed with ADC1 TSEN and VBAT. + */ + + adc_internal(priv, &setbits); + + stm32_modifyreg32(STM32L4_ADC_CCR, clrbits, setbits); + +#ifdef ADC_HAVE_DMA + + /* Enable DMA */ + + if (priv->hasdma) + { + /* Stop and free DMA if it was started before */ + + if (priv->dma != NULL) + { + stm32l4_dmastop(priv->dma); + stm32l4_dmafree(priv->dma); + } + + priv->dma = stm32l4_dmachannel(priv->dmachan); + + stm32l4_dmasetup(priv->dma, + priv->base + STM32L4_ADC_DR_OFFSET, + (uint32_t)priv->dmabuffer, + priv->nchannels, + ADC_DMA_CONTROL_WORD); + + stm32l4_dmastart(priv->dma, adc_dmaconvcallback, dev, false); + } + +#endif + + /* Set ADEN to wake up the ADC from Power Down. */ + + adc_enable(priv); + +#ifdef ADC_HAVE_TIMER + if (priv->tbase != 0) + { + ret = adc_timinit(priv); + if (ret < 0) + { + aerr("ERROR: adc_timinit failed: %d\n", ret); + } + } +#endif + + leave_critical_section(flags); + + ainfo("ISR: 0x%08x CR: 0x%08x CFGR: 0x%08x CFGR2: 0x%08x\n", + adc_getreg(priv, STM32L4_ADC_ISR_OFFSET), + adc_getreg(priv, STM32L4_ADC_CR_OFFSET), + adc_getreg(priv, STM32L4_ADC_CFGR_OFFSET), + adc_getreg(priv, STM32L4_ADC_CFGR2_OFFSET)); + ainfo("SQR1: 0x%08x SQR2: 0x%08x SQR3: 0x%08x SQR4: 0x%08x\n", + adc_getreg(priv, STM32L4_ADC_SQR1_OFFSET), + adc_getreg(priv, STM32L4_ADC_SQR2_OFFSET), + adc_getreg(priv, STM32L4_ADC_SQR3_OFFSET), + adc_getreg(priv, STM32L4_ADC_SQR4_OFFSET)); + ainfo("CCR: 0x%08x\n", getreg32(STM32L4_ADC_CCR)); + + /* Enable the ADC interrupt */ + + ainfo("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 stm32_dev_s *priv = (FAR struct stm32_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_reset(dev); +} + +/**************************************************************************** + * 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 stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + uint32_t regval; + + ainfo("intf: %d enable: %d\n", priv->intf, enable ? 1 : 0); + + regval = adc_getreg(priv, STM32L4_ADC_IER_OFFSET); + if (enable) + { + /* Enable end of conversion interrupt */ + + regval |= ADC_INT_EOC; + } + else + { + /* Disable all interrupts */ + + regval &= ~ADC_INT_MASK; + } + adc_putreg(priv, STM32L4_ADC_IER_OFFSET, regval); +} + +/**************************************************************************** + * Name: adc_sqrbits + ****************************************************************************/ + +static uint32_t adc_sqrbits(FAR struct stm32_dev_s *priv, int first, int last, + int offset) +{ + uint32_t bits = 0; + int i; + + for (i = first - 1; + i < priv->nchannels && i < last; + i++, offset += ADC_SQ_OFFSET) + { + bits |= (uint32_t)priv->chanlist[i] << offset; + } + + return bits; +} + +/**************************************************************************** + * Name: adc_internal + ****************************************************************************/ + +static bool adc_internal(FAR struct stm32_dev_s * priv, uint32_t *adc_ccr) +{ + int i; + bool internal = false; + + if (priv->intf == 1 || priv->intf == 3) + { + for (i = 0; i < priv->nchannels; i++) + { + if (priv->chanlist[i] < ADC_EXTERNAL_CHAN_MIN || \ + priv->chanlist[i] > ADC_EXTERNAL_CHAN_MAX) + { + internal = true; + switch (priv->chanlist[i]) + { + case 0: + if (priv->intf == 1) + { + *adc_ccr |= ADC_CCR_VREFEN; + } + break; + + case 17: + *adc_ccr |= ADC_CCR_TSEN; + break; + + case 18: + *adc_ccr |= ADC_CCR_VBATEN; + break; + } + } + + } + } + + /* REVISIT: ADC2 or ADC3 DAC outputs not considered internal. */ + + return internal; +} + +/**************************************************************************** + * Name: adc_set_ch + * + * Description: + * Sets the ADC channel. + * + * Input Parameters: + * dev - pointer to device structure used by the driver + * ch - ADC channel number + 1. 0 reserved for all configured channels + * + * Returned Value: + * int - errno + * + ****************************************************************************/ + +static int adc_set_ch(FAR struct adc_dev_s *dev, uint8_t ch) +{ + FAR struct stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + uint32_t bits; + int i; + + if (ch == 0) + { + priv->current = 0; + priv->nchannels = priv->cchannels; + } + else + { + for (i = 0; i < priv->cchannels && priv->chanlist[i] != ch - 1; i++); + + if (i >= priv->cchannels) + { + return -ENODEV; + } + + priv->current = i; + priv->nchannels = 1; + } + + DEBUGASSERT(priv->nchannels <= ADC_MAX_SAMPLES); + + bits = adc_sqrbits(priv, ADC_SQR4_FIRST, ADC_SQR4_LAST, ADC_SQR4_SQ_OFFSET); + adc_modifyreg(priv, STM32L4_ADC_SQR4_OFFSET, ~ADC_SQR4_RESERVED, bits); + + bits = adc_sqrbits(priv, ADC_SQR3_FIRST, ADC_SQR3_LAST, ADC_SQR3_SQ_OFFSET); + adc_modifyreg(priv, STM32L4_ADC_SQR3_OFFSET, ~ADC_SQR3_RESERVED, bits); + + bits = adc_sqrbits(priv, ADC_SQR2_FIRST, ADC_SQR2_LAST, ADC_SQR2_SQ_OFFSET); + adc_modifyreg(priv, STM32L4_ADC_SQR2_OFFSET, ~ADC_SQR2_RESERVED, bits); + + bits = ((uint32_t)priv->nchannels - 1) << ADC_SQR1_L_SHIFT | + adc_sqrbits(priv, ADC_SQR1_FIRST, ADC_SQR1_LAST, ADC_SQR1_SQ_OFFSET); + adc_modifyreg(priv, STM32L4_ADC_SQR1_OFFSET, ~ADC_SQR1_RESERVED, bits); + + return OK; +} + +/**************************************************************************** + * Name: adc_ioctl + * + * Description: + * All ioctl calls will be routed through this method. + * + * Input Parameters: + * dev - pointer to device structure used by the driver + * cmd - command + * arg - arguments passed with command + * + * Returned Value: + * + ****************************************************************************/ + +static int adc_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + int ret = OK; + + switch (cmd) + { + case ANIOC_TRIGGER: + adc_startconv(priv, true); + break; + + default: + aerr("ERROR: Unknown cmd: %d\n", cmd); + ret = -ENOTTY; + break; + } + + return ret; +} + +/**************************************************************************** + * Name: adc_interrupt + * + * Description: + * Common ADC interrupt handler. + * + * Input Parameters: + * + * Returned Value: + * + ****************************************************************************/ + +static int adc_interrupt(FAR struct adc_dev_s *dev, uint32_t adcisr) +{ + FAR struct stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + int32_t value; + + /* Identifies the interruption AWD or OVR */ + + if ((adcisr & ADC_INT_AWD1) != 0) + { + value = adc_getreg(priv, STM32L4_ADC_DR_OFFSET); + value &= ADC_DR_MASK; + + awarn("WARNING: Analog Watchdog, Value (0x%03x) out of range!\n", value); + + /* Stop ADC conversions to avoid continuous interrupts */ + + adc_startconv(priv, false); + } + + if ((adcisr & ADC_INT_OVR) != 0) + { + awarn("WARNING: Overrun has occurred!\n"); + } + + /* EOC: End of conversion */ + + if ((adcisr & ADC_INT_EOC) != 0) + { + /* Read the converted value and clear EOC bit + * (It is cleared by reading the ADC_DR) + */ + + value = adc_getreg(priv, STM32L4_ADC_DR_OFFSET); + value &= ADC_DR_MASK; + + /* Verify that the upper-half driver has bound its callback functions */ + + if (priv->cb != NULL) + { + /* Give the ADC data to the ADC driver. The ADC receive() method + * 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. + */ + + DEBUGASSERT(priv->cb->au_receive != NULL); + priv->cb->au_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; +} + +/**************************************************************************** + * Name: adc12_interrupt + * + * Description: + * ADC1/2 interrupt handler + * + * Input Parameters: + * + * Returned Value: + * + ****************************************************************************/ + +#if defined(CONFIG_STM32L4_ADC1) || defined(CONFIG_STM32L4_ADC2) +static int adc12_interrupt(int irq, FAR void *context, FAR void *arg) +{ + uint32_t regval; + uint32_t pending; + +#ifdef CONFIG_STM32L4_ADC1 + regval = getreg32(STM32L4_ADC1_ISR); + pending = regval & ADC_INT_MASK; + if (pending != 0) + { + adc_interrupt(&g_adcdev1, regval); + + /* Clear interrupts */ + + putreg32(regval, STM32L4_ADC1_ISR); + } +#endif + +#ifdef CONFIG_STM32L4_ADC2 + regval = getreg32(STM32L4_ADC2_ISR); + pending = regval & ADC_INT_MASK; + if (pending != 0) + { + adc_interrupt(&g_adcdev2, regval); + + /* Clear interrupts */ + + putreg32(regval, STM32L4_ADC2_ISR); + } +#endif + + return OK; +} +#endif + +/**************************************************************************** + * Name: adc3_interrupt + * + * Description: + * ADC3 interrupt handler + * + * Input Parameters: + * + * Returned Value: + * + ****************************************************************************/ +#ifdef CONFIG_STM32L4_ADC3 +static int adc3_interrupt(int irq, FAR void *context, FAR void *arg) +{ + uint32_t regval; + uint32_t pending; + + regval = getreg32(STM32L4_ADC3_ISR); + pending = regval & ADC_INT_MASK; + if (pending != 0) + { + adc_interrupt(&g_adcdev3, regval); + + /* Clear interrupts */ + + putreg32(regval, STM32L4_ADC3_ISR); + } + + return OK; +} +#endif + +/**************************************************************************** + * Name: adc_dmaconvcallback + * + * Description: + * Callback for DMA. Called from the DMA transfer complete interrupt after + * all channels have been converted and transferred with DMA. + * + * Input Parameters: + * + * handle - handle to DMA + * isr - + * arg - adc device + * + * Returned Value: + * + ****************************************************************************/ + +#ifdef ADC_HAVE_DMA +static void adc_dmaconvcallback(DMA_HANDLE handle, uint8_t isr, FAR void *arg) +{ + FAR struct adc_dev_s *dev = (FAR struct adc_dev_s *)arg; + FAR struct stm32_dev_s *priv = (FAR struct stm32_dev_s *)dev->ad_priv; + int i; + + /* Verify that the upper-half driver has bound its callback functions */ + + if (priv->cb != NULL) + { + DEBUGASSERT(priv->cb->au_receive != NULL); + + for (i = 0; i < priv->nchannels; i++) + { + priv->cb->au_receive(dev, priv->chanlist[priv->current], priv->dmabuffer[priv->current]); + priv->current++; + if (priv->current >= priv->nchannels) + { + /* Restart the conversion sequence from the beginning */ + + priv->current = 0; + } + } + } + + /* Restart DMA for the next conversion series */ + + adc_modifyreg(priv, STM32L4_ADC_CFGR_OFFSET, ADC_CFGR_DMAEN, 0); + adc_modifyreg(priv, STM32L4_ADC_CFGR_OFFSET, 0, ADC_CFGR_DMAEN); +} +#endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** - * Name: stm32_adc_initialize + * Name: stm32l4_adc_initialize * * Description: * Initialize the ADC. @@ -105,7 +1784,8 @@ struct adc_dev_s *stm32l4_adc_initialize(int intf, FAR const uint8_t *chanlist, int cchannels) { - FAR struct adc_dev_s *dev = NULL; + FAR struct adc_dev_s *dev; + FAR struct stm32_dev_s *priv; ainfo("intf: %d cchannels: %d\n", intf, cchannels); @@ -114,19 +1794,19 @@ struct adc_dev_s *stm32l4_adc_initialize(int intf, FAR const uint8_t *chanlist, #ifdef CONFIG_STM32L4_ADC1 case 1: ainfo("ADC1 selected\n"); - //dev = &g_adcdev1; + dev = &g_adcdev1; break; #endif #ifdef CONFIG_STM32L4_ADC2 case 2: ainfo("ADC2 selected\n"); - //dev = &g_adcdev2; + dev = &g_adcdev2; break; #endif #ifdef CONFIG_STM32L4_ADC3 case 3: ainfo("ADC3 selected\n"); - //dev = &g_adcdev3; + dev = &g_adcdev3; break; #endif default: @@ -134,12 +1814,23 @@ struct adc_dev_s *stm32l4_adc_initialize(int intf, FAR const uint8_t *chanlist, return NULL; } -#warning "Missing ADC implementation!" + /* Configure the selected ADC */ + + priv = (FAR struct stm32_dev_s *)dev->ad_priv; + priv->cb = NULL; + + DEBUGASSERT(cchannels <= ADC_MAX_SAMPLES); + if (cchannels > ADC_MAX_SAMPLES) + { + cchannels = ADC_MAX_SAMPLES; + } + + priv->cchannels = cchannels; + memcpy(priv->chanlist, chanlist, cchannels); return dev; } #endif /* CONFIG_STM32L4_STM32L4X3 || CONFIG_STM32L4_STM32L4X6 */ -#endif /* CONFIG_STM32L4_ADC1 || CONFIG_STM32L4_ADC2 || CONFIG_STM32L4_ADC3 - */ +#endif /* CONFIG_STM32L4_ADC1 || CONFIG_STM32L4_ADC2 || CONFIG_STM32L4_ADC3 */ #endif /* CONFIG_ADC */