2148 lines
59 KiB
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 */
|