/************************************************************************************ * arch/arm/src/sama5/sam_adc.c * * Copyright (C) 2013, 2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * References: * * SAMA5D3 Series Data Sheet * Atmel NoOS sample code. * * The Atmel sample code has a BSD compatible license that requires this * copyright notice: * * Copyright (c) 2012, Atmel Corporation * * 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 names NuttX nor Atmel nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ************************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "up_internal.h" #include "up_arch.h" #include "chip.h" #include "cache.h" #include "chip/sam_adc.h" #include "chip/sam_pmc.h" #include "chip/sam_pinmap.h" #include "sam_periphclks.h" #include "sam_memories.h" #include "sam_pio.h" #include "sam_dmac.h" #include "sam_tc.h" #include "sam_tsd.h" #include "sam_adc.h" #if defined(CONFIG_SAMA5_ADC) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Count the number of channels in use */ #define SAMA5_CHAN0_INUSE 0 #define SAMA5_CHAN1_INUSE 0 #define SAMA5_CHAN2_INUSE 0 #define SAMA5_CHAN3_INUSE 0 #define SAMA5_CHAN4_INUSE 0 #define SAMA5_CHAN5_INUSE 0 #define SAMA5_CHAN6_INUSE 0 #define SAMA5_CHAN7_INUSE 0 #define SAMA5_CHAN8_INUSE 0 #define SAMA5_CHAN9_INUSE 0 #define SAMA5_CHAN10_INUSE 0 #define SAMA5_CHAN11_INUSE 0 #ifdef CONFIG_SAMA5_ADC_CHAN0 # undef SAMA5_CHAN0_INUSE # define SAMA5_CHAN0_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN1 # undef SAMA5_CHAN1_INUSE # define SAMA5_CHAN1_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN2 # undef SAMA5_CHAN2_INUSE # define SAMA5_CHAN2_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN3 # undef SAMA5_CHAN3_INUSE # define SAMA5_CHAN3_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN4 # undef SAMA5_CHAN4_INUSE # define SAMA5_CHAN4_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN5 # undef SAMA5_CHAN5_INUSE # define SAMA5_CHAN5_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN6 # undef SAMA5_CHAN6_INUSE # define SAMA5_CHAN6_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN7 # undef SAMA5_CHAN7_INUSE # define SAMA5_CHAN7_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN8 # undef SAMA5_CHAN8_INUSE # define SAMA5_CHAN8_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN9 # undef SAMA5_CHAN9_INUSE # define SAMA5_CHAN9_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN10 # undef SAMA5_CHAN10_INUSE # define SAMA5_CHAN10_INUSE 1 #endif #ifdef CONFIG_SAMA5_ADC_CHAN11 # undef SAMA5_CHAN11_INUSE # define SAMA5_CHAN11_INUSE 1 #endif #define SAMA5_NCHANNELS \ (SAMA5_CHAN0_INUSE + SAMA5_CHAN1_INUSE + SAMA5_CHAN2_INUSE + \ SAMA5_CHAN3_INUSE + SAMA5_CHAN4_INUSE + SAMA5_CHAN5_INUSE + \ SAMA5_CHAN6_INUSE + SAMA5_CHAN7_INUSE + SAMA5_CHAN8_INUSE + \ SAMA5_CHAN9_INUSE + SAMA5_CHAN10_INUSE + SAMA5_CHAN11_INUSE) /* Get the set of channel interrupts to enable */ #define SAMA5_CHAN0_ENABLE 0 #define SAMA5_CHAN1_ENABLE 0 #define SAMA5_CHAN2_ENABLE 0 #define SAMA5_CHAN3_ENABLE 0 #define SAMA5_CHAN4_ENABLE 0 #define SAMA5_CHAN5_ENABLE 0 #define SAMA5_CHAN6_ENABLE 0 #define SAMA5_CHAN7_ENABLE 0 #define SAMA5_CHAN8_ENABLE 0 #define SAMA5_CHAN9_ENABLE 0 #define SAMA5_CHAN10_ENABLE 0 #define SAMA5_CHAN11_ENABLE 0 #if defined(CONFIG_SAMA5_ADC_CHAN0) # undef SAMA5_CHAN0_ENABLE # define SAMA5_CHAN0_ENABLE ADC_INT_EOC0 #endif #if defined(CONFIG_SAMA5_ADC_CHAN1) # undef SAMA5_CHAN1_ENABLE # define SAMA5_CHAN1_ENABLE ADC_INT_EOC1 #endif #if defined(CONFIG_SAMA5_ADC_CHAN2) # undef SAMA5_CHAN2_ENABLE # define SAMA5_CHAN2_ENABLE ADC_INT_EOC2 #endif #if defined(CONFIG_SAMA5_ADC_CHAN3) # undef SAMA5_CHAN3_ENABLE # define SAMA5_CHAN3_ENABLE ADC_INT_EOC3 #endif #if defined(CONFIG_SAMA5_ADC_CHAN4) # undef SAMA5_CHAN4_ENABLE # define SAMA5_CHAN4_ENABLE ADC_INT_EOC4 #endif #if defined(CONFIG_SAMA5_ADC_CHAN5) # undef SAMA5_CHAN5_ENABLE # define SAMA5_CHAN5_ENABLE ADC_INT_EOC5 #endif #if defined(CONFIG_SAMA5_ADC_CHAN6) # undef SAMA5_CHAN6_ENABLE # define SAMA5_CHAN6_ENABLE ADC_INT_EOC6 #endif #if defined(CONFIG_SAMA5_ADC_CHAN7) # undef SAMA5_CHAN7_ENABLE # define SAMA5_CHAN7_ENABLE ADC_INT_EOC7 #endif #if defined(CONFIG_SAMA5_ADC_CHAN8) # undef SAMA5_CHAN8_ENABLE # define SAMA5_CHAN8_ENABLE ADC_INT_EOC8 #endif #if defined(CONFIG_SAMA5_ADC_CHAN9) # undef SAMA5_CHAN9_ENABLE # define SAMA5_CHAN9_ENABLE ADC_INT_EOC9 #endif #if defined(CONFIG_SAMA5_ADC_CHAN10) # undef SAMA5_CHAN10_ENABLE # define SAMA5_CHAN10_ENABLE ADC_INT_EOC10 #endif #if defined(CONFIG_SAMA5_ADC_CHAN11) # undef SAMA5_CHAN11_ENABLE # define SAMA5_CHAN11_ENABLE ADC_INT_EOC11 #endif #define SAMA5_CHAN_ENABLE \ (SAMA5_CHAN0_ENABLE | SAMA5_CHAN1_ENABLE | SAMA5_CHAN2_ENABLE | \ SAMA5_CHAN3_ENABLE | SAMA5_CHAN4_ENABLE | SAMA5_CHAN5_ENABLE | \ SAMA5_CHAN6_ENABLE | SAMA5_CHAN7_ENABLE | SAMA5_CHAN8_ENABLE | \ SAMA5_CHAN9_ENABLE | SAMA5_CHAN10_ENABLE | SAMA5_CHAN11_ENABLE) /* If we are supporting the analog chang feature, then sure that there * is a gain setting for each enabled channel. * * Valid gain settings are {0, 1, 2, 3} which may be interpreted as * either {1, 1, 2, 4} if the DIFFx bit in COR register is zero or as * {0.5, 1, 2, 2} if the DIFFx bit is set. */ #ifdef CONFIG_SAMA5_ADC_ANARCH # undef CONFIG_SAMA5_ADC_GAIN # if defined(CONFIG_SAMA5_ADC_CHAN0) && !defined(CONFIG_SAMA5_ADC_GAIN0) # define CONFIG_SAMA5_ADC_GAIN0 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN1) && !defined(CONFIG_SAMA5_ADC_GAIN1) # define CONFIG_SAMA5_ADC_GAIN1 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN2) && !defined(CONFIG_SAMA5_ADC_GAIN2) # define CONFIG_SAMA5_ADC_GAIN2 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN3) && !defined(CONFIG_SAMA5_ADC_GAIN3) # define CONFIG_SAMA5_ADC_GAIN3 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN4) && !defined(CONFIG_SAMA5_ADC_GAIN4) # define CONFIG_SAMA5_ADC_GAIN4 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN5) && !defined(CONFIG_SAMA5_ADC_GAIN5) # define CONFIG_SAMA5_ADC_GAIN5 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN6) && !defined(CONFIG_SAMA5_ADC_GAIN6) # define CONFIG_SAMA5_ADC_GAIN6 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN7) && !defined(CONFIG_SAMA5_ADC_GAIN7) # define CONFIG_SAMA5_ADC_GAIN7 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN8) && !defined(CONFIG_SAMA5_ADC_GAIN8) # define CONFIG_SAMA5_ADC_GAIN8 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN9) && !defined(CONFIG_SAMA5_ADC_GAIN9) # define CONFIG_SAMA5_ADC_GAIN9 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN10) && !defined(CONFIG_SAMA5_ADC_GAIN10) # define CONFIG_SAMA5_ADC_GAIN10 1 # endif # if defined(CONFIG_SAMA5_ADC_CHAN11) && !defined(CONFIG_SAMA5_ADC_GAIN11) # define CONFIG_SAMA5_ADC_GAIN11 1 # endif /* Otherwise, make sure the single global gain value is defined */ #else # ifndef CONFIG_SAMA5_ADC_GAIN # define CONFIG_SAMA5_ADC_GAIN 1 # endif # undef CONFIG_SAMA5_ADC_GAIN0 # undef CONFIG_SAMA5_ADC_GAIN1 # undef CONFIG_SAMA5_ADC_GAIN2 # undef CONFIG_SAMA5_ADC_GAIN3 # undef CONFIG_SAMA5_ADC_GAIN4 # undef CONFIG_SAMA5_ADC_GAIN5 # undef CONFIG_SAMA5_ADC_GAIN6 # undef CONFIG_SAMA5_ADC_GAIN7 # undef CONFIG_SAMA5_ADC_GAIN8 # undef CONFIG_SAMA5_ADC_GAIN9 # undef CONFIG_SAMA5_ADC_GAIN10 # undef CONFIG_SAMA5_ADC_GAIN11 #endif /* Check timer configuration */ #if defined(CONFIG_SAMA5_ADC_TIOATRIG) && !defined(CONFIG_SAMA5_TC0) # error CONFIG_SAMA5_ADC_TIOATRIG requires CONFIG_SAMA5_TC0 #endif /* Determine the set channels that are available. Not all channels will be * available if the touch screen is enabled */ #ifdef CONFIG_SAMA5_TSD # ifdef CONFIG_SAMA5_TSD_5WIRE # define SAMA5_ADC_CHALL (ADC_CHALL & ~TSD_5WIRE_ALL) # else # define SAMA5_ADC_CHALL (ADC_CHALL & ~TSD_4WIRE_ALL) # endif #else # define SAMA5_ADC_CHALL ADC_CHALL #endif /* DMA configuration flags */ #ifdef CONFIG_SAMA5_ADC_DMA # define DMA_FLAGS \ DMACH_FLAG_FIFOCFG_LARGEST | \ ((SAM_IRQ_ADC << DMACH_FLAG_PERIPHPID_SHIFT) | DMACH_FLAG_PERIPHAHB_AHB_IF2 | \ DMACH_FLAG_PERIPHH2SEL | DMACH_FLAG_PERIPHISPERIPH | \ DMACH_FLAG_PERIPHWIDTH_16BITS | DMACH_FLAG_PERIPHCHUNKSIZE_1 | \ ((0x3f) << DMACH_FLAG_MEMPID_SHIFT) | DMACH_FLAG_MEMAHB_AHB_IF0 | \ DMACH_FLAG_MEMWIDTH_16BITS | DMACH_FLAG_MEMINCREMENT | \ DMACH_FLAG_MEMCHUNKSIZE_1) #endif /* Pick an unused channel number */ #if !defined(CONFIG_SAMA5_ADC_CHAN0) # define SAMA5_ADC_UNUSED 0 #elif !defined(CONFIG_SAMA5_ADC_CHAN1) # define SAMA5_ADC_UNUSED 1 #elif !defined(CONFIG_SAMA5_ADC_CHAN2) # define SAMA5_ADC_UNUSED 2 #elif !defined(CONFIG_SAMA5_ADC_CHAN3) # define SAMA5_ADC_UNUSED 3 #elif !defined(CONFIG_SAMA5_ADC_CHAN4) # define SAMA5_ADC_UNUSED 4 #elif !defined(CONFIG_SAMA5_ADC_CHAN5) # define SAMA5_ADC_UNUSED 5 #elif !defined(CONFIG_SAMA5_ADC_CHAN6) # define SAMA5_ADC_UNUSED 6 #elif !defined(CONFIG_SAMA5_ADC_CHAN7) # define SAMA5_ADC_UNUSED 7 #elif !defined(CONFIG_SAMA5_ADC_CHAN8) # define SAMA5_ADC_UNUSED 8 #elif !defined(CONFIG_SAMA5_ADC_CHAN9) # define SAMA5_ADC_UNUSED 9 #elif !defined(CONFIG_SAMA5_ADC_CHAN10) # define SAMA5_ADC_UNUSED 10 #elif !defined(CONFIG_SAMA5_ADC_CHAN11) # define SAMA5_ADC_UNUSED 11 #else # undef SAMA5_ADC_UNUSED #endif /* Number of DMA samples to collect */ #if !defined(CONFIG_SAMA5_ADC_DMA) # undef CONFIG_SAMA5_ADC_DMASAMPLES # define CONFIG_SAMA5_ADC_DMASAMPLES 1 #elif !defined(CONFIG_SAMA5_ADC_DMASAMPLES) # error CONFIG_SAMA5_ADC_DMASAMPLES must be defined #elif CONFIG_SAMA5_ADC_DMASAMPLES < 2 # warning Values of ONFIG_SAMA5_ADC_DMASAMPLES < 2 are inefficient #endif #define SAMA5_ADC_SAMPLES (CONFIG_SAMA5_ADC_DMASAMPLES * SAMA5_NCHANNELS) /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes the overall state of the ADC */ struct sam_adc_s { sem_t exclsem; /* Supports exclusive access to the ADC interface */ bool initialized; /* The ADC driver is already initialized */ uint32_t frequency; /* ADC clock frequency */ #ifdef SAMA5_ADC_HAVE_CHANNELS #ifdef CONFIG_SAMA5_ADC_DMA volatile bool odd; /* Odd buffer is in use */ volatile bool ready; /* Worker has completed the last set of samples */ volatile bool enabled; /* DMA data transfer is enabled */ #endif struct adc_dev_s *dev; /* A reference to the outer, ADC device container */ uint32_t pending; /* Pending EOC events */ struct work_s work; /* Supports the interrupt handling "bottom half" */ #ifdef CONFIG_SAMA5_ADC_DMA DMA_HANDLE dma; /* Handle for DMA channel */ #endif #ifdef CONFIG_SAMA5_ADC_TIOATRIG TC_HANDLE tc; /* Handle for the timer channel */ #endif /* DMA sample data buffer */ #ifdef CONFIG_SAMA5_ADC_DMA uint16_t evenbuf[SAMA5_ADC_SAMPLES]; uint16_t oddbuf[SAMA5_ADC_SAMPLES]; #endif #endif /* SAMA5_ADC_HAVE_CHANNELS */ /* Debug stuff */ #ifdef CONFIG_SAMA5_ADC_REGDEBUG bool wrlast; /* Last was a write */ uintptr_t addrlast; /* Last address */ uint32_t vallast; /* Last value */ int ntimes; /* Number of times */ #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Register operations ******************************************************/ #if defined(CONFIG_SAMA5_ADC_REGDEBUG) && defined(CONFIG_DEBUG) static bool sam_adc_checkreg(struct sam_adc_s *priv, bool wr, uint32_t regval, uintptr_t address); #endif /* DMA helper functions */ #ifdef SAMA5_ADC_HAVE_CHANNELS #ifdef CONFIG_SAMA5_ADC_DMA static void sam_adc_dmadone(void *arg); static void sam_adc_dmacallback(DMA_HANDLE handle, void *arg, int result); static int sam_adc_dmasetup(struct sam_adc_s *priv, FAR uint8_t *buffer, size_t buflen); static void sam_adc_dmastart(struct sam_adc_s *priv); #endif /* ADC interrupt handling */ static void sam_adc_endconversion(void *arg); #endif static int sam_adc_interrupt(int irq, void *context); /* ADC methods */ #ifdef SAMA5_ADC_HAVE_CHANNELS static void sam_adc_reset(struct adc_dev_s *dev); static int sam_adc_setup(struct adc_dev_s *dev); static void sam_adc_shutdown(struct adc_dev_s *dev); static void sam_adc_rxint(struct adc_dev_s *dev, bool enable); static int sam_adc_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg); /* Initialization/Configuration */ #ifdef CONFIG_SAMA5_ADC_TIOATRIG static int sam_adc_settimer(struct sam_adc_s *priv, uint32_t frequency, int channel); static void sam_adc_freetimer(struct sam_adc_s *priv); #endif static int sam_adc_trigger(struct sam_adc_s *priv); static void sam_adc_autocalibrate(struct sam_adc_s *priv); static void sam_adc_offset(struct sam_adc_s *priv); static void sam_adc_gain(struct sam_adc_s *priv); static void sam_adc_analogchange(struct sam_adc_s *priv); static void sam_adc_sequencer(struct sam_adc_s *priv); static void sam_adc_channels(struct sam_adc_s *priv); #endif /**************************************************************************** * Private Data ****************************************************************************/ #ifdef SAMA5_ADC_HAVE_CHANNELS /* ADC lower half device operations */ static const struct adc_ops_s g_adcops = { .ao_reset = sam_adc_reset, .ao_setup = sam_adc_setup, .ao_shutdown = sam_adc_shutdown, .ao_rxint = sam_adc_rxint, .ao_ioctl = sam_adc_ioctl, }; #endif /* ADC internal state */ static struct sam_adc_s g_adcpriv; #ifdef SAMA5_ADC_HAVE_CHANNELS /* ADC device instance */ static struct adc_dev_s g_adcdev; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Register Operations ****************************************************************************/ /**************************************************************************** * Name: sam_adc_checkreg * * Description: * Check if the current register access is a duplicate of the preceding. * * Input Parameters: * regval - The value to be written * address - The address of the register to write to * * Returned Value: * true: This is the first register access of this type. * flase: This is the same as the preceding register access. * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_REGDEBUG static bool sam_adc_checkreg(struct sam_adc_s *priv, bool wr, uint32_t regval, uintptr_t address) { if (wr == priv->wrlast && /* Same kind of access? */ regval == priv->vallast && /* Same value? */ address == priv->addrlast) /* Same address? */ { /* Yes, then just keep a count of the number of times we did this. */ priv->ntimes++; return false; } else { /* Did we do the previous operation more than once? */ if (priv->ntimes > 0) { /* Yes... show how many times we did it */ lldbg("...[Repeats %d times]...\n", priv->ntimes); } /* Save information about the new access */ priv->wrlast = wr; priv->vallast = regval; priv->addrlast = address; priv->ntimes = 0; } /* Return true if this is the first time that we have done this operation */ return true; } #endif #ifdef SAMA5_ADC_HAVE_CHANNELS /**************************************************************************** * DMA Helpers ****************************************************************************/ /**************************************************************************** * Name: sam_adc_dmadone * * Description: * This function executes on the worker thread. It is scheduled by * sam_adc_dmacallback at the complete of each DMA sequenece. There is * and interlock using ping-pong buffers and boolean values to prevent * overrunning the worker thread: * * oddbuf[]/evenbuf[] - Ping pong buffers are used. The DMA collects * data in one buffer while the worker thread processes data in the * other. * odd - If true, then DMA is active in the oddbuf[]; evenbuf[] holds * completed DMA data. * ready - Ping ponging is halted while ready is false; If data overrun * occurs, then sample data will be lost on one sequence. The worker * thread sets ready when it has completed processing the last sample * data. * * Input Parameters * arg - The ADC private data structure cast to (void *) * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_DMA static void sam_adc_dmadone(void *arg) { struct sam_adc_s *priv = (struct sam_adc_s *)arg; uint16_t *buffer; uint16_t *next; uint16_t sample; int chan; int i; avdbg("ready=%d enabled=%d\n", priv->enabled, priv->ready); ASSERT(priv != NULL && !priv->ready); /* If the DMA transfer is not enabled, just ignore the data (and do not start * the next DMA transfer). */ if (priv->enabled) { /* Toggle to the next buffer. * * buffer - The buffer on which the DMA has just completed * next - The buffer in which to start the next DMA */ if (priv->odd) { buffer = priv->oddbuf; next = priv->evenbuf; priv->odd = false; } else { buffer = priv->evenbuf; next = priv->oddbuf; priv->odd = true; } /* Restart the DMA conversion as quickly as possible using the next * buffer. * * REVISIT: In the original design, toggling the ping-pong buffers and * restarting the DMA was done in the interrupt handler so that the * next buffer could be filling while the current buffer is being * processed here on the worker thread. But, unfortunately, * sam_adcm_dmasetup() cannot be called from an interrupt handler. * * A consequence of this is that there is a small window from the time * that the last set of samples was taken, the worker thread runs, and * the follow logic restarts the DMA in which samples could be lost! * * Without the interrupt level DMA restart logic, there is not really * any good reason to support the ping-poing buffers at all. */ sam_adc_dmasetup(priv, (FAR uint8_t *)next, SAMA5_ADC_SAMPLES * sizeof(uint16_t)); /* Invalidate the DMA buffer so that we are guaranteed to reload the * newly DMAed data from RAM. */ cp15_invalidate_dcache((uintptr_t)buffer, (uintptr_t)buffer + SAMA5_ADC_SAMPLES * sizeof(uint16_t)); /* Process each sample */ for (i = 0; i < SAMA5_ADC_SAMPLES; i++, buffer++) { /* Get the sample and the channel number */ chan = (int)((*buffer & ADC_LCDR_CHANB_MASK) >> ADC_LCDR_CHANB_SHIFT); sample = ((*buffer & ADC_LCDR_DATA_MASK) >> ADC_LCDR_DATA_SHIFT); /* And give the sample data to the ADC upper half */ (void)adc_receive(priv->dev, chan, sample); } } /* We are ready to handle the next sample sequence */ priv->ready = true; } #endif /**************************************************************************** * Name: sam_adc_dmastart * * Description: * Initiate DMA sampling. * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_DMA static void sam_adc_dmastart(struct sam_adc_s *priv) { /* Make sure that the worker is available and that DMA is not disabled */ if (priv->ready && priv->enabled) { priv->odd = false; /* Start with the even buffer */ sam_adc_dmasetup(priv, (FAR uint8_t *)priv->evenbuf, SAMA5_ADC_SAMPLES * sizeof(uint16_t)); } } #endif /**************************************************************************** * Name: sam_adc_dmacallback * * Description: * Called when one ADC DMA sequence completes. This function defers * processing of the samples to sam_adc_dmadone which runs on the worker * thread. * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_DMA static void sam_adc_dmacallback(DMA_HANDLE handle, void *arg, int result) { struct sam_adc_s *priv = (struct sam_adc_s *)arg; int ret; allvdbg("ready=%d enabled=%d\n", priv->enabled, priv->ready); DEBUGASSERT(priv->ready); /* Check of the bottom half is keeping up with us. * * ready == false: Would mean that the worker thready has not ran since * the the last DMA callback. * enabled == false: Means that the upper half has asked us nicely to stop * transferring DMA data. */ if (priv->ready && priv->enabled) { /* Verify that the worker is available */ DEBUGASSERT(priv->work.worker == NULL); /* Mark the work as busy and schedule the DMA done processing to * occur on the worker thread. */ priv->ready = false; ret = work_queue(HPWORK, &priv->work, sam_adc_dmadone, priv, 0); if (ret != 0) { alldbg("ERROR: Failed to queue work: %d\n", ret); } } /* REVISIT: There used to be logic here to toggle the ping-pong buffers and * to restart the DMA conversion. This would allow refilling one buffer * while the worker processes the other buffer that was just filled. But, * unfortunately, sam_adcm_dmasetup() and dma_rxsetup cannot be called * from an interrupt handler. * * A consequence of this is that there is a small window from the time * that the last set of samples was taken, the worker thread runs, and the * logic on the worker thread restarts the DMA. Samples trigger during * this window will be be lost! * * Without this logic, there is not really any strong reason to support * the ping-poing buffers at all. */ } #endif /**************************************************************************** * Name: sam_adc_dmasetup * * Description: * Setup to perform a read DMA. If the processor supports a data cache, * then this method will also make sure that the contents of the DMA memory * and the data cache are coherent. For read transfers this may mean * invalidating the data cache. * * Input Parameters: * priv - An instance of the ADC device interface * buffer - The memory to DMA from * buflen - The size of the DMA transfer in bytes * * Returned Value: * OK on success; a negated errno on failure * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_DMA static int sam_adc_dmasetup(FAR struct sam_adc_s *priv, FAR uint8_t *buffer, size_t buflen) { uint32_t paddr; uint32_t maddr; avdbg("buffer=%p buflen=%d\n", buffer, (int)buflen); DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); DEBUGASSERT(((uint32_t)buffer & 3) == 0); /* Physical address of the ADC LCDR register and of the buffer location in * RAM. */ paddr = sam_physregaddr(SAM_ADC_LCDR); maddr = sam_physramaddr((uintptr_t)buffer); /* Configure the RX DMA */ sam_dmarxsetup(priv->dma, paddr, maddr, buflen); /* Start the DMA */ sam_dmastart(priv->dma, sam_adc_dmacallback, priv); return OK; } #endif /**************************************************************************** * ADC interrupt handling ****************************************************************************/ /**************************************************************************** * Name: sam_adc_endconversion * * Description: * This function executes on the worker thread. It is scheduled by * sam_adc_interrupt whenever any enabled end-of-conversion event occurs. * All EOC interrupts are disabled when this function runs. * sam_adc_endconversion will re-enable EOC interrupts when it completes * processing all pending EOC events. * * Input Parameters * arg - The ADC private data structure cast to (void *) * * Returned Value: * None * ****************************************************************************/ static void sam_adc_endconversion(void *arg) { struct sam_adc_s *priv = (struct sam_adc_s *)arg; uint32_t regval; uint32_t pending; int chan; ASSERT(priv != NULL); avdbg("pending=%08x\n", priv->pending); /* Get the set of unmasked, pending ADC interrupts */ pending = priv->pending; /* Get exclusive access to the driver data structure */ sam_adc_lock(priv); /* Check for the end of conversion event on each channel */ for (chan = 0; chan < SAM_ADC_NCHANNELS && pending != 0; chan++) { uint32_t bit = ADC_INT_EOC(chan); if ((pending & bit) != 0) { /* Read the ADC sample and pass it to the upper half */ regval = sam_adc_getreg(priv, SAM_ADC_CDR(chan)); (void)adc_receive(priv->dev, chan, regval & ADC_CDR_DATA_MASK); pending &= ~bit; } } /* Exit, re-enabling ADC interrupts */ sam_adc_putreg(priv, SAM_ADC_IER, SAMA5_CHAN_ENABLE); /* Release our lock on the ADC structure */ sam_adc_unlock(priv); } #endif /* SAMA5_ADC_HAVE_CHANNELS */ /**************************************************************************** * Name: sam_adc_interrupt * * Description: * ADC interrupt handler * ****************************************************************************/ static int sam_adc_interrupt(int irq, void *context) { struct sam_adc_s *priv = &g_adcpriv; uint32_t isr; uint32_t imr; uint32_t pending; /* Get the set of unmasked, pending ADC interrupts */ isr = sam_adc_getreg(priv, SAM_ADC_ISR); imr = sam_adc_getreg(priv, SAM_ADC_IMR); pending = isr & imr; /* Handle pending touchscreen interrupts */ #ifdef CONFIG_SAMA5_TSD if ((pending & ADC_TSD_ALLINTS) != 0) { /* Let the touchscreen handle its interrupts. Pass the pending * interrupt set PLUS the pen status bit. */ sam_tsd_interrupt(isr & (imr | ADC_SR_PENS)); pending &= ~ADC_TSD_ALLINTS; } #endif #ifdef SAMA5_ADC_HAVE_CHANNELS /* Check for end-of-conversion interrupts */ if ((pending & ADC_INT_EOCALL) != 0) { int ret; /* Disable further end-of-conversion interrupts. End-of-conversion * interrupts will be re-enabled after the worker thread executes. */ sam_adc_putreg(priv, SAM_ADC_IDR, ADC_INT_EOCALL); /* Save the set of pending interrupts for the bottom half (in case any * were cleared by reading the ISR). */ priv->pending = pending; /* Transfer processing to the worker thread. Since end-of-conversion * interrupts are disabled while the work is pending, no special action * should be required to protected the work queue. */ DEBUGASSERT(priv->work.worker == NULL); ret = work_queue(HPWORK, &priv->work, sam_adc_endconversion, priv, 0); if (ret != 0) { alldbg("ERROR: Failed to queue work: %d\n", ret); } pending &= ~ADC_INT_EOCALL; } #endif /* Make sure that all interrupts were handled */ DEBUGASSERT(pending == 0); return OK; } #ifdef SAMA5_ADC_HAVE_CHANNELS /**************************************************************************** * ADC methods ****************************************************************************/ /**************************************************************************** * Name: sam_adc_reset * * Description: * Reset the ADC device. Called early to initialize the hardware. This * is called, before sam_adc_setup() and on error conditions. * ****************************************************************************/ static void sam_adc_reset(struct adc_dev_s *dev) { #ifdef CONFIG_SAMA5_ADC_DMA struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv; #endif uint32_t regval; avdbg("Resetting..\n"); /* NOTE: We can't really reset the ADC hardware without losing the * touchscreen configuration. */ /* Stop any ongoing DMA */ #ifdef CONFIG_SAMA5_ADC_DMA if (priv->dma) { sam_dmastop(priv->dma); } #endif /* Stop an release any timer */ #ifdef CONFIG_SAMA5_ADC_TIOATRIG sam_adc_freetimer(priv); #endif /* Disable all EOC interrupts */ sam_adc_putreg(priv, SAM_ADC_IDR, ADC_INT_EOCALL); /* Disable all channels */ sam_adc_putreg(priv, SAM_ADC_CHDR, SAMA5_ADC_CHALL); /* Disable the sequencer and analog change */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~(ADC_MR_USEQ | ADC_MR_ANACH); sam_adc_putreg(priv, SAM_ADC_MR, regval); /* Reset gain, offset, differential modes */ sam_adc_putreg(priv, SAM_ADC_CGR, 0); sam_adc_putreg(priv, SAM_ADC_COR, 0); #ifndef CONFIG_SAMA5_ADC_SWTRIG /* Select software trigger (i.e., basically no trigger) */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~ADC_MR_TRGSEL_MASK; sam_adc_putreg(priv, SAM_ADC_MR, regval); regval = sam_adc_getreg(priv, SAM_ADC_TRGR); regval &= ~ADC_TRGR_TRGMOD_MASK; regval |= ADC_TRGR_TRGMOD_NOTRIG; sam_adc_putreg(priv, SAM_ADC_TRGR, regval); #endif } /**************************************************************************** * Name: sam_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. * ****************************************************************************/ static int sam_adc_setup(struct adc_dev_s *dev) { struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv; uint32_t regval; avdbg("Setup\n"); /* Enable channel number tag. This bit will force the channel number (CHNB) * to be included in the LDCR register content. */ regval = sam_adc_getreg(priv, SAM_ADC_EMR); regval |= ADC_EMR_TAG; sam_adc_putreg(priv, SAM_ADC_EMR, regval); /* Enable (or disable) the sequencer */ sam_adc_sequencer(priv); /* Enable ADC channels */ sam_adc_channels(priv); /* Enable/disable analog change. This feature permits different settings * per channel. */ sam_adc_analogchange(priv); /* Set gain */ sam_adc_gain(priv); /* Set offset and single/differential mode */ sam_adc_offset(priv); /* Perform Auto Calibration */ sam_adc_autocalibrate(priv); #ifdef CONFIG_SAMA5_ADC_DMA /* Initiate DMA transfers */ priv->ready = true; /* Worker is avaiable */ priv->enabled = true; /* Transfers are enabled */ sam_adc_dmastart(priv); #else /* Enable end-of-conversion interrupts for all enabled channels. */ sam_adc_putreg(priv, SAM_ADC_IER, SAMA5_CHAN_ENABLE); #endif /* Configure trigger mode and start conversion */ return sam_adc_trigger(priv); } /**************************************************************************** * Name: sam_adc_shutdown * * Description: * Disable the ADC. This method is called when the ADC device is closed. * This method reverses the operation the setup method. * ****************************************************************************/ static void sam_adc_shutdown(struct adc_dev_s *dev) { #ifdef CONFIG_SAMA5_ADC_DMA struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv; #endif avdbg("Shutdown\n"); /* Reset the ADC peripheral */ sam_adc_reset(dev); /* Disable ADC interrupts at the level of the AIC */ up_disable_irq(SAM_IRQ_ADC); /* Then detach the ADC interrupt handler. */ irq_detach(SAM_IRQ_ADC); } /**************************************************************************** * Name: sam_adc_rxint * * Description: * Call to enable or disable RX interrupts * ****************************************************************************/ static void sam_adc_rxint(struct adc_dev_s *dev, bool enable) { #ifdef CONFIG_SAMA5_ADC_DMA struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv; #endif avdbg("enable=%d\n", enable); #ifdef CONFIG_SAMA5_ADC_DMA /* Ignore redundant requests */ if (priv->enabled != enable) { /* Set a flag. If disabling, the DMA sequence will terminate at the * completion of the next DMA. */ priv->enabled = enable; /* If enabling, then we need to restart the DMA transfer */ sam_adc_dmastart(priv); } #else /* Are we enabling or disabling? */ if (enable) { /* Enable channel interrupts */ sam_adc_putreg(priv, SAM_ADC_IER, SAMA5_CHAN_ENABLE); } else { /* Disable channel interrupts */ sam_adc_putreg(priv, SAM_ADC_IDR, ADC_INT_EOCALL); } #endif } /**************************************************************************** * Name: sam_adc_ioctl * * Description: * All ioctl calls will be routed through this method * ****************************************************************************/ static int sam_adc_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg) { #ifdef CONFIG_SAMA5_ADC_SWTRIG struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv; #endif int ret = OK; avdbg("cmd=%d arg=%ld\n", cmd, arg); switch (cmd) { #ifdef CONFIG_SAMA5_ADC_SWTRIG case ANIOC_TRIGGER: /* Software trigger */ { sam_adc_putreg(priv, SAM_ADC_CR, ADC_CR_START); /* Start conversion */ } break; #endif /* Unsupported or invalid command */ default: ret = -ENOTTY; break; } return ret; } /**************************************************************************** * Initialization/Configuration ****************************************************************************/ /**************************************************************************** * Name: sam_adc_settimer * * Description: * Configure a timer to trigger the sampling periodically * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_TIOATRIG static int sam_adc_settimer(struct sam_adc_s *priv, uint32_t frequency, int channel) { uint32_t div; uint32_t tcclks; uint32_t mode; uint32_t fdiv; int ret; avdbg("frequency=%ld channel=%d\n", (long)frequency, channel); DEBUGASSERT(priv && frequency > 0); /* Configure TC for a 1Hz frequency and trigger on RC compare. */ ret = sam_tc_divisor(frequency, &div, &tcclks); if (ret < 0) { adbg("ERROR: sam_tc_divisor failed: %d\n", ret); return ret; } /* Set the timer/counter waveform mode the the clock input slected by * sam_tc_divisor() */ mode = ((tcclks << TC_CMR_TCCLKS_SHIFT) | /* Use selected TCCLKS value */ TC_CMR_WAVSEL_UPRC | /* UP mode w/ trigger on RC Compare */ TC_CMR_WAVE | /* Wave mode */ TC_CMR_ACPA_CLEAR | /* RA Compare Effect on TIOA: Clear */ TC_CMR_ACPC_SET); /* RC effect on TIOA: Set */ /* Now allocate and configure the channel */ priv->tc = sam_tc_allocate(channel, mode); if (!priv->tc) { adbg("ERROR: Failed to allocate channel %d mode %08x\n", channel, mode); return -EINVAL; } /* The divider returned by sam_tc_divisor() is the reload value that will * achieve a 1HZ rate. We need to multiply this to get the desired * frequency. sam_tc_divisor() should have already assure that we can * do this without overflowing a 32-bit unsigned integer. */ fdiv = div * frequency; DEBUGASSERT(div > 0 && div <= fdiv); /* Will check for integer overflow */ /* Set up TC_RA and TC_RC. The frequency is determined by RA and RC: TIOA is * cleared on RA match; TIOA is set on RC match. */ sam_tc_setregister(priv->tc, TC_REGA, fdiv << 1); sam_tc_setregister(priv->tc, TC_REGC, fdiv); /* And start the timer */ sam_tc_start(priv->tc); return OK; } #endif /**************************************************************************** * Name: sam_adc_freetimer * * Description: * Configure a timer to trigger the sampling periodically * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_TIOATRIG static void sam_adc_freetimer(struct sam_adc_s *priv) { /* Is a timer allocated? */ avdbg("tc=%p\n", priv->tc); if (priv->tc) { /* Yes.. stop it and free it */ sam_tc_stop(priv->tc); sam_tc_free(priv->tc); priv->tc = NULL; } } #endif /**************************************************************************** * Name: sam_adc_trigger * * Description: * Configure trigger mode and start conversion. * ****************************************************************************/ static int sam_adc_trigger(struct sam_adc_s *priv) { uint32_t regval; int ret = OK; #if defined(CONFIG_SAMA5_ADC_SWTRIG) avdbg("Setup software trigger\n"); /* Configure the software trigger */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~ADC_MR_TRGSEL_MASK; sam_adc_putreg(priv, SAM_ADC_MR, regval); /* No trigger, only software trigger can start conversions */ regval = sam_adc_getreg(priv, SAM_ADC_TRGR); regval &= ~ADC_TRGR_TRGMOD_MASK; regval |= ADC_TRGR_TRGMOD_NOTRIG; sam_adc_putreg(priv, SAM_ADC_TRGR, regval); #elif defined(CONFIG_SAMA5_ADC_ADTRG) avdbg("Setup ADTRG trigger\n"); /* Configure the trigger via the external ADTRG signal */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~ADC_MR_TRGSEL_MASK; regval |= ADC_MR_TRGSEL_ADC_ADTRIG; sam_adc_putreg(priv, SAM_ADC_MR, regval); /* External trigger edge selection */ regval = sam_adc_getreg(priv, SAM_ADC_TRGR); regval &= ~ADC_TRGR_TRGMOD_MASK; #if defined(CONFIG_SAMA5_ADC_ADTRG_RISING) regval |= ADC_TRGR_TRGMOD_EXTRISE; #elif defined(CONFIG_SAMA5_ADC_ADTRG_FALLING) regval |= ADC_TRGR_TRGMOD_EXTFALL; #elif defined(CONFIG_SAMA5_ADC_ADTRG_BOTH) regval |= ADC_TRGR_TRGMOD_EXTBOTH; #else # error External trigger edge not defined #endif sam_adc_putreg(priv, SAM_ADC_TRGR, regval); #elif defined(CONFIG_SAMA5_ADC_TIOATRIG) avdbg("Setup timer/counter trigger\n"); /* Start the timer */ #if defined(CONFIG_SAMA5_ADC_TIOA0TRIG) ret = sam_adc_settimer(priv, CONFIG_SAMA5_ADC_TIOAFREQ, TC_CHAN0); #elif defined(CONFIG_SAMA5_ADC_TIOA1TRIG) ret = sam_adc_settimer(priv, CONFIG_SAMA5_ADC_TIOAFREQ, TC_CHAN1); #elif defined(CONFIG_SAMA5_ADC_TIOA2TRIG) ret = sam_adc_settimer(priv, CONFIG_SAMA5_ADC_TIOAFREQ, TC_CHAN2); #else # error Timer/counter for trigger not defined ret = -ENOSYS; #endif if (ret < 0) { adbg("ERROR: sam_adc_settimer failed: %d\n", ret); return ret; } /* Configure to trigger using Timer/counter 0, channel 1, 2, or 3. * NOTE: This trigger option depends on having properly configuer * timer/counter 0 to provide this output. That is done independently * the the timer/counter driver. */ /* Set TIOAn trigger where n=0, 1, or 2 */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~ADC_MR_TRGSEL_MASK; #if defined(CONFIG_SAMA5_ADC_TIOA0TRIG) regval |= ADC_MR_TRGSEL_TIOA0; /* Timer/counter 0 channel 0 output A */ #elif defined(CONFIG_SAMA5_ADC_TIOA1TRIG) regval |= ADC_MR_TRGSEL_TIOA1; /* Timer/counter 0 channel 1 output A */ #elif defined(CONFIG_SAMA5_ADC_TIOA2TRIG) regval |= ADC_MR_TRGSEL_TIOA2; /* Timer/counter 0 channel 2 output A */ #else # error Timer/counter for trigger not defined #endif sam_adc_putreg(priv, SAM_ADC_MR, regval); /* Timer trigger edge selection */ regval = sam_adc_getreg(priv, SAM_ADC_TRGR); regval &= ~ADC_TRGR_TRGMOD_MASK; #if defined(CONFIG_SAMA5_ADC_TIOA_RISING) regval |= ADC_TRGR_TRGMOD_EXTRISE; #elif defined(CONFIG_SAMA5_ADC_TIOA_FALLING) regval |= ADC_TRGR_TRGMOD_EXTFALL; #elif defined(CONFIG_SAMA5_ADC_TIOA_BOTH) regval |= ADC_TRGR_TRGMOD_EXTBOTH; #else # error External trigger edge not defined #endif sam_adc_putreg(priv, SAM_ADC_TRGR, regval); #else # error "Undefined ADC trigger" #endif return ret; } /**************************************************************************** * Name: sam_adc_autocalibrate * * Description: * Perform ADC auto-calibration. * ****************************************************************************/ static void sam_adc_autocalibrate(struct sam_adc_s *priv) { #ifdef CONFIG_SAMA5_ADC_AUTOCALIB uint32_t regval; avdbg("Entry\n"); /* Launch an automatic calibration of the ADC cell on next sequence */ regval = sam_adc_getreg(priv, SAM_ADC_CR); regval |= ADC_CR_AUTOCAL; sam_adc_putreg(priv, SAM_ADC_CR, regval); /* Wait for auto calibration to complete */ while (sam_adc_getreg(priv, SAM_ADC_ISR) & ADC_ISR_EOCAL) != ADC_ISR_EOCAL); #endif } /**************************************************************************** * Name: sam_adc_offset * * Description: * Configure ADC offset. Also while we are modifying the COR register, * configure differential mode if selected. * ****************************************************************************/ static void sam_adc_offset(struct sam_adc_s *priv) { uint32_t regval = 0; avdbg("Entry\n"); #ifdef CONFIG_SAMA5_ADC_ANARCH /* Set the offset for each enabled channel. This centers the analog signal * on Vrefin/2 before the gain scaling. The Offset applied is: (G-1)Vrefin/2 * where G is the gain applied. The default is no offset. */ #if defined(CONFIG_SAMA5_ADC_CHAN0) && defined(CONFIG_SAMA5_ADC_OFFSET0) regval |= ADC_COR_OFF0; #endif #if defined(CONFIG_SAMA5_ADC_CHAN1) && defined(CONFIG_SAMA5_ADC_OFFSET1) regval |= ADC_COR_OFF1; #endif #if defined(CONFIG_SAMA5_ADC_CHAN2) && defined(CONFIG_SAMA5_ADC_OFFSET2) regval |= ADC_COR_OFF2; #endif #if defined(CONFIG_SAMA5_ADC_CHAN3) && defined(CONFIG_SAMA5_ADC_OFFSET3) regval |= ADC_COR_OFF3; #endif #if defined(CONFIG_SAMA5_ADC_CHAN4) && defined(CONFIG_SAMA5_ADC_OFFSET4) regval |= ADC_COR_OFF4; #endif #if defined(CONFIG_SAMA5_ADC_CHAN5) && defined(CONFIG_SAMA5_ADC_OFFSET5) regval |= ADC_COR_OFF5; #endif #if defined(CONFIG_SAMA5_ADC_CHAN6) && defined(CONFIG_SAMA5_ADC_OFFSET6) regval |= ADC_COR_OFF6; #endif #if defined(CONFIG_SAMA5_ADC_CHAN7) && defined(CONFIG_SAMA5_ADC_OFFSET7) regval |= ADC_COR_OFF7; #endif #if defined(CONFIG_SAMA5_ADC_CHAN8) && defined(CONFIG_SAMA5_ADC_OFFSET8) regval |= ADC_COR_OFF8; #endif #if defined(CONFIG_SAMA5_ADC_CHAN9) && defined(CONFIG_SAMA5_ADC_OFFSET9) regval |= ADC_COR_OFF9; #endif #if defined(CONFIG_SAMA5_ADC_CHAN10) && defined(CONFIG_SAMA5_ADC_OFFSET10) regval |= ADC_COR_OFF10; #endif #if defined(CONFIG_SAMA5_ADC_CHAN11) && defined(CONFIG_SAMA5_ADC_OFFSET11) regval |= ADC_COR_OFF11; #endif /* Set the differential mode of operation for each enabled channel. * The default is single-ended operation. */ #if defined(CONFIG_SAMA5_ADC_CHAN0) && defined(CONFIG_SAMA5_ADC_DIFFMODE0) regval |= ADC_COR_DIFF0; #endif #if defined(CONFIG_SAMA5_ADC_CHAN1) && defined(CONFIG_SAMA5_ADC_DIFFMODE1) regval |= ADC_COR_DIFF1; #endif #if defined(CONFIG_SAMA5_ADC_CHAN2) && defined(CONFIG_SAMA5_ADC_DIFFMODE2) regval |= ADC_COR_DIFF2; #endif #if defined(CONFIG_SAMA5_ADC_CHAN3) && defined(CONFIG_SAMA5_ADC_DIFFMODE3) regval |= ADC_COR_DIFF3; #endif #if defined(CONFIG_SAMA5_ADC_CHAN4) && defined(CONFIG_SAMA5_ADC_DIFFMODE4) regval |= ADC_COR_DIFF4; #endif #if defined(CONFIG_SAMA5_ADC_CHAN5) && defined(CONFIG_SAMA5_ADC_DIFFMODE5) regval |= ADC_COR_DIFF5; #endif #if defined(CONFIG_SAMA5_ADC_CHAN6) && defined(CONFIG_SAMA5_ADC_DIFFMODE6) regval |= ADC_COR_DIFF6; #endif #if defined(CONFIG_SAMA5_ADC_CHAN7) && defined(CONFIG_SAMA5_ADC_DIFFMODE7) regval |= ADC_COR_DIFF7; #endif #if defined(CONFIG_SAMA5_ADC_CHAN8) && defined(CONFIG_SAMA5_ADC_DIFFMODE8) regval |= ADC_COR_DIFF8; #endif #if defined(CONFIG_SAMA5_ADC_CHAN9) && defined(CONFIG_SAMA5_ADC_DIFFMODE9) regval |= ADC_COR_DIFF9; #endif #if defined(CONFIG_SAMA5_ADC_CHAN10) && defined(CONFIG_SAMA5_ADC_DIFFMODE10) regval |= ADC_COR_DIFF10; #endif #if defined(CONFIG_SAMA5_ADC_CHAN11) && defined(CONFIG_SAMA5_ADC_DIFFMODE11) regval |= ADC_COR_DIFF11; #endif #else /* Set offset and differential mode only on channel 0. This will be * used for all channel. */ #if CONFIG_SAMA5_ADC_OFFSET regval |= ADC_COR_OFF0; #endif #if CONFIG_SAMA5_ADC_DIFFMODE regval |= ADC_COR_DIFF0; #endif #endif /* Save the updated COR register value */ sam_adc_putreg(priv, SAM_ADC_COR, regval); } /**************************************************************************** * Name: sam_adc_gain * * Description: * Configure ADC gain. * ****************************************************************************/ static void sam_adc_gain(struct sam_adc_s *priv) { #ifdef CONFIG_SAMA5_ADC_ANARCH uint32_t regval; avdbg("Entry\n"); /* Set the gain for each enabled channel */ regval = 0; #ifdef CONFIG_SAMA5_ADC_CHAN0 regval |= ADC_CGR_GAIN0(CONFIG_SAMA5_ADC_GAIN0); #endif #ifdef CONFIG_SAMA5_ADC_CHAN1 regval |= ADC_CGR_GAIN1(CONFIG_SAMA5_ADC_GAIN1); #endif #ifdef CONFIG_SAMA5_ADC_CHAN2 regval |= ADC_CGR_GAIN2(CONFIG_SAMA5_ADC_GAIN2); #endif #ifdef CONFIG_SAMA5_ADC_CHAN3 regval |= ADC_CGR_GAIN3(CONFIG_SAMA5_ADC_GAIN3); #endif #ifdef CONFIG_SAMA5_ADC_CHAN4 regval |= ADC_CGR_GAIN4(CONFIG_SAMA5_ADC_GAIN4); #endif #ifdef CONFIG_SAMA5_ADC_CHAN5 regval |= ADC_CGR_GAIN5(CONFIG_SAMA5_ADC_GAIN5); #endif #ifdef CONFIG_SAMA5_ADC_CHAN6 regval |= ADC_CGR_GAIN6(CONFIG_SAMA5_ADC_GAIN6); #endif #ifdef CONFIG_SAMA5_ADC_CHAN7 regval |= ADC_CGR_GAIN7(CONFIG_SAMA5_ADC_GAIN7); #endif #ifdef CONFIG_SAMA5_ADC_CHAN8 regval |= ADC_CGR_GAIN8(CONFIG_SAMA5_ADC_GAIN8); #endif #ifdef CONFIG_SAMA5_ADC_CHAN9 regval |= ADC_CGR_GAIN9(CONFIG_SAMA5_ADC_GAIN9); #endif #ifdef CONFIG_SAMA5_ADC_CHAN10 regval |= ADC_CGR_GAIN10(CONFIG_SAMA5_ADC_GAIN10); #endif #ifdef CONFIG_SAMA5_ADC_CHAN11 regval |= ADC_CGR_GAIN11(CONFIG_SAMA5_ADC_GAIN11); #endif sam_adc_putreg(priv, SAM_ADC_CGR, regval); #else avdbg("Gain=%d\n", CONFIG_SAMA5_ADC_GAIN); /* Set GAIN0 only. GAIN0 will be used for all channels. */ sam_adc_putreg(priv, SAM_ADC_CGR, ADC_CGR_GAIN0(CONFIG_SAMA5_ADC_GAIN)); #endif } /**************************************************************************** * Name: sam_adc_analogchange * * Description: * Configure analog change. This features permits different analog * settings per channel. * ****************************************************************************/ static void sam_adc_analogchange(struct sam_adc_s *priv) { uint32_t regval; avdbg("Entry\n"); /* Enable/disable the analog change feature */ regval = sam_adc_getreg(priv, SAM_ADC_MR); #ifdef CONFIG_SAMA5_ADC_ANARCH /* Disable analog change: No analog change on channel switching: DIFF0, * GAIN0 and OFF0 are used for all channels. */ regval |= ADC_MR_ANACH; #else /* Enable analog change: Allows different analog settings for each * channel using the ADC_CGR and ADC_COR Registers. */ regval &= ~ADC_MR_ANACH; #endif sam_adc_putreg(priv, SAM_ADC_MR, regval); } /**************************************************************************** * Name: sam_adc_sequencer * * Description: * Configure and enable the sequencer * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_SEQUENCER static void sam_adc_setseqr(int chan, uint32_t *seqr1, uint32_t *seqr2, int seq) { if (seq > 8) { *seqr2 |= ADC_SEQR2_USCH(seq, chan); } else { *seqr1 |= ADC_SEQR1_USCH(seq, chan); } avdbg("chan=%d seqr1=%08x seqr2=%08x seq=%d\n", chan, *seqr1, *seqr2, seq); } #endif static void sam_adc_sequencer(struct sam_adc_s *priv) { #ifdef CONFIG_SAMA5_ADC_SEQUENCER uint32_t regval; uint32_t seqr1; uint32_t seqr2; int seq; avdbg("Setup sequencer\n"); /* Set user configured channel sequence */ seqr1 = 0; seqr2 = 0; seq = 1; #ifdef CONFIG_SAMA5_ADC_CHAN0 sam_adc_setseqr(0, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN1 sam_adc_setseqr(1, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN2 sam_adc_setseqr(2, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN3 sam_adc_setseqr(3, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN4 sam_adc_setseqr(4, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN5 sam_adc_setseqr(5, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN6 sam_adc_setseqr(6, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN7 sam_adc_setseqr(7, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN8 sam_adc_setseqr(8, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN9 sam_adc_setseqr(9, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN10 sam_adc_setseqr(10, &seqr1, &seqr2, seq++); #endif #ifdef CONFIG_SAMA5_ADC_CHAN11 sam_adc_setseqr(11, &seqr1, &seqr2, seq++); #endif /* If not all channels are used, then program an unused channel number * into the remaining slots. If we don't do this, we will get multiple * samples for the enabled channels. */ #ifdef SAMA5_ADC_UNUSED for (; seq < 9; seq++) { seqr1 |= ADC_SEQR1_USCH(seq, SAMA5_ADC_UNUSED); } for (; seq < 12; seq++) { seqr2 |= ADC_SEQR2_USCH(seq, SAMA5_ADC_UNUSED); } #endif /* Save the new values to the SEQR1 and SEQR2 registers */ sam_adc_putreg(priv, SAM_ADC_SEQR1, seqr1); sam_adc_putreg(priv, SAM_ADC_SEQR2, seqr2); /* Enable sequencer. Any channel that is not enabled will be skipped by * the sequencer (that is why we programmed the unused channels above. */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval |= ADC_MR_USEQ; sam_adc_putreg(priv, SAM_ADC_MR, regval); #else uint32_t regval; avdbg("Disable sequencer\n"); /* Disable the sequencer */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~ADC_MR_USEQ; sam_adc_putreg(priv, SAM_ADC_MR, regval); #endif } /**************************************************************************** * Name: sam_adc_channels * * Description: * Configure and enable the channels * ****************************************************************************/ static void sam_adc_channels(struct sam_adc_s *priv) { uint32_t regval; avdbg("Entry\n"); /* Enable channels. */ regval = 0; #ifdef CONFIG_SAMA5_ADC_CHAN0 regval |= ADC_CH0; #endif #ifdef CONFIG_SAMA5_ADC_CHAN1 regval |= ADC_CH1; #endif #ifdef CONFIG_SAMA5_ADC_CHAN2 regval |= ADC_CH2; #endif #ifdef CONFIG_SAMA5_ADC_CHAN3 regval |= ADC_CH3; #endif #ifdef CONFIG_SAMA5_ADC_CHAN4 regval |= ADC_CH4; #endif #ifdef CONFIG_SAMA5_ADC_CHAN5 regval |= ADC_CH5; #endif #ifdef CONFIG_SAMA5_ADC_CHAN6 regval |= ADC_CH6; #endif #ifdef CONFIG_SAMA5_ADC_CHAN7 regval |= ADC_CH7; #endif #ifdef CONFIG_SAMA5_ADC_CHAN8 regval |= ADC_CH8; #endif #ifdef CONFIG_SAMA5_ADC_CHAN9 regval |= ADC_CH9; #endif #ifdef CONFIG_SAMA5_ADC_CHAN10 regval |= ADC_CH10; #endif #ifdef CONFIG_SAMA5_ADC_CHAN11 regval |= ADC_CH11; #endif sam_adc_putreg(priv, SAM_ADC_CHER, regval); } #endif /* SAMA5_ADC_HAVE_CHANNELS */ /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: sam_adc_initialize * * Description: * Initialize the adc * * Returned Value: * Valid can device structure reference on succcess; a NULL on failure * ****************************************************************************/ struct adc_dev_s *sam_adc_initialize(void) { struct sam_adc_s *priv = &g_adcpriv; uint32_t regval; uint32_t mck; int ret; /* Have we already been initialzed? If yes, than just hand out the * interface one more time. */ if (!priv->initialized) { avdbg("Initializing...\n"); /* Disable ADC peripheral clock */ sam_adc_disableclk(); /* Configure ADC pins */ #ifdef CONFIG_SAMA5_ADC_CHAN0 sam_configpio(PIO_ADC_AD0); #endif #ifdef CONFIG_SAMA5_ADC_CHAN1 sam_configpio(PIO_ADC_AD1); #endif #ifdef CONFIG_SAMA5_ADC_CHAN2 sam_configpio(PIO_ADC_AD2); #endif #ifdef CONFIG_SAMA5_ADC_CHAN3 sam_configpio(PIO_ADC_AD3); #endif #ifdef CONFIG_SAMA5_ADC_CHAN4 sam_configpio(PIO_ADC_AD4); #endif #ifdef CONFIG_SAMA5_ADC_CHAN5 sam_configpio(PIO_ADC_AD5); #endif #ifdef CONFIG_SAMA5_ADC_CHAN6 sam_configpio(PIO_ADC_AD6); #endif #ifdef CONFIG_SAMA5_ADC_CHAN7 sam_configpio(PIO_ADC_AD7); #endif #ifdef CONFIG_SAMA5_ADC_CHAN8 sam_configpio(PIO_ADC_AD8); #endif #ifdef CONFIG_SAMA5_ADC_CHAN9 sam_configpio(PIO_ADC_AD9); #endif #ifdef CONFIG_SAMA5_ADC_CHAN10 sam_configpio(PIO_ADC_AD10); #endif #ifdef CONFIG_SAMA5_ADC_CHAN11 sam_configpio(PIO_ADC_AD11); #endif #ifdef CONFIG_SAMA5_ADC_ADTRG sam_configpio(PIO_ADC_TRG); #endif /* Initialize the public ADC device data structure */ g_adcdev.ad_ops = &g_adcops; g_adcdev.ad_priv = priv; /* Initialize the private ADC device data structure */ sem_init(&priv->exclsem, 0, 1); priv->dev = &g_adcdev; #ifdef CONFIG_SAMA5_ADC_DMA /* Allocate a DMA channel from DMAC1 */ priv->dma = sam_dmachannel(1, DMA_FLAGS); DEBUGASSERT(priv->dma); #endif /* Determine the maximum ADC peripheral clock frequency */ mck = BOARD_MCK_FREQUENCY; if (mck <= SAM_ADC_MAXPERCLK) { priv->frequency = mck; regval = PMC_PCR_DIV1; } else if ((mck >> 1) <= SAM_ADC_MAXPERCLK) { priv->frequency = (mck >> 1); regval = PMC_PCR_DIV2; } else if ((mck >> 2) <= SAM_ADC_MAXPERCLK) { priv->frequency = (mck >> 2); regval = PMC_PCR_DIV4; } else if ((mck >> 3) <= SAM_ADC_MAXPERCLK) { priv->frequency = (mck >> 3); regval = PMC_PCR_DIV8; } else { adbg("ERROR: Cannot realize ADC input frequency\n"); return NULL; } /* Set the maximum ADC peripheral clock frequency */ regval |= PMC_PCR_PID(SAM_PID_ADC) | PMC_PCR_CMD | PMC_PCR_EN; sam_adc_putreg(priv, SAM_PMC_PCR, regval); /* Enable the ADC peripheral clock*/ sam_adc_enableclk(); /* Reset the ADC controller */ sam_adc_putreg(priv, SAM_ADC_CR, ADC_CR_SWRST); /* Reset Mode Register */ sam_adc_putreg(priv, SAM_ADC_MR, 0); /* Set the MCK clock prescaler: ADCClock = MCK / ((PRESCAL+1)*2) */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~ADC_MR_PRESCAL_MASK; regval |= ADC_MR_PRESCAL(BOARD_ADC_PRESCAL); sam_adc_putreg(priv, SAM_ADC_MR, regval); /* Formula: * Startup Time = startup value / ADCClock * Transfer Time = (TRANSFER * 2 + 3) / ADCClock * Tracking Time = (TRACKTIM + 1) / ADCClock * Settling Time = settling value / ADCClock * For example, ADC clock = 6MHz (166.7 ns) * Startup time = 512 / 6MHz = 85.3 us * Transfer Time = (1 * 2 + 3) / 6MHz = 833.3 ns * Tracking Time = (0 + 1) / 6MHz = 166.7 ns * Settling Time = 3 / 6MHz = 500 ns */ /* Set ADC timing */ regval = sam_adc_getreg(priv, SAM_ADC_MR); regval &= ~(ADC_MR_STARTUP_MASK | ADC_MR_TRACKTIM_MASK | ADC_MR_SETTLING_MASK); regval |= (ADC_MR_STARTUP_512 | ADC_MR_TRACKTIM(0) | ADC_MR_SETTLING_17); sam_adc_putreg(priv, SAM_ADC_MR, regval); /* Attach the ADC interrupt */ ret = irq_attach(SAM_IRQ_ADC, sam_adc_interrupt); if (ret < 0) { adbg("ERROR: Failed to attach IRQ %d: %d\n", SAM_IRQ_ADC, ret); return NULL; } /* Disable all ADC interrupts at the source */ sam_adc_putreg(priv, SAM_ADC_IDR, ADC_INT_ALL); /* Enable the ADC interrupt at the AIC */ up_enable_irq(SAM_IRQ_ADC); /* Now we are initialized */ priv->initialized = true; } /* Return a pointer to the device structure */ avdbg("Returning %p\n", &g_adcdev); return &g_adcdev; } /**************************************************************************** * Name: sam_adc_lock * * Description: * Get exclusive access to the ADC interface * ****************************************************************************/ void sam_adc_lock(FAR struct sam_adc_s *priv) { int ret; avdbg("Locking\n"); do { ret = sem_wait(&priv->exclsem); /* This should only fail if the wait was canceled by an signal * (and the worker thread will receive a lot of signals). */ DEBUGASSERT(ret == OK || errno == EINTR); } while (ret < 0); } /**************************************************************************** * Name: sam_adc_unlock * * Description: * Relinquish the lock on the ADC interface * ****************************************************************************/ void sam_adc_unlock(FAR struct sam_adc_s *priv) { avdbg("Unlocking\n"); sem_post(&priv->exclsem); } /**************************************************************************** * Name: sam_adc_getreg * * Description: * Read any 32-bit register using an absolute address. * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_REGDEBUG uint32_t sam_adc_getreg(struct sam_adc_s *priv, uintptr_t address) { uint32_t regval = getreg32(address); if (sam_adc_checkreg(priv, false, regval, address)) { lldbg("%08x->%08x\n", address, regval); } return regval; } #endif /**************************************************************************** * Name: sam_adc_putreg * * Description: * Write to any 32-bit register using an absolute address. * ****************************************************************************/ #ifdef CONFIG_SAMA5_ADC_REGDEBUG void sam_adc_putreg(struct sam_adc_s *priv, uintptr_t address, uint32_t regval) { if (sam_adc_checkreg(priv, true, regval, address)) { lldbg("%08x<-%08x\n", address, regval); } putreg32(regval, address); } #endif #endif /* CONFIG_SAMA5_ADC */