From bb0f68b35e46c33271f9e011959ae96f1dbbf8f1 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Thu, 3 Oct 2013 10:06:14 -0600 Subject: SAMA5 ADC and touchscreen: Drivers are code complete and ready for test --- nuttx/arch/arm/src/sama5/chip/sam_adc.h | 3 + nuttx/arch/arm/src/sama5/sam_adc.c | 827 ++++++++++++++++++++++++++++---- 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; @@ -509,42 +772,6 @@ ignored: sem_adc_unlock(priv->adc); } -/**************************************************************************** - * 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 * @@ -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,11 +1020,357 @@ 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 * @@ -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; -- cgit v1.2.3