| /**************************************************************************** |
| * arch/arm/src/stm32/stm32_sdadc.c |
| * |
| * Copyright (C) 2011, 2013, 2015-2017 Gregory Nutt. All rights reserved. |
| * Copyright (C) 2016 Studelec. All rights reserved. |
| * Authors: Gregory Nutt <gnutt@nuttx.org> |
| * Marc Rechté <mrechte@studelec-sa.com> |
| * |
| * derived from arch/arm/src/stm32/stm32_adc.c |
| * |
| * 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 <nuttx/config.h> |
| |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <unistd.h> |
| |
| #include <arch/board/board.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/analog/adc.h> |
| #include <nuttx/analog/ioctl.h> |
| |
| #include "arm_internal.h" |
| #include "chip.h" |
| #include "stm32.h" |
| #include "stm32_dma.h" |
| #include "stm32_pwr.h" |
| #include "stm32_sdadc.h" |
| |
| #ifdef CONFIG_STM32_SDADC |
| |
| /* Some SDADC peripheral must be enabled */ |
| |
| #if defined(CONFIG_STM32_SDADC1) || defined(CONFIG_STM32_SDADC2) || \ |
| defined(CONFIG_STM32_SDADC3) |
| |
| /* This implementation is for the STM32F37XX only */ |
| |
| #ifndef CONFIG_STM32_STM32F37XX |
| # error "This chip is not yet supported" |
| #endif |
| |
| /* TODO: At the moment there is no implementation |
| * for timer and external triggers |
| */ |
| |
| #if defined(SDADC_HAVE_TIMER) |
| # error "There is no proper implementation for TIMER TRIGGERS at the moment" |
| #endif |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* RCC reset ****************************************************************/ |
| |
| #define STM32_RCC_RSTR STM32_RCC_APB2RSTR |
| #define RCC_RSTR_SDADC1RST RCC_APB2RSTR_SDADC1RST |
| #define RCC_RSTR_SDADC2RST RCC_APB2RSTR_SDADC2RST |
| #define RCC_RSTR_SDADC3RST RCC_APB2RSTR_SDADC3RST |
| |
| /* SDADC interrupts *********************************************************/ |
| |
| #define SDADC_ISR_ALLINTS (SDADC_ISR_JEOCF | SDADC_ISR_JOVRF) |
| |
| /* SDADC Channels/DMA *******************************************************/ |
| |
| #define SDADC_DMA_CONTROL_WORD (DMA_CCR_MSIZE_16BITS | \ |
| DMA_CCR_PSIZE_16BITS | \ |
| DMA_CCR_MINC | \ |
| DMA_CCR_CIRC) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This structure describes the state of one SDADC block */ |
| |
| struct stm32_dev_s |
| { |
| const struct adc_callback_s *cb; |
| uint8_t irq; /* Interrupt generated by this SDADC block */ |
| uint8_t nchannels; /* Number of channels */ |
| uint8_t cchannels; /* Number of configured channels */ |
| uint8_t intf; /* SDADC interface number */ |
| uint8_t current; /* Current SDADC channel being converted */ |
| uint8_t refv; /* Reference voltage selection */ |
| #ifdef SDADC_HAVE_DMA |
| uint8_t dmachan; /* DMA channel needed by this SDADC */ |
| bool hasdma; /* True: This channel supports DMA */ |
| #endif |
| #ifdef SDADC_HAVE_TIMER |
| uint8_t trigger; /* Timer trigger selection: see SDADCx_JEXTSEL_TIMxx */ |
| #endif |
| uint32_t base; /* Base address of registers unique to this SDADC |
| * block */ |
| #ifdef SDADC_HAVE_TIMER |
| uint32_t tbase; /* Base address of timer used by this SDADC block */ |
| uint32_t jextsel /* JEXTSEL value used by this SDADC block */ |
| uint32_t pclck; /* The PCLK frequency that drives this timer */ |
| uint32_t freq; /* The desired frequency of conversions */ |
| #endif |
| #ifdef SDADC_HAVE_DMA |
| DMA_HANDLE dma; /* Allocated DMA channel */ |
| |
| /* DMA transfer buffer */ |
| |
| int16_t dmabuffer[SDADC_MAX_SAMPLES]; |
| #endif |
| |
| /* List of selected SDADC injected channels to sample */ |
| |
| uint8_t chanlist[SDADC_MAX_SAMPLES]; |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* ADC Register access */ |
| |
| static uint32_t sdadc_getreg(struct stm32_dev_s *priv, int offset); |
| static void sdadc_putreg(struct stm32_dev_s *priv, int offset, |
| uint32_t value); |
| static void sdadc_modifyreg(struct stm32_dev_s *priv, int offset, |
| uint32_t clrbits, uint32_t setbits); |
| #ifdef ADC_HAVE_TIMER |
| static uint16_t tim_getreg(struct stm32_dev_s *priv, int offset); |
| static void tim_putreg(struct stm32_dev_s *priv, int offset, |
| uint16_t value); |
| static void tim_modifyreg(struct stm32_dev_s *priv, int offset, |
| uint16_t clrbits, uint16_t setbits); |
| static void tim_dumpregs(struct stm32_dev_s *priv, |
| const char *msg); |
| #endif |
| |
| static void sdadc_rccreset(struct stm32_dev_s *priv, bool reset); |
| |
| /* ADC Interrupt Handler */ |
| |
| static int sdadc_interrupt(int irq, void *context, void *arg); |
| |
| /* ADC Driver Methods */ |
| |
| static int sdadc_bind(struct adc_dev_s *dev, |
| const struct adc_callback_s *callback); |
| static void sdadc_reset(struct adc_dev_s *dev); |
| static int sdadc_setup(struct adc_dev_s *dev); |
| static void sdadc_shutdown(struct adc_dev_s *dev); |
| static void sdadc_rxint(struct adc_dev_s *dev, bool enable); |
| static int sdadc_ioctl(struct adc_dev_s *dev, int cmd, |
| unsigned long arg); |
| static void sdadc_enable(struct stm32_dev_s *priv, bool enable); |
| |
| static int sdadc_set_ch(struct adc_dev_s *dev, uint8_t ch); |
| |
| #ifdef ADC_HAVE_TIMER |
| static void sdadc_timstart(struct stm32_dev_s *priv, bool enable); |
| static int sdadc_timinit(struct stm32_dev_s *priv); |
| #endif |
| |
| #ifdef ADC_HAVE_DMA |
| static void sdadc_dmaconvcallback(DMA_HANDLE handle, uint8_t isr, |
| void *arg); |
| #endif |
| |
| static void sdadc_startconv(struct stm32_dev_s *priv, bool enable); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* SDADC interface operations */ |
| |
| static const struct adc_ops_s g_sdadcops = |
| { |
| .ao_bind = sdadc_bind, |
| .ao_reset = sdadc_reset, |
| .ao_setup = sdadc_setup, |
| .ao_shutdown = sdadc_shutdown, |
| .ao_rxint = sdadc_rxint, |
| .ao_ioctl = sdadc_ioctl, |
| }; |
| |
| /* SDADC1 state */ |
| |
| #ifdef CONFIG_STM32_SDADC1 |
| static struct stm32_dev_s g_sdadcpriv1 = |
| { |
| .irq = STM32_IRQ_SDADC1, |
| .intf = 1, |
| .base = STM32_SDADC1_BASE, |
| .refv = SDADC1_REFV, |
| #ifdef SDADC1_HAVE_TIMER |
| .trigger = CONFIG_STM32_SDADC1_TIMTRIG, |
| .tbase = SDADC1_TIMER_BASE, |
| .extsel = SDADC1_EXTSEL_VALUE, |
| .pclck = SDADC1_TIMER_PCLK_FREQUENCY, |
| .freq = CONFIG_STM32_SDADC1_SAMPLE_FREQUENCY, |
| #endif |
| #ifdef SDADC1_HAVE_DMA |
| .dmachan = DMACHAN_SDADC1, |
| .hasdma = true, |
| #endif |
| }; |
| |
| static struct adc_dev_s g_sdadcdev1 = |
| { |
| .ad_ops = &g_sdadcops, |
| .ad_priv = &g_sdadcpriv1, |
| }; |
| #endif |
| |
| /* SDADC2 state */ |
| |
| #ifdef CONFIG_STM32_SDADC2 |
| static struct stm32_dev_s g_sdadcpriv2 = |
| { |
| .irq = STM32_IRQ_SDADC2, |
| .base = STM32_SDADC2_BASE, |
| .refv = SDADC2_REFV, |
| #ifdef SDADC2_HAVE_TIMER |
| .trigger = CONFIG_STM32_SDADC2_TIMTRIG, |
| .tbase = SDADC2_TIMER_BASE, |
| .extsel = SDADC2_EXTSEL_VALUE, |
| .pclck = SDADC2_TIMER_PCLK_FREQUENCY, |
| .freq = CONFIG_STM32_SDADC2_SAMPLE_FREQUENCY, |
| #endif |
| #ifdef SDADC2_HAVE_DMA |
| .dmachan = DMACHAN_SDADC2, |
| .hasdma = true, |
| #endif |
| }; |
| |
| static struct adc_dev_s g_sdadcdev2 = |
| { |
| .ad_ops = &g_sdadcops, |
| .ad_priv = &g_sdadcpriv2, |
| }; |
| #endif |
| |
| /* SDADC3 state */ |
| |
| #ifdef CONFIG_STM32_SDADC3 |
| static struct stm32_dev_s g_sdadcpriv3 = |
| { |
| .irq = STM32_IRQ_SDADC3, |
| .base = STM32_SDADC3_BASE, |
| .refv = SDADC3_REFV, |
| #ifdef SDADC3_HAVE_TIMER |
| .trigger = CONFIG_STM32_SDADC3_TIMTRIG, |
| .tbase = SDADC3_TIMER_BASE, |
| .extsel = SDADC3_EXTSEL_VALUE, |
| .pclck = SDADC3_TIMER_PCLK_FREQUENCY, |
| .freq = CONFIG_STM32_SDADC3_SAMPLE_FREQUENCY, |
| #endif |
| #ifdef SDADC3_HAVE_DMA |
| .dmachan = DMACHAN_SDADC3, |
| .hasdma = true, |
| #endif |
| }; |
| |
| static struct adc_dev_s g_sdadcdev3 = |
| { |
| .ad_ops = &g_sdadcops, |
| .ad_priv = &g_sdadcpriv3, |
| }; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sdadc_getreg |
| * |
| * Description: |
| * Read the value of an SDADC register. |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * offset - The offset to the register to read |
| * |
| * Returned Value: |
| * The current contents of the specified register |
| * |
| ****************************************************************************/ |
| |
| static uint32_t sdadc_getreg(struct stm32_dev_s *priv, int offset) |
| { |
| return getreg32(priv->base + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_putreg |
| * |
| * Description: |
| * Write a value to an SDADC register. |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * offset - The offset to the register to write to |
| * value - The value to write to the register |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_putreg(struct stm32_dev_s *priv, int offset, |
| uint32_t value) |
| { |
| putreg32(value, priv->base + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_modifyreg |
| * |
| * Description: |
| * Modify the value of an SDADC register (not atomic). |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * offset - The offset to the register to modify |
| * clrbits - The bits to clear |
| * setbits - The bits to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_modifyreg(struct stm32_dev_s *priv, int offset, |
| uint32_t clrbits, uint32_t setbits) |
| { |
| sdadc_putreg(priv, offset, |
| (sdadc_getreg(priv, offset) & ~clrbits) | setbits); |
| } |
| |
| /**************************************************************************** |
| * Name: tim_getreg |
| * |
| * Description: |
| * Read the value of an SDADC timer register. |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * offset - The offset to the register to read |
| * |
| * Returned Value: |
| * The current contents of the specified register |
| * |
| ****************************************************************************/ |
| |
| #ifdef SDADC_HAVE_TIMER |
| static uint16_t tim_getreg(struct stm32_dev_s *priv, int offset) |
| { |
| return getreg16(priv->tbase + offset); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: tim_putreg |
| * |
| * Description: |
| * Write a value to an SDADC timer register. |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * offset - The offset to the register to write to |
| * value - The value to write to the register |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef SDADC_HAVE_TIMER |
| static void tim_putreg(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 SDADC timer register (not atomic). |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * offset - The offset to the register to modify |
| * clrbits - The bits to clear |
| * setbits - The bits to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef SDADC_HAVE_TIMER |
| static void tim_modifyreg(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 SDADC block state |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef SDADC_HAVE_TIMER |
| static void tim_dumpregs(struct stm32_dev_s *priv, const char *msg) |
| { |
| ainfo("%s:\n", msg); |
| |
| /* TODO */ |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sdadc_timstart |
| * |
| * Description: |
| * Start (or stop) the timer counter |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * enable - True: Start conversion |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| #ifdef SDADC_HAVE_TIMER |
| static void sdadc_timstart(struct stm32_dev_s *priv, bool enable) |
| { |
| ainfo("enable: %d\n", enable ? 1 : 0); |
| |
| if (enable) |
| { |
| /* Start the counter */ |
| |
| tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); |
| } |
| else |
| { |
| /* Disable the counter */ |
| |
| tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sdadc_timinit |
| * |
| * Description: |
| * Initialize the timer that drivers the SDADC sampling for this channel |
| * using the pre-calculated timer divider definitions. |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * |
| * Returned Value: |
| * Zero on success; a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef SDADC_HAVE_TIMER |
| static int sdadc_timinit(struct stm32_dev_s *priv) |
| { |
| /* TODO */ |
| |
| aerr("ERROR: not implemented"); |
| return ERROR; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sdadc_startconv |
| * |
| * Description: |
| * Start (or stop) the SDADC conversion process |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * enable - True: Start conversion |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_startconv(struct stm32_dev_s *priv, bool enable) |
| { |
| ainfo("enable: %d\n", enable ? 1 : 0); |
| |
| if (enable) |
| { |
| /* Start the conversion of injected channels */ |
| |
| sdadc_modifyreg(priv, STM32_SDADC_CR2_OFFSET, 0, SDADC_CR2_JSWSTART); |
| } |
| else |
| { |
| /* Wait for a possible conversion to stop */ |
| |
| while ((sdadc_getreg(priv, |
| STM32_SDADC_ISR_OFFSET) & SDADC_ISR_JCIP) != 0); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_rccreset |
| * |
| * Description: |
| * (De)Initializes the SDADC block registers to their default |
| * reset values. |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * reset - true: to put in reset state, false: to revert to normal state |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_rccreset(struct stm32_dev_s *priv, bool reset) |
| { |
| uint32_t adcbit; |
| |
| /* Pick the appropriate bit in the APB2 reset register. |
| */ |
| |
| switch (priv->intf) |
| { |
| #ifdef CONFIG_STM32_SDADC1 |
| case 1: |
| adcbit = RCC_RSTR_SDADC1RST; |
| break; |
| #endif |
| #ifdef CONFIG_STM32_SDADC2 |
| case 2: |
| adcbit = RCC_RSTR_SDADC2RST; |
| break; |
| #endif |
| #ifdef CONFIG_STM32_SDADC3 |
| case 3: |
| adcbit = RCC_RSTR_SDADC3RST; |
| break; |
| #endif |
| default: |
| return; |
| } |
| |
| /* Set or clear the selected bit in the APB2 reset register. |
| * modifyreg32() disables interrupts. Disabling interrupts is necessary |
| * because the APB2RSTR register is used by several different drivers. |
| */ |
| |
| if (reset) |
| { |
| /* Enable SDADC reset state */ |
| |
| modifyreg32(STM32_RCC_RSTR, 0, adcbit); |
| } |
| else |
| { |
| /* Release SDADC from reset state */ |
| |
| modifyreg32(STM32_RCC_RSTR, adcbit, 0); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_power_down_idle |
| * |
| * Description: |
| * Enables or disables power down during the idle phase. |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * pdi_high - true: The SDADC is powered down when waiting for a start |
| * event |
| * false: The SDADC is powered up when waiting for a start event |
| * |
| * Returned Value: |
| * None. |
| * |
| ****************************************************************************/ |
| |
| #if 0 |
| static void sdadc_power_down_idle(struct stm32_dev_s *priv, |
| bool pdi_high) |
| { |
| uint32_t regval; |
| |
| ainfo("PDI: %d\n", pdi_high ? 1 : 0); |
| |
| regval = sdadc_getreg(priv, STM32_SDADC_CR2_OFFSET); |
| |
| if ((regval & SDADC_CR2_ADON) == 0) |
| { |
| regval = sdadc_getreg(priv, STM32_SDADC_CR1_OFFSET); |
| if (pdi_high) |
| { |
| regval |= SDADC_CR1_PDI; |
| } |
| else |
| { |
| regval &= ~SDADC_CR1_PDI; |
| } |
| |
| sdadc_putreg(priv, STM32_SDADC_CR1_OFFSET, regval); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sdadc_enable |
| * |
| * Description: |
| * Enables or disables the specified SDADC peripheral. |
| * Does not start conversion unless the SDADC is |
| * triggered by timer |
| * |
| * Input Parameters: |
| * priv - A reference to the SDADC block state |
| * enable - true: enable SDADC conversion |
| * false: disable SDADC conversion |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_enable(struct stm32_dev_s *priv, bool enable) |
| { |
| uint32_t regval; |
| |
| ainfo("enable: %d\n", enable ? 1 : 0); |
| |
| regval = sdadc_getreg(priv, STM32_SDADC_CR2_OFFSET); |
| |
| if (enable) |
| { |
| /* Enable the SDADC */ |
| |
| sdadc_putreg(priv, STM32_SDADC_CR2_OFFSET, regval | SDADC_CR2_ADON); |
| |
| /* Wait for the SDADC to be stabilized */ |
| |
| while (sdadc_getreg(priv, STM32_SDADC_ISR_OFFSET) & SDADC_ISR_STABIP); |
| } |
| else if ((regval & SDADC_CR2_ADON) != 0) |
| { |
| /* Ongoing conversions will be stopped implicitly */ |
| |
| /* Disable the SDADC */ |
| |
| sdadc_putreg(priv, STM32_SDADC_CR2_OFFSET, regval & ~SDADC_CR2_ADON); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_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 - SDADC device |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| #ifdef SDADC_HAVE_DMA |
| static void sdadc_dmaconvcallback(DMA_HANDLE handle, |
| uint8_t isr, void *arg) |
| { |
| struct adc_dev_s *dev = (struct adc_dev_s *)arg; |
| struct stm32_dev_s *priv = (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; |
| } |
| } |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sdadc_bind |
| * |
| * Description: |
| * Bind the upper-half driver callbacks to the lower-half implementation. |
| * This must be called early in order to receive SDADC event notifications. |
| * |
| ****************************************************************************/ |
| |
| static int sdadc_bind(struct adc_dev_s *dev, |
| const struct adc_callback_s *callback) |
| { |
| struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv; |
| |
| DEBUGASSERT(priv != NULL); |
| priv->cb = callback; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_reset |
| * |
| * Description: |
| * Reset the SDADC device. |
| * This is firstly called whenever the SDADC device is registered by |
| * sdadc_register() |
| * Does mostly the SDAC register setting. |
| * Leave the device in power down mode. |
| * Note that SDACx clock is already enable (for all SDADC) by the |
| * rcc_enableapb2() |
| * |
| * Input Parameters: |
| * dev - pointer to the sdadc device structure |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_reset(struct adc_dev_s *dev) |
| { |
| struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv; |
| irqstate_t flags; |
| uint32_t setbits = 0; |
| |
| ainfo("intf: %d\n", priv->intf); |
| |
| /* TODO: why critical ? */ |
| |
| flags = enter_critical_section(); |
| |
| /* Enable SDADC reset state */ |
| |
| sdadc_rccreset(priv, true); |
| |
| /* Enable power */ |
| |
| stm32_pwr_enablesdadc(priv->intf); |
| |
| /* Release SDADC from reset state */ |
| |
| sdadc_rccreset(priv, false); |
| |
| /* Enable the SDADC (and wait until it stabilizes) */ |
| |
| sdadc_enable(priv, true); |
| |
| /* Put SDADC in in initialization mode */ |
| |
| sdadc_putreg(priv, STM32_SDADC_CR1_OFFSET, SDADC_CR1_INIT); |
| |
| /* Wait for the SDADC to be ready */ |
| |
| while ((sdadc_getreg(priv, |
| STM32_SDADC_ISR_OFFSET) & |
| SDADC_ISR_INITRDY) == 0); |
| |
| /* Load configurations */ |
| |
| sdadc_putreg(priv, STM32_SDADC_CONF0R_OFFSET, SDADC_CONF0R_DEFAULT); |
| sdadc_putreg(priv, STM32_SDADC_CONF1R_OFFSET, SDADC_CONF1R_DEFAULT); |
| sdadc_putreg(priv, STM32_SDADC_CONF2R_OFFSET, SDADC_CONF2R_DEFAULT); |
| |
| sdadc_putreg(priv, STM32_SDADC_CONFCHR1_OFFSET, SDADC_CONFCHR1_DEFAULT); |
| sdadc_putreg(priv, STM32_SDADC_CONFCHR2_OFFSET, SDADC_CONFCHR2_DEFAULT); |
| |
| /* Configuration of the injected channels group */ |
| |
| sdadc_set_ch(dev, 0); |
| |
| /* CR1 ********************************************************************/ |
| |
| /* Enable interrupt / dma flags, is done later by upper half when opening |
| * device by calling sdadc_rxint() |
| */ |
| |
| setbits = SDADC_CR1_INIT; /* remains in init mode while configuring */ |
| |
| /* Reference voltage */ |
| |
| setbits |= priv->refv; |
| |
| /* Set CR1 configuration */ |
| |
| sdadc_putreg(priv, STM32_SDADC_CR1_OFFSET, setbits); |
| |
| /* CR2 ********************************************************************/ |
| |
| setbits = SDADC_CR2_ADON; /* leave it ON ! */ |
| |
| /* TODO: JEXTEN / JEXTSEL */ |
| |
| /* Number of calibrations is for 3 configurations */ |
| |
| setbits |= (2 << SDADC_CR2_CALIBCNT_SHIFT); |
| |
| /* Set CR2 configuration */ |
| |
| sdadc_putreg(priv, STM32_SDADC_CR2_OFFSET, setbits); |
| |
| /* Release INIT mode ******************************************************/ |
| |
| sdadc_modifyreg(priv, STM32_SDADC_CR1_OFFSET, SDADC_CR1_INIT, 0); |
| |
| /* Calibrate the SDADC */ |
| |
| sdadc_modifyreg(priv, STM32_SDADC_CR2_OFFSET, 0, SDADC_CR2_STARTCALIB); |
| |
| /* Wait for the calibration to complete (may take up to 5ms) */ |
| |
| while ((sdadc_getreg(priv, |
| STM32_SDADC_ISR_OFFSET) & SDADC_ISR_EOCALF) == 0); |
| |
| /* Clear this flag */ |
| |
| sdadc_modifyreg(priv, |
| STM32_SDADC_CLRISR_OFFSET, SDADC_CLRISR_CLREOCALF, 0); |
| |
| #ifdef SDADC_HAVE_TIMER |
| if (priv->tbase != 0) |
| { |
| ret = sdadc_timinit(priv); |
| if (ret < 0) |
| { |
| aerr("ERROR: sdadc_timinit failed: %d\n", ret); |
| } |
| } |
| #endif |
| |
| /* Put the device in low power mode until it is actually used by |
| * application code. |
| */ |
| |
| sdadc_enable(priv, false); |
| |
| leave_critical_section(flags); |
| |
| ainfo("CR1: 0x%08x CR2: 0x%08x\n", |
| sdadc_getreg(priv, STM32_SDADC_CR1_OFFSET), |
| sdadc_getreg(priv, STM32_SDADC_CR2_OFFSET)); |
| |
| ainfo("CONF0R: 0x%08x CONF1R: 0x%08x CONF3R: 0x%08x\n", |
| sdadc_getreg(priv, STM32_SDADC_CONF0R_OFFSET), |
| sdadc_getreg(priv, STM32_SDADC_CONF1R_OFFSET), |
| sdadc_getreg(priv, STM32_SDADC_CONF2R_OFFSET)); |
| |
| ainfo("CONFCHR1: 0x%08x CONFCHR2: 0x%08x JCHGR: 0x%08x\n", |
| sdadc_getreg(priv, STM32_SDADC_CONFCHR1_OFFSET), |
| sdadc_getreg(priv, STM32_SDADC_CONFCHR2_OFFSET), |
| sdadc_getreg(priv, STM32_SDADC_JCHGR_OFFSET)); |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_setup |
| * |
| * Description: |
| * Configure the ADC. This method is called the first time that the SDADC |
| * device is opened. |
| * This is called by the upper half driver sdadc_open(). |
| * This will occur when the port is first |
| * opened in the application code (/dev/sdadcN). |
| * It would be called again after closing all references to this file and |
| * reopening it. |
| * This function wakes up the device and setup the DMA / IRQ |
| * |
| * Input Parameters: |
| * dev - pointer to the sdadc device structure |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static int sdadc_setup(struct adc_dev_s *dev) |
| { |
| struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv; |
| int ret; |
| |
| /* Wakes up the device */ |
| |
| sdadc_enable(priv, true); |
| |
| /* Setup DMA or interrupt control. Note that either DMA or interrupt is |
| * setup not both. |
| */ |
| |
| #ifdef SDADC_HAVE_DMA |
| if (priv->hasdma) |
| { |
| /* Setup DMA */ |
| |
| /* Stop and free DMA if it was started before */ |
| |
| if (priv->dma != NULL) |
| { |
| stm32_dmastop(priv->dma); |
| stm32_dmafree(priv->dma); |
| } |
| |
| priv->dma = stm32_dmachannel(priv->dmachan); |
| |
| stm32_dmasetup(priv->dma, |
| priv->base + STM32_SDADC_JDATAR_OFFSET, |
| (uint32_t)priv->dmabuffer, |
| priv->nchannels, |
| SDADC_DMA_CONTROL_WORD); |
| |
| stm32_dmastart(priv->dma, sdadc_dmaconvcallback, dev, false); |
| } |
| else |
| { |
| /* Attach the SDADC interrupt */ |
| |
| ret = irq_attach(priv->irq, sdadc_interrupt, dev); |
| if (ret < 0) |
| { |
| ainfo("irq_attach failed: %d\n", ret); |
| return ret; |
| } |
| } |
| #else |
| /* Attach the SDADC interrupt */ |
| |
| ret = irq_attach(priv->irq, sdadc_interrupt, dev); |
| if (ret < 0) |
| { |
| ainfo("irq_attach failed: %d\n", ret); |
| return ret; |
| } |
| #endif |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_shutdown |
| * |
| * Description: |
| * Disable the ADC. This method is called when the last instance |
| * of the SDADC device is closed by the user application. |
| * This method reverses the operation the setup method. |
| * |
| * Input Parameters: |
| * dev - pointer to the sdadc device structure |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_shutdown(struct adc_dev_s *dev) |
| { |
| struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv; |
| |
| /* Put the device in low power mode */ |
| |
| sdadc_enable(priv, false); |
| |
| /* Disable interrupt / dma */ |
| |
| sdadc_rxint(dev, false); |
| |
| #ifdef SDADC_HAVE_DMA |
| if (priv->hasdma) |
| { |
| /* Stop and free DMA if it was started before */ |
| |
| if (priv->dma != NULL) |
| { |
| stm32_dmastop(priv->dma); |
| stm32_dmafree(priv->dma); |
| } |
| } |
| else |
| { |
| /* Disable ADC interrupts and detach the SDADC interrupt handler */ |
| |
| up_disable_irq(priv->irq); |
| irq_detach(priv->irq); |
| } |
| #else |
| /* Disable ADC interrupts and detach the SDADC interrupt handler */ |
| |
| up_disable_irq(priv->irq); |
| irq_detach(priv->irq); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_rxint |
| * |
| * Description: |
| * Call to enable or disable RX interrupts. |
| * |
| * Input Parameters: |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static void sdadc_rxint(struct adc_dev_s *dev, bool enable) |
| { |
| struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv; |
| uint32_t setbits; |
| |
| ainfo("intf: %d enable: %d\n", priv->intf, enable ? 1 : 0); |
| |
| /* DMA mode */ |
| |
| #ifdef SDADC_HAVE_DMA |
| if (priv->hasdma) |
| { |
| setbits = SDADC_CR1_JDMAEN; /* DMA enabled for injected channels group */ |
| } |
| else |
| { |
| /* Interrupt enable for injected channel group overrun |
| * and end of conversion |
| */ |
| |
| setbits = SDADC_CR1_JOVRIE | SDADC_CR1_JEOCIE; |
| } |
| #else |
| setbits = SDADC_CR1_JOVRIE | SDADC_CR1_JEOCIE; |
| #endif |
| |
| if (enable) |
| { |
| /* Enable */ |
| |
| sdadc_modifyreg(priv, STM32_SDADC_CR1_OFFSET, 0, setbits); |
| } |
| else |
| { |
| /* Disable all ADC interrupts and DMA */ |
| |
| sdadc_modifyreg(priv, STM32_SDADC_CR1_OFFSET, |
| SDADC_CR1_JOVRIE | SDADC_CR1_JEOCIE | SDADC_CR1_JDMAEN, |
| 0); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_set_ch |
| * |
| * Description: |
| * Sets the SDADC injected channel group. |
| * |
| * 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 sdadc_set_ch(struct adc_dev_s *dev, uint8_t ch) |
| { |
| struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv; |
| uint32_t bits = 0; |
| 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; |
| } |
| |
| for (i = 0; i < priv->nchannels; i++) |
| { |
| bits |= (uint32_t)(1 << priv->chanlist[i]); |
| } |
| |
| sdadc_putreg(priv, STM32_SDADC_JCHGR_OFFSET, bits); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_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 sdadc_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg) |
| { |
| struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv; |
| int ret = OK; |
| |
| switch (cmd) |
| { |
| case ANIOC_TRIGGER: |
| { |
| sdadc_startconv(priv, true); |
| } |
| break; |
| |
| case ANIOC_GET_NCHANNELS: |
| { |
| /* Return the number of configured channels */ |
| |
| ret = priv->cchannels; |
| } |
| break; |
| |
| default: |
| { |
| aerr("ERROR: Unknown cmd: %d\n", cmd); |
| ret = -ENOTTY; |
| } |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: sdadc_interrupt |
| * |
| * Description: |
| * Common SDADC interrupt handler. |
| * |
| * Input Parameters: |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| static int sdadc_interrupt(int irq, void *context, void *arg) |
| { |
| struct adc_dev_s *dev = (struct adc_dev_s *)arg; |
| struct stm32_dev_s *priv; |
| uint32_t regval; |
| uint32_t pending; |
| int32_t data; |
| uint8_t chan; |
| |
| DEBUGASSERT(dev != NULL && dev->ad_priv != NULL); |
| priv = (struct stm32_dev_s *)dev->ad_priv; |
| |
| regval = sdadc_getreg(priv, STM32_SDADC_ISR_OFFSET); |
| pending = regval & SDADC_ISR_ALLINTS; |
| if (pending == 0) |
| { |
| return OK; |
| } |
| |
| /* JOVRF: overrun flag */ |
| |
| if ((regval & SDADC_ISR_JOVRF) != 0) |
| { |
| awarn("WARNING: Overrun has occurred!\n"); |
| } |
| |
| /* JEOCF: End of conversion */ |
| |
| if ((regval & SDADC_ISR_JEOCF) != 0) |
| { |
| /* Read the converted value and clear JEOCF bit |
| * (It is cleared by reading the SDADC_JDATAR) |
| */ |
| |
| data = sdadc_getreg(priv, |
| STM32_SDADC_JDATAR_OFFSET) & |
| SDADC_JDATAR_JDATA_MASK; |
| chan = sdadc_getreg(priv, |
| STM32_SDADC_JDATAR_OFFSET) & |
| SDADC_JDATAR_JDATACH_MASK; |
| |
| DEBUGASSERT(priv->chanlist[priv->current] == chan); |
| |
| /* Verify that the upper-half driver has bound its callback functions */ |
| |
| if (priv->cb != NULL) |
| { |
| /* Give the SDADC data to the ADC driver. The ADC receive() method |
| * accepts 3 parameters: |
| * |
| * 1) The first is the ADC device instance for this SDADC 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, chan, data); |
| } |
| |
| /* 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; |
| } |
| |
| /* do no clear this interrupt (cleared by reading data) */ |
| |
| pending &= ~SDADC_ISR_JEOCF; |
| } |
| |
| /* Clears interrupt flags, if any */ |
| |
| if (pending) |
| { |
| sdadc_putreg(priv, STM32_SDADC_CLRISR_OFFSET, pending); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: stm32_sdadcinitialize |
| * |
| * Description: |
| * Initialize one SDADC block |
| * |
| * The logic is, save and initialize the channel list in the private driver |
| * structure and return the corresponding adc device structure. |
| * |
| * Each SDADC will convert the channels indicated each |
| * time a conversion is triggered either by software, timer or external |
| * event. Channels are numbered from 0 - 8 and must be given in order |
| * (contrarily to what says ST RM0313 doc !!!). |
| * |
| * Input Parameters: |
| * intf - Could be {1,2,3} for SDADC1, SDADC2, or SDADC3 |
| * chanlist - The list of channels eg. { 0, 3, 7, 8 } |
| * cchannels - Number of channels |
| * |
| * Returned Value: |
| * Valid ADC device structure reference on success; a NULL on failure |
| * |
| ****************************************************************************/ |
| |
| struct adc_dev_s *stm32_sdadcinitialize(int intf, |
| const uint8_t *chanlist, |
| int cchannels) |
| { |
| struct adc_dev_s *dev; |
| struct stm32_dev_s *priv; |
| int i; |
| |
| ainfo("intf: %d cchannels: %d\n", intf, cchannels); |
| |
| switch (intf) |
| { |
| #ifdef CONFIG_STM32_SDADC1 |
| case 1: |
| ainfo("SDADC1 selected\n"); |
| dev = &g_sdadcdev1; |
| break; |
| #endif |
| #ifdef CONFIG_STM32_SDADC2 |
| case 2: |
| ainfo("SDADC2 selected\n"); |
| dev = &g_sdadcdev2; |
| break; |
| #endif |
| #ifdef CONFIG_STM32_SDADC3 |
| case 3: |
| ainfo("SDADC3 selected\n"); |
| dev = &g_sdadcdev3; |
| break; |
| #endif |
| default: |
| aerr("ERROR: No SDADC interface defined\n"); |
| return NULL; |
| } |
| |
| /* Check channel list in order */ |
| |
| DEBUGASSERT((cchannels <= SDADC_MAX_SAMPLES) && (cchannels > 0)); |
| for (i = 0; i < cchannels - 1; i++) |
| { |
| if (chanlist[i] >= chanlist[i + 1]) |
| { |
| aerr("ERROR: SDADC channel list must be given in order\n"); |
| return NULL; |
| } |
| } |
| |
| /* Configure the selected SDADC */ |
| |
| priv = (struct stm32_dev_s *)dev->ad_priv; |
| |
| priv->cb = NULL; |
| priv->cchannels = cchannels; |
| |
| memcpy(priv->chanlist, chanlist, cchannels); |
| |
| return dev; |
| } |
| |
| #endif /* CONFIG_STM32_SDADC1 || CONFIG_STM32_SDADC2 || |
| * CONFIG_STM32_SDADC3 */ |
| #endif /* CONFIG_STM32_SDADC */ |