summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Nutt <gnutt@nuttx.org>2013-10-03 10:06:14 -0600
committerGregory Nutt <gnutt@nuttx.org>2013-10-03 10:06:14 -0600
commitbb0f68b35e46c33271f9e011959ae96f1dbbf8f1 (patch)
treedae5287a6fcf717e7e5ce448d48d9d665c4b1bb2
parente96afc264a3b53bfc067d76957d442e9156eb20d (diff)
downloadnuttx-bb0f68b35e46c33271f9e011959ae96f1dbbf8f1.tar.gz
nuttx-bb0f68b35e46c33271f9e011959ae96f1dbbf8f1.tar.bz2
nuttx-bb0f68b35e46c33271f9e011959ae96f1dbbf8f1.zip
SAMA5 ADC and touchscreen: Drivers are code complete and ready for test
-rw-r--r--nuttx/arch/arm/src/sama5/chip/sam_adc.h3
-rw-r--r--nuttx/arch/arm/src/sama5/sam_adc.c827
2 files changed, 726 insertions, 104 deletions
diff --git a/nuttx/arch/arm/src/sama5/chip/sam_adc.h b/nuttx/arch/arm/src/sama5/chip/sam_adc.h
index da94e51e0..154c1ea96 100644
--- a/nuttx/arch/arm/src/sama5/chip/sam_adc.h
+++ b/nuttx/arch/arm/src/sama5/chip/sam_adc.h
@@ -308,6 +308,8 @@
#define ADC_INT_NOPEN (1 << 30) /* Bit 30: No Pen Contact Interrupt */
#define ADC_SR_PENS (1 << 31) /* Bit 31: Pen detect Status (SR only) */
+#define ADC_INT_ALL (0xe7f00fff)
+
/* Overrun Status Register */
#define ADC_OVER_OVRE(n) (1 << (n))
@@ -396,6 +398,7 @@
#define ADC_COR_OFF9 (1 << 9) /* Bit 9: Offset for channel 9 */
#define ADC_COR_OFF10 (1 << 10) /* Bit 10: Offset for channel 10 */
#define ADC_COR_OFF11 (1 << 11) /* Bit 11: Offset for channel 11 */
+
#define ADC_COR_DIFF(n) (1 << ((n)+16))
#define ADC_COR_DIFF0 (1 << 16) /* Bit 16: Offset for channel 0 */
#define ADC_COR_DIFF1 (1 << 17) /* Bit 17: Offset for channel 1 */
diff --git a/nuttx/arch/arm/src/sama5/sam_adc.c b/nuttx/arch/arm/src/sama5/sam_adc.c
index b9c52ff3a..8b4d408a0 100644
--- a/nuttx/arch/arm/src/sama5/sam_adc.c
+++ b/nuttx/arch/arm/src/sama5/sam_adc.c
@@ -77,6 +77,76 @@
/****************************************************************************
* 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
@@ -131,19 +201,96 @@
#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)
+ (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 {1, 2, 3, 4} 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 zet.
+ */
+
+#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_TOUCHSCREEN
# ifdef CONFIG_SAMA5_TSD_5WIRE
-# SAMA5_ADC_CHALL (ADC_CHALL & ~TSD_5WIRE_ALL)
+# SAMA5_ADC_CHALL (ADC_CHALL & ~TSD_5WIRE_ALL)
# else
-# SAMA5_ADC_CHALL (ADC_CHALL & ~TSD_4WIRE_ALL)
+# SAMA5_ADC_CHALL (ADC_CHALL & ~TSD_4WIRE_ALL)
# endif
#else
-# SAMA5_ADC_CHALL ADC_CHALL
+# SAMA5_ADC_CHALL ADC_CHALL
#endif
/* DMA configuration flags */
@@ -161,29 +308,29 @@
/* Pick an unused channel number */
-#if defined(CONFIG_SAMA5_ADC_CHAN0)
+#if !defined(CONFIG_SAMA5_ADC_CHAN0)
# define SAMA5_ADC_UNUSED 0
-#elif defined(CONFIG_SAMA5_ADC_CHAN1)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN1)
# define SAMA5_ADC_UNUSED 1
-#elif defined(CONFIG_SAMA5_ADC_CHAN2)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN2)
# define SAMA5_ADC_UNUSED 2
-#elif defined(CONFIG_SAMA5_ADC_CHAN3)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN3)
# define SAMA5_ADC_UNUSED 3
-#elif defined(CONFIG_SAMA5_ADC_CHAN4)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN4)
# define SAMA5_ADC_UNUSED 4
-#elif defined(CONFIG_SAMA5_ADC_CHAN5)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN5)
# define SAMA5_ADC_UNUSED 5
-#elif defined(CONFIG_SAMA5_ADC_CHAN6)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN6)
# define SAMA5_ADC_UNUSED 6
-#elif defined(CONFIG_SAMA5_ADC_CHAN7)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN7)
# define SAMA5_ADC_UNUSED 7
-#elif defined(CONFIG_SAMA5_ADC_CHAN8)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN8)
# define SAMA5_ADC_UNUSED 8
-#elif defined(CONFIG_SAMA5_ADC_CHAN9)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN9)
# define SAMA5_ADC_UNUSED 9
-#elif defined(CONFIG_SAMA5_ADC_CHAN10)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN10)
# define SAMA5_ADC_UNUSED 10
-#elif defined(CONFIG_SAMA5_ADC_CHAN11)
+#elif !defined(CONFIG_SAMA5_ADC_CHAN11)
# define SAMA5_ADC_UNUSED 11
#else
# undef SAMA5_ADC_UNUSED
@@ -215,15 +362,27 @@
struct sam_adc_s
{
+ sem_t exclsem; /* Supports exclusive access to the ADC interface */
+
#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 */
+#endif
struct adc_dev_s dev; /* The external via of the ADC device */
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
- struct work_s work; /* Supports the interrupt handling "bottom half" */
+
+ /* DMA sample data buffer */
+
+#ifdef CONFIG_SAMA5_ADC_DMA
+ uint32_t evenbuf[SAMA5_NCHANNELS];
+ uint32_t oddbuf[SAMA5_NCHANNELS];
#endif
- sem_t exclsem; /* Supports exclusive access to the ADC interface */
+#endif /* SAMA5_ADC_HAVE_CHANNELS */
/* Debug stuff */
@@ -248,6 +407,7 @@ static bool sam_adc_checkreg(struct sam_adc_s *priv, bool wr,
/* DMA helper functions */
#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)
@@ -256,8 +416,7 @@ static int sam_adc_dmasetup(struct sam_adc_s *priv, FAR uint8_t *buffer,
/* ADC interrupt handling */
#ifdef SAMA5_ADC_HAVE_CHANNELS
-static void sam_tsd_bottomhalf(void *arg);
-static int sam_tsd_schedule(struct sam_tsd_s *priv, uint32_t pending);
+static void sam_adc_endconversion(void *arg);
#endif
static int sam_adc_interrupt(int irq, void *context);
@@ -272,8 +431,13 @@ static int sam_adc_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg);
/* Initialization/Configuration */
-static void sam_adc_sequencer(struct sam_tsd_s *priv);
-static void sam_adc_channels(truct sam_tsd_s *priv);
+static void 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(truct sam_adc_s *priv);
#endif
/****************************************************************************
@@ -373,11 +537,83 @@ static bool sam_adc_checkreg(struct sam_adc_s *priv, bool wr,
/****************************************************************************
* 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 sample;
+ int chan;
+ int i;
+
+ ASSERT(priv != NULL && !priv->ready);
+
+ /* Select the completed DMA buffer */
+
+ buffer = priv->odd ? priv->evenbuf : priv->oddbuf;
+
+ /* 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_NCHANNELS * sizeof(uint32_t));
+
+ /* Process each sample */
+
+ for (i = 0; i < SAMA5_NCHANNELS; i++, buffer++)
+ {
+ /* Get the sample and the channel number */
+
+ chan = (int)((*buffer & ADC_LCDR_CHANB_MASK) >> ADC_LCDR_CHANB_SHIFT);
+ sample = (uint16_t)((*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_dmacallback
*
* Description:
- * Called when HSMCI DMA completes
+ * Called when one ADC DMA sequence completes. This function defers
+ * processing of the samples to sam_adc_dmadone which runs on the worker
+ * thread.
*
****************************************************************************/
@@ -385,7 +621,34 @@ static bool sam_adc_checkreg(struct sam_adc_s *priv, bool wr,
static void sam_adc_dmacallback(DMA_HANDLE handle, void *arg, int result)
{
struct sam_dev_s *priv = (struct sam_dev_s *)arg;
-#warning Missing logic
+
+ /* Check of the bottom half is keeping up with us */
+
+ if (priv->ready)
+ {
+ /* Toggle to the next buffer. Note that the toggle only occurs if
+ * the bottom half is ready to accept more data. Otherwise, we
+ * will get a data overrun and just re-use the last buffer.
+ */
+
+ priv->odd = !priv->odd;
+ priv->ready = false;
+
+ /* Transfer processing to the bottom half */
+
+ DEBUGASSERT(priv->work.worker == NULL);
+ ret = work_queue(HPWORK, &priv->work, sam_adc_dmadone, priv, 0);
+ if (ret != 0)
+ {
+ illdbg("ERROR: Failed to queue work: %d\n", ret);
+ }
+ }
+
+ /* Restart the DMA conversion using the next buffer */
+
+ sam_adc_dmasetup(priv->dma,
+ priv->odd ? (void *)priv->oddbuf , (void *)priv->evenbuf
+ SAMA5_NCHANNELS);
}
#endif
@@ -447,14 +710,14 @@ static int sam_adc_dmasetup(FAR struct sam_adc_s *priv, FAR uint8_t *buffer,
* ADC interrupt handling
****************************************************************************/
/****************************************************************************
- * Name: sam_tsd_bottomhalf
+ * Name: sam_adc_endconversion
*
* Description:
* This function executes on the worker thread. It is scheduled by
- * sam_tsd_interrupt whenever any enabled end-of-conversion event occurs.
- * All EOC interrupts are disabled when this function runs. sam_tsd_bottomhalf
- * will re-enable EOC interrupts when it completes processing all pending
- * EOC events.
+ * 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 *)
@@ -464,9 +727,9 @@ static int sam_adc_dmasetup(FAR struct sam_adc_s *priv, FAR uint8_t *buffer,
*
****************************************************************************/
-static void sam_tsd_bottomhalf(void *arg)
+static void sam_adc_endconversion(void *arg)
{
- struct sam_tsd_s *priv = (struct sam_tsd_s *)arg;
+ struct sam_adc_s *priv = (struct sam_adc_s *)arg;
uint32_t regval;
uint32_t pending;
int chan;
@@ -510,42 +773,6 @@ ignored:
}
/****************************************************************************
- * Name: sam_tsd_schedule
- ****************************************************************************/
-
-static int sam_tsd_schedule(struct sam_tsd_s *priv, uint32_t pending)
-{
- int ret;
-
- /* Disable further touchscreen interrupts. Touchscreen interrupts will be
- * re-enabled after the worker thread executes.
- */
-
- sam_adc_putreg32(priv->adc, 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 touchscreen ADC 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_tsd_bottomhalf, priv, 0);
- if (ret != 0)
- {
- illdbg("Failed to queue work: %d\n", ret);
- }
-
- return OK;
-}
-#endif /* SAMA5_ADC_HAVE_CHANNELS */
-
-/****************************************************************************
* Name: sam_adc_interrupt
*
* Description:
@@ -560,6 +787,7 @@ static int sam_adc_interrupt(int irq, void *context)
uint32_t imr;
uint32_t pending;
uint32_t regval;
+ int ret;
/* Get the set of unmasked, pending ADC interrupts */
@@ -574,7 +802,7 @@ static int sam_adc_interrupt(int irq, void *context)
{
/* Let the touchscreen handle its interrupts */
- sam_tsd_interrupt(pending);
+ sam_adc_interrupt(pending);
pending &= ~ADC_TSD_INTS;
}
#endif
@@ -584,14 +812,28 @@ static int sam_adc_interrupt(int irq, void *context)
if ((pending & ADC_INT_EOCALL) != 0)
{
- /* Schedule sampling to occur by the interrupt bottom half on the
- * worker thread.
+ /* Disable further end-of-conversion interrupts. End-of-conversion
+ * interrupts will be re-enabled after the worker thread executes.
+ */
+
+ sam_adc_putreg32(priv->adc, 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.
*/
- ret = sam_tsd_schedule(priv, pending);
- if (ret < 0)
+ DEBUGASSERT(priv->work.worker == NULL);
+ ret = work_queue(HPWORK, &priv->work, sam_adc_endconversion, priv, 0);
+ if (ret != 0)
{
- idbg("ERROR: sam_tsd_schedule failed: %d\n", ret);
+ illdbg("ERROR: Failed to queue work: %d\n", ret);
}
pending &= ~ADC_INT_EOCALL;
@@ -633,13 +875,18 @@ static void sam_adc_reset(struct adc_dev_s *dev)
sam_adc_putreg(priv, SAM_ADC_CHDR, SAMA5_ADC_CHALL);
- /* Disable the sequencer */
+ /* Disable the sequencer and analog change */
regval = sam_adc_getreg(priv, SAM_ADC_MR);
- regval &= ~ADC_MR_USEQ;
+ regval &= ~(ADC_MR_USEQ | ADC_MR_ANACH);
sam_adc_putreg(priv, SAM_ADC_MR, regval);
- /* Gain, offset, autocal, trigger mode, etc */
+ /* Reset gain, offset, differential modes */
+
+ sam_adc_putreg(priv, SAM_CGR_MR, 0);
+ sam_adc_putreg(priv, SAM_COR_MR, 0);
+
+ /* trigger mode, etc */
#warning Missing logic
}
@@ -659,6 +906,14 @@ static int sam_adc_setup(struct adc_dev_s *dev)
struct sam_adc_s *priv = (struct sam_adc_s *)dev->ad_priv;
int ret;
+ /* 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_puttreg(priv, SAM_ADC_EMR, regval);
+
/* Enable (or disable) the sequencer */
sam_adc_sequencer(priv);
@@ -667,38 +922,38 @@ static int sam_adc_setup(struct adc_dev_s *dev)
sam_adc_channels(priv);
- /* Set gain and offset (only single ended mode used here) */
-#warning Missing logic
+ /* Enable/disable analog change. This feature permits different settings
+ * per channel.
+ */
- /* Set Auto Calibration Mode*/
-#warning Missing logic
+ 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
/* Configure for DMA transfer */
-#warning Missing logic
+ priv->odd = false;
+ sam_adc_dmasetup(priv->dma, (void *)priv->evenbuf, SAMA5_NCHANNELS);
#else
/* Enable end-of-conversion interrupts for all enabled channels. */
-#warning Missing logic
+ sam_adc_putreg(priv, SAM_ADC_IER, SAMA5_CHAN_ENABLE);
#endif
- /* 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 ret;
- }
-
- /* Enable the ADC interrupt */
-
- up_enable_irq(SAM_IRQ_ADC);
-
- /* Configure trigger mode and start convention */
-#warning Missing logic
+ /* Configure trigger mode and start conversion */
+ sam_adc_trigger(priv);
return OK;
}
@@ -765,12 +1020,358 @@ 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)
{
- /* No ioctl commands supported */
+ /* No ioctl commands supported:
+ *
+ * REVISIT: Need to implement a ioctl to support software triggering
+ */
+
+#ifdef CONFIG_SAMA5_ADC_SWTRIG
+# error Need an ioctl to perform software triggering
+#endif
return -ENOTTY;
}
/****************************************************************************
+ * Initialization/Configuration
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: sam_adc_trigger
+ *
+ * Description:
+ * Configure trigger mode and start conversion.
+ *
+ ****************************************************************************/
+
+static void sam_adc_trigger(struct sam_adc_s *priv)
+{
+ uint32_t reggal;
+
+#if defined(CONFIG_SAMA5_ADC_SWTRIG)
+ /* Configure the software trigger */
+
+ regval = sam_adc_getreg(priv->dev, SAM_ADC_MR);
+ regval &= ~ADC_MR_TRGSEL_MASK;
+ sam_adc_putreg(priv->dev, 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_NO_TRIGGER;
+ sam_adc_putreg(priv, SAM_ADC_TRGR, regval);
+
+#elif defined(CONFIG_SAMA5_ADC_ADTRG)
+ /* Configure the trigger via the external ADTRG signal */
+
+ regval = sam_adc_getreg(priv->dev, SAM_ADC_MR);
+ regval &= ~ADC_MR_TRGSEL_MASK;
+ regval |= ADC_MR_TRGSEL_ADC_ADTRIG;
+ sam_adc_putreg(priv->dev, 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)
+ /* 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->dev, 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_TIOA0TRIG)
+ regval |= ADC_MR_TRGSEL_TIOA2; /* Timer/counter 0 channel 2 output A */
+#else
+# error Timer/counter for trigger not defined
+#endif
+
+ regval |= ADC_MR_TRGSEL_ADC_TRIG1;
+ sam_adc_putreg(priv->dev, 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
+}
+
+/****************************************************************************
+ * 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;
+
+ /* 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;
+
+#ifdef CONFIG_SAMA5_ADC_ANARCH
+ /* Set the offset for each enabled channel. This xenters 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 differentila 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)
+{
+ uint32_t regval;
+
+#ifdef CONFIG_SAMA5_ADC_ANARCH
+ /* 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
+ /* 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;
+
+ /* Enable/disable the analog change feature */
+
+ regval = sam_adc_getreg(priv->dev, 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->dev, SAM_ADC_MR, regval);
+}
+
+/****************************************************************************
* Name: sam_adc_sequencer
*
* Description:
@@ -792,7 +1393,7 @@ static void sam_adc_setseqr(int chan, uint32_t *seqr1, uint32_t *seqr2, int seq)
}
#endif
-static void sam_adc_sequencer(truct sam_tsd_s *priv)
+static void sam_adc_sequencer(truct sam_adc_s *priv)
{
#ifdef CONFIG_SAMA5_ADC_SEQUENCER
uint32_t seqr1;
@@ -904,7 +1505,7 @@ static void sam_adc_sequencer(truct sam_tsd_s *priv)
*
****************************************************************************/
-static void sam_adc_channels(truct sam_tsd_s *priv)
+static void sam_adc_channels(truct sam_adc_s *priv)
{
uint32_t regval;
@@ -1029,7 +1630,8 @@ struct adc_dev_s *sam_adc_initialize(void)
#ifdef CONFIG_SAMA5_ADC_CHAN11
sam_configpio(PIO_ADC_AD11);
#endif
-#if 0
+
+#ifdef CONFIG_SAMA5_ADC_ADTRG
sam_configpio(PIO_ADC_TRG);
#endif
@@ -1087,6 +1689,23 @@ struct adc_dev_s *sam_adc_initialize(void)
regval |= ADC_MR_STARTUP_SUT512 | ADC_MR_TRACKTIM(0) | ADC_MR_SETTLING_AST17;
sam_adc_puttreg(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 ret;
+ }
+
+ /* 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);
+
/* Return a pointer to the device structure */
return &g_adcdev;