nuttx/arch/arm/src/sama5/sam_adc.c

2148 lines
59 KiB
C

/************************************************************************************
* arch/arm/src/sama5/sam_adc.c
*
* Copyright (C) 2013 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* 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 <nuttx/config.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <semaphore.h>
#include <errno.h>
#include <debug.h>
#include <arch/board/board.h>
#include <nuttx/arch.h>
#include <nuttx/wqueue.h>
#include <nuttx/analog/adc.h>
#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)
/* Clocking */
#if BOARD_MCK_FREQUENCY <= SAM_ADC_MAXPERCLK
# define ADC_FREQUENCY BOARD_MCK_FREQUENCY
# define ADC_PCR_DIV PMC_PCR_DIV1
#elif (BOARD_MCK_FREQUENCY >> 1) <= SAM_ADC_MAXPERCLK
# define ADC_FREQUENCY (BOARD_MCK_FREQUENCY >> 1)
# define ADC_PCR_DIV PMC_PCR_DIV2
#elif (BOARD_MCK_FREQUENCY >> 2) <= SAM_ADC_MAXPERCLK
# define ADC_FREQUENCY (BOARD_MCK_FREQUENCY >> 2)
# define ADC_PCR_DIV PMC_PCR_DIV4
#elif (BOARD_MCK_FREQUENCY >> 3) <= SAM_ADC_MAXPERCLK
# define ADC_FREQUENCY (BOARD_MCK_FREQUENCY >> 3)
# define ADC_PCR_DIV PMC_PCR_DIV8
#else
# error Cannot realize ADC input frequency
#endif
/****************************************************************************
* 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 */
#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.
*
****************************************************************************/
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));
}
}
/****************************************************************************
* 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)
{
struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv;
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)
{
struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv;
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)
{
struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv;
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;
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
/* Set the maximum ADC peripheral clock frequency */
regval = PMC_PCR_PID(SAM_PID_ADC) | PMC_PCR_CMD | ADC_PCR_DIV | 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 */