From abdd0c637cce2669461705886911a2f6f3057fec Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Fri, 8 Nov 2013 16:09:20 -0600 Subject: [PATCH] SAMA5 I2S: Improved I2S buffering --- arch/arm/src/sama5/Kconfig | 9 + arch/arm/src/sama5/sam_ssc.c | 882 +++++++++++++++++++++++++++++++---- include/nuttx/audio/i2s.h | 64 ++- 3 files changed, 855 insertions(+), 100 deletions(-) diff --git a/arch/arm/src/sama5/Kconfig b/arch/arm/src/sama5/Kconfig index 50564476a9..4db8548047 100644 --- a/arch/arm/src/sama5/Kconfig +++ b/arch/arm/src/sama5/Kconfig @@ -1380,6 +1380,15 @@ endif # SAMA5_TWI0 || SAMA5_TWI1 || SAMA5_TWI2 if SAMA5_SSC0 || SAMA5_SSC1 menu "SSC Configuration" +config SAMA5_SSC_MAXINFLIGHT + int "SSC queue size" + default 16 + ---help--- + This is the total number of transfers, both RX and TX, that can be + enqueue before the caller is required to wait. This setting + determines the number certain queue data structures that will be + pre-allocated. + if SAMA5_SSC0 comment "SSC0 Configuration" diff --git a/arch/arm/src/sama5/sam_ssc.c b/arch/arm/src/sama5/sam_ssc.c index 601a8879ca..0ea0895945 100644 --- a/arch/arm/src/sama5/sam_ssc.c +++ b/arch/arm/src/sama5/sam_ssc.c @@ -48,11 +48,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include "up_internal.h" @@ -75,6 +77,14 @@ ****************************************************************************/ /* Configuration ************************************************************/ +#ifndef CONFIG_SCHED_WORKQUEUE +# error Work queue support is required (CONFIG_SCHED_WORKQUEUE) +#endif + +#ifndef SAMA5_SSC_MAXINFLIGHT +# define SAMA5_SSC_MAXINFLIGHT 16 +#endif + #if defined(CONFIG_SAMA5_SSC0) && !defined(CONFIG_SAMA5_DMAC0) # error CONFIG_SAMA5_DMAC0 required by SSC0 #endif @@ -170,15 +180,19 @@ #endif #ifdef CONFIG_DEBUG_I2S -# define i2sdbg lldbg +# define i2sdbg dbg +# define i2slldbg lldbg # ifdef CONFIG_DEBUG_VERBOSE -# define i2svdbg lldbg +# define i2svdbg dbg +# define i2sllvdbg lldbg # else # define i2svdbg(x...) # endif #else # define i2sdbg(x...) +# define i2slldbg(x...) # define i2svdbg(x...) +# define i2sllvdbg(x...) #endif #define DMA_INITIAL 0 @@ -192,6 +206,18 @@ /**************************************************************************** * Private Types ****************************************************************************/ +/* I2S buffer container */ + +struct sam_buffer_s +{ + struct sam_buffer_s *flink; /* Supports a singly linked list */ + i2s_callback_t callback; /* Function to call when the transfer completes */ + uint32_t timeout; /* The timeout value to use with DMA transfers */ + void *arg; /* The argument to be returned with the callback */ + void *buffer; /* I/O buffer */ + size_t nbytes; /* Number of valid bytes in the buffer */ + int result; /* The result of the transfer */ +}; /* The state of the one SSC peripheral */ @@ -210,20 +236,31 @@ struct sam_ssc_s uint16_t txout:2; /* Transmitter clock output. See SSC_CLKOUT_* definitions */ uint8_t datalen; /* Data width (2-32) */ uint8_t pid; /* Peripheral ID */ - sem_t dmawait; /* Used to wait for DMA completion */ uint32_t frequency; /* Target MCK frequency */ - WDOG_ID dmadog; /* Watchdog that handles DMA timeouts */ - int result; /* DMA result */ #ifdef SSC_HAVE_RX - uint32_t rxcmr; /* Value of CMR register for RX transfers */ DMA_HANDLE rxdma; /* SSC RX DMA handle */ + WDOG_ID rxdog; /* Watchdog that handles RX DMA timeouts */ + sq_queue_t rxpend; /* A queue of pending RX transfers */ + sq_queue_t rxdone; /* A queue of completed RX transfers */ + struct sam_buffer_s *rxact; /* The active RX transfer */ + struct work_s rxwork; /* Supports worker thread RX operations */ #endif #ifdef SSC_HAVE_TX - uint32_t txcmr; /* Value of CMR register for TX transfers */ DMA_HANDLE txdma; /* SSC TX DMA handle */ + WDOG_ID txdog; /* Watchdog that handles TX DMA timeouts */ + sq_queue_t txpend; /* A queue of pending TX transfers */ + sq_queue_t txdone; /* A queue of completed TX transfers */ + struct sam_buffer_s *txact; /* The active TX transfer */ + struct work_s txwork; /* Supports worker thread TX operations */ #endif + /* Pre-allocated pool of buffer containers */ + + sem_t bufsem; /* Buffer wait semaphore */ + struct sam_buffer_s *freelist; /* A list a free buffer containers */ + struct sam_buffer_s containers[SAMA5_SSC_MAXINFLIGHT]; + /* Debug stuff */ #ifdef CONFIG_SAMA5_SSC_REGDEBUG @@ -246,7 +283,7 @@ struct sam_ssc_s * Private Function Prototypes ****************************************************************************/ -/* Helpers */ +/* Register helpers */ #ifdef CONFIG_SAMA5_SSC_REGDEBUG static bool ssc_checkreg(struct sam_ssc_s *priv, bool wr, uint32_t value, @@ -267,17 +304,33 @@ static void ssc_dumpregs(struct sam_ssc_s *priv, const char *msg); # define ssc_dumpregs(priv,msg) #endif +/* Semaphore helpers */ + +static void ssc_exclsemtake(FAR struct sam_ssc_s *priv); +#define ssc_exclsemgive(priv) sem_post(&priv->exclsem) + +static void ssc_bufsemtake(FAR struct sam_ssc_s *priv); +#define ssc_bufsemgive(priv) sem_post(&priv->bufsem) + +/* Buffer container helpers */ + +static struct sam_buffer_s * + ssc_bfallocate(struct sam_ssc_s *priv); +static void ssc_bffree(struct sam_ssc_s *priv, + struct sam_buffer_s *bfcontainer); +static void ssc_bfinitialize(struct sam_ssc_s *priv); + /* DMA support */ #if defined(CONFIG_SAMA5_SSC_DMADEBUG) && defined(SSC_HAVE_RX) -# define ssc_rxdma_sample(s,i) sam_dmasample((s)->rxdma, &(s)->rxdmaregs[i]) +# define ssc_rxdma_sample(s,i) sam_dmasample((s)->rxdma, &(s)->rxdmaregs[i]) static void ssc_rxdma_sampleinit(struct sam_ssc_s *priv); static void ssc_rxdma_sampledone(struct sam_ssc_s *priv); #else -# define ssc_rxdma_sample(s,i) -# define ssc_rxdma_sampleinit(s) -# define ssc_rxdma_sampledone(s) +# define ssc_rxdma_sample(s,i) +# define ssc_rxdma_sampleinit(s) +# define ssc_rxdma_sampledone(s) #endif @@ -287,26 +340,36 @@ static void ssc_txdma_sampleinit(struct sam_ssc_s *priv); static void ssc_txdma_sampledone(struct sam_ssc_s *priv); #else -# define ssc_txdma_sample(s,i) -# define ssc_txdma_sampleinit(s) -# define ssc_txdma_sampledone(s) +# define ssc_txdma_sample(s,i) +# define ssc_txdma_sampleinit(s) +# define ssc_txdma_sampledone(s) #endif #ifdef SSC_HAVE_RX +static void ssc_rxdmatimeout(int argc, uint32_t arg); +static int ssc_rxdmasetup(FAR struct sam_ssc_s *priv); +static void ssc_rxworker(FAR void *arg); +static void ssc_rxschedule(FAR struct sam_ssc_s *priv, int result); static void ssc_rxcallback(DMA_HANDLE handle, void *arg, int result); #endif #ifdef SSC_HAVE_TX +static void ssc_txdmatimeout(int argc, uint32_t arg); +static int ssc_txdmasetup(FAR struct sam_ssc_s *priv); +static void ssc_txworker(FAR void *arg); +static void ssc_txschedule(FAR struct sam_ssc_s *priv, int result); static void ssc_txcallback(DMA_HANDLE handle, void *arg, int result); #endif /* I2S methods */ static uint32_t ssc_frequency(FAR struct i2s_dev_s *dev, uint32_t frequency); -static void ssc_send(struct i2s_dev_s *dev, - const void *buffer, size_t nbytes); -static void ssc_receive(struct i2s_dev_s *dev, void *buffer, - size_t nbytes); +static int ssc_send(FAR struct i2s_dev_s *dev, FAR const void *buffer, + size_t nbytes, i2s_callback_t callback, FAR void *arg, + uint32_t timeout); +static int ssc_receive(FAR struct i2s_dev_s *dev, FAR void *buffer, + size_t nbytes, i2s_callback_t callback, FAR void *arg, + uint32_t timeout); /* Initialization */ @@ -494,6 +557,179 @@ static void ssc_dumpregs(struct sam_ssc_s *priv, const char *msg) } #endif + +/**************************************************************************** + * Name: ssc_exclsemtake + * + * Description: + * Take the exclusive access semaphore handling any exceptional conditions + * + * Input Parameters: + * priv - A reference to the SSC peripheral state + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void ssc_exclsemtake(FAR struct sam_ssc_s *priv) +{ + int ret; + + /* Wait until we successfully get the semaphore. EINTR is the only + * expected 'failure' (meaning that the wait for the semaphore was + * interrupted by a signal. + */ + + do + { + ret = sem_wait(&priv->exclsem); + DEBUGASSERT(ret == 0 || errno == EINTR); + } + while (ret < 0); +} + +/**************************************************************************** + * Name: ssc_exclsemtake + * + * Description: + * Take the buffer semaphore handling any exceptional conditions + * + * Input Parameters: + * priv - A reference to the SSC peripheral state + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void ssc_bufsemtake(FAR struct sam_ssc_s *priv) +{ + int ret; + + /* Wait until we successfully get the semaphore. EINTR is the only + * expected 'failure' (meaning that the wait for the semaphore was + * interrupted by a signal. + */ + + do + { + ret = sem_wait(&priv->bufsem); + DEBUGASSERT(ret == 0 || errno == EINTR); + } + while (ret < 0); +} + +/**************************************************************************** + * Name: ssc_bfallocate + * + * Description: + * Allocate a buffer container by removing the one at the head of the + * free list + * + * Input Parameters: + * priv - SSC state instance + * + * Returned Value: + * A non-NULL pointer to the allocate buffer container on success; NULL if + * there are no available buffer containers. + * + * Assumptions: + * The caller does NOT have exclusive acces to the SSC state structure. + * That would result in a deadlock! + * + ****************************************************************************/ + +static struct sam_buffer_s *ssc_bfallocate(struct sam_ssc_s *priv) +{ + struct sam_buffer_s *bfcontainer; + irqstate_t flags; + + /* Set aside a buffer container. By doing this, we guarantee that we will + * have at least one free buffer container. + */ + + ssc_bufsemtake(priv); + + /* Get the buffer from the head of the free list */ + + flags = irqsave(); + bfcontainer = priv->freelist; + ASSERT(bfcontainer); + + /* Unlink the buffer from the freelist */ + + priv->freelist = bfcontainer->flink; + irqrestore(flags); + return bfcontainer; +} + +/**************************************************************************** + * Name: ssc_bffree + * + * Description: + * Free buffer container by adding it to the head of the free list + * + * Input Parameters: + * priv - SSC state instance + * bfcontainer - The buffer container to be freed + * + * Returned Value: + * None + * + * Assumptions: + * The caller has exclusive access to the SSC state structure + * + ****************************************************************************/ + +static void ssc_bffree(struct sam_ssc_s *priv, struct sam_buffer_s *bfcontainer) +{ + irqstate_t flags; + + /* Put the buffer container back on the free list */ + + flags = irqsave(); + bfcontainer->flink = priv->freelist; + priv->freelist = bfcontainer; + irqrestore(flags); + + /* Wake up any threads waiting for a buffer container */ + + ssc_bufsemgive(priv); +} + +/**************************************************************************** + * Name: ssc_bfinitialize + * + * Description: + * Initialize the buffer container allocator by adding all of the + * pre-allocated buffer containers to the free list + * + * Input Parameters: + * priv - SSC state instance + * + * Returned Value: + * None + * + * Assumptions: + * Called early in SSC intialization so that there are no issues with + * concurrency. + * + ****************************************************************************/ + +static void ssc_bfinitialize(struct sam_ssc_s *priv) +{ + int i; + + priv->freelist = NULL; + sem_init(&priv->bufsem, 0, SAMA5_SSC_MAXINFLIGHT); + + for (i = 0; i < SAMA5_SSC_MAXINFLIGHT; i++) + { + ssc_bffree(priv, &priv->containers[i]); + } +} + /**************************************************************************** * Name: ssc_rxdma_sampleinit * @@ -501,7 +737,7 @@ static void ssc_dumpregs(struct sam_ssc_s *priv, const char *msg) * Initialize sampling of RX DMA registers (if CONFIG_SAMA5_SSC_DMADEBUG) * * Input Parameters: - * priv - Chip select doing the DMA + * priv - SSC state instance * * Returned Value: * None @@ -528,7 +764,7 @@ static void ssc_rxdma_sampleinit(struct sam_ssc_s *priv, bool tx) * Initialize sampling of TX DMA registers (if CONFIG_SAMA5_SSC_DMADEBUG) * * Input Parameters: - * priv - Chip select doing the DMA + * priv - SSC state instance * * Returned Value: * None @@ -555,7 +791,7 @@ static void ssc_txdma_sampleinit(struct sam_ssc_s *priv) * Dump sampled RX DMA registers * * Input Parameters: - * priv - Chip select doing the DMA + * priv - SSC state instance * * Returned Value: * None @@ -616,7 +852,7 @@ static void ssc_rxdma_sampledone(struct sam_ssc_s *priv) * Dump sampled DMA registers * * Input Parameters: - * priv - Chip select doing the DMA + * priv - SSC state instance * * Returned Value: * None @@ -650,8 +886,16 @@ static void ssc_txdma_sampledone(struct sam_ssc_s *priv) * -OR- DMA timeout. */ - sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_CALLBACK], - "TX: At DMA callback"); + if (priv->result == -ETIMEDOUT) + { + sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_TIMEOUT], + "TX: At DMA timeout"); + } + else + { + sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_CALLBACK], + "TX: At DMA callback"); + } sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_END_TRANSFER], "TX: At End-of-Transfer"); @@ -659,11 +903,10 @@ static void ssc_txdma_sampledone(struct sam_ssc_s *priv) #endif /**************************************************************************** - * Name: ssc_dmatimeout + * Name: ssc_rxdmatimeout * * Description: - * The watchdog timeout setup when a has expired without completion of a - * DMA. + * The RX watchdog timeout without completion of the RX DMA. * * Input Parameters: * argc - The number of arguments (should be 1) @@ -677,25 +920,168 @@ static void ssc_txdma_sampledone(struct sam_ssc_s *priv) * ****************************************************************************/ -static void ssc_dmatimeout(int argc, uint32_t arg) +#ifdef SSC_HAVE_RX +static void ssc_rxdmatimeout(int argc, uint32_t arg) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; DEBUGASSERT(priv != NULL); /* Sample DMA registers at the time of the timeout */ - ssc_rxdma_sample(priv, DMA_CALLBACK); + ssc_rxdma_sample(priv, DMA_TIMEOUT); - /* Report timeout result, perhaps overwriting any failure reports from - * the TX callback. + /* Cancel the DMA */ +#warning Missing logic + + /* Then schedule completion of the transfer to occur on the worker thread */ + + ssc_rxschedule(priv, -ETIMEDOUT); +} +#endif + +/**************************************************************************** + * Name: ssc_rxdmasetup + * + * Description: + * Setup and initiate the next RX DMA transfer + * + * Input Parameters: + * priv - SSC state instance + * + * Returned Value: + * OK on success; a negated errno value on failure + * + * Assumptions: + * Interrupts are disabled + * + ****************************************************************************/ + +#ifdef SSC_HAVE_RX +static int ssc_rxdmasetup(struct sam_ssc_s *priv) +{ + int ret; + + /* If there is already an active transmission in progress, then bail + * returning success. */ - priv->result = -ETIMEDOUT; + if (priv->rxact) + { + return OK; + } - /* Then wake up the waiting thread */ + /* Removing the pending RX transfer at the head of the RX pending queue + * and save it as the new active transfer. + */ +#warning Missing logic - sem_post(&priv->dmawait); + /* Start the RX DMA */ +#warning Missing logic + + /* Start a watchdog to catch DMA timeouts */ + + if (priv->txact->timeout > 0) + { + ret = wd_start(priv->txdog, priv->txact->timeout, + (wdentry_t)ssc_rxdmatimeout, 1, (uint32_t)priv); + if (ret < 0) + { + int errcode = errno; + i2slldbg("ERROR: wd_start failed: %d\n", errcode); + return -errcode; + } + } + + return OK; } +#endif + +/**************************************************************************** + * Name: ssc_rxworker + * + * Description: + * RX transfer done worker + * + * Input Parameters: + * arg - the SSC device instance cast to void* + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef SSC_HAVE_RX +static void ssc_rxworker(FAR void *arg) +{ + /* When the transfer was started, the active buffer container was removed + * from the rxpend queue and saved in as rxact. + * + * If the DMA completes successfully or fails due to a timeout, the buffer + * container in rxact will be moved to the end of the rxdone queue and + * this worker will be started. + */ + + i2svdbg("txact=%p txdone.head=%p\n", priv->txact, priv->tdone.head); +#warning Missing logic +} +#endif + +/**************************************************************************** + * Name: ssc_rxschedule + * + * Description: + * An RX DMA completion or timeout has occurred. Schedule processing on + * the working thread. + * + * Input Parameters: + * handle - The DMA handler + * arg - A pointer to the chip select struction + * result - The result of the DMA transfer + * + * Returned Value: + * None + * + * Assumptions: + * Interrupts are disabled + * + ****************************************************************************/ + +#ifdef SSC_HAVE_RX +static void ssc_rxschedule(struct sam_ssc_s *priv, int result) +{ + int ret; + + /* Upon entry, the transfer that just complete is the one at head at + * priv->rxact. It does not reside in any queue. + */ + + DEBUGASSERT(priv->rxact != NULL); + + /* Report the result of the transfer */ + + priv->rxact->result = result; + + /* Add the completed buffer to the tail of the txdone queue */ + + sq_addlast((sq_entry_t *)priv->rxact, &priv->rxdone); + priv->rxact = NULL; + + /* If the worker has completed running, then reschedule the working thread. + * REVISIT: There may be a race condition here. + */ + + if (work_available(&priv->rxwork)) + { + /* Schedule the TX DMA done processing to occur on the worker thread. */ + + ret = work_queue(HPWORK, &priv->rxwork, ssc_rxworker, priv, 0); + if (ret != 0) + { + i2slldbg("ERROR: Failed to queue RX work: %d\n", ret); + } + } +} +#endif /**************************************************************************** * Name: ssc_rxcallback @@ -713,6 +1099,7 @@ static void ssc_dmatimeout(int argc, uint32_t arg) * ****************************************************************************/ +#ifdef SSC_HAVE_RX static void ssc_rxcallback(DMA_HANDLE handle, void *arg, int result) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; @@ -720,27 +1107,202 @@ static void ssc_rxcallback(DMA_HANDLE handle, void *arg, int result) /* Cancel the watchdog timeout */ - (void)wd_cancel(priv->dmadog); + (void)wd_cancel(priv->rxdog); - /* Sample DMA registers at the time of the callback */ + /* Sample DMA registers at the time of the DMA completion */ ssc_rxdma_sample(priv, DMA_CALLBACK); - /* Report the result of the transfer only if the TX callback has not already - * reported an error. + /* Then schedule completion of the transfer to occur on the worker thread */ + + ssc_rxschedule(priv, result); +} +#endif + +/**************************************************************************** + * Name: ssc_txdmatimeout + * + * Description: + * The RX watchdog timeout without completion of the RX DMA. + * + * Input Parameters: + * argc - The number of arguments (should be 1) + * arg - The argument (state structure reference cast to uint32_t) + * + * Returned Value: + * None + * + * Assumptions: + * Always called from the interrupt level with interrupts disabled. + * + ****************************************************************************/ + +#ifdef SSC_HAVE_TX +static void ssc_txdmatimeout(int argc, uint32_t arg) +{ + struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; + DEBUGASSERT(priv != NULL); + + /* Sample DMA registers at the time of the timeout */ + + ssc_txdma_sample(priv, DMA_TIMEOUT); + + /* Cancel the DMA */ +#warning Missing logic + + /* Then schedule completion of the transfer to occur on the worker thread */ + + ssc_txschedule(priv, -ETIMEDOUT); +} +#endif + +/**************************************************************************** + * Name: ssc_txdmasetup + * + * Description: + * Setup and initiate the next TX DMA transfer + * + * Input Parameters: + * priv - SSC state instance + * + * Returned Value: + * OK on success; a negated errno value on failure + * + * Assumptions: + * Interrupts are disabled + * + ****************************************************************************/ + +#ifdef SSC_HAVE_TX +static int ssc_txdmasetup(struct sam_ssc_s *priv) +{ + int ret; + + /* If there is already an active transmission in progress, then bail + * returning success. */ - if (priv->result == -EBUSY) + if (priv->txact) { - /* Save the result of the transfer if no error was previuosly reported */ - - priv->result = result; + return OK; } - /* Then wake up the waiting thread */ + /* Removing the pending TX transfer at the head of the TX pending queue + * and save it as the new active transfer. + */ +#warning Missing logic - sem_post(&priv->dmawait); + /* Start the TX DMA */ +#warning Missing logic + + /* Start a watchdog to catch DMA timeouts */ + + if (priv->txact->timeout > 0) + { + ret = wd_start(priv->txdog, priv->txact->timeout, + (wdentry_t)ssc_rxdmatimeout, 1, (uint32_t)priv); + if (ret < 0) + { + int errcode = errno; + i2slldbg("ERROR: wd_start failed: %d\n", errcode); + return -errcode; + } + } + + return OK; } +#endif + +/**************************************************************************** + * Name: ssc_txworker + * + * Description: + * TX transfer done worker + * + * Input Parameters: + * arg - the SSC device instance cast to void* + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef SSC_HAVE_TX +static void ssc_txworker(FAR void *arg) +{ + struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; + DEBUGASSERT(priv); + + /* When the transfer was started, the active buffer container was removed + * from the txpend queue and saved in as txact. + * + * If the DMA completes successfully or fails due to a timeout, the buffer + * container in txact will be moved to the end of the txdone queue and + * this worker will be started. + */ + + i2svdbg("txact=%p txdone.head=%p\n", priv->txact, priv->tdone.head); +#warning Missing logic +} +#endif + +/**************************************************************************** + * Name: ssc_txschedule + * + * Description: + * An TX DMA completion or timeout has occurred. Schedule processing on + * the working thread. + * + * Input Parameters: + * handle - The DMA handler + * arg - A pointer to the chip select struction + * result - The result of the DMA transfer + * + * Returned Value: + * None + * + * Assumptions: + * - Interrupts are disabled + * - The TX timeout has been canceled. + * + ****************************************************************************/ + +#ifdef SSC_HAVE_TX +static void ssc_txschedule(struct sam_ssc_s *priv, int result) +{ + int ret; + + /* Upon entry, the transfer that just complete is the one at head at + * priv->txact. It does not reside in any queue. + */ + + DEBUGASSERT(priv->txact != NULL); + + /* Report the result of the transfer */ + + priv->txact->result = result; + + /* Add the completed buffer to the tail of the txdone queue */ + + sq_addlast((sq_entry_t *)priv->txact, &priv->txdone); + priv->txact = NULL; + + /* If the worker has completed running, then reschedule the working thread. + * REVISIT: There may be a race condition here. + */ + + if (work_available(&priv->txwork)) + { + /* Schedule the TX DMA done processing to occur on the worker thread. */ + + ret = work_queue(HPWORK, &priv->txwork, ssc_txworker, priv, 0); + if (ret != 0) + { + i2slldbg("ERROR: Failed to queue TX work: %d\n", ret); + } + } +} +#endif /**************************************************************************** * Name: ssc_txcallback @@ -758,25 +1320,25 @@ static void ssc_rxcallback(DMA_HANDLE handle, void *arg, int result) * ****************************************************************************/ +#ifdef SSC_HAVE_TX static void ssc_txcallback(DMA_HANDLE handle, void *arg, int result) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; DEBUGASSERT(priv != NULL); + /* Cancel the watchdog timeout */ + + (void)wd_cancel(priv->txdog); + + /* Sample DMA registers at the time of the DMA completion */ + ssc_txdma_sample(priv, DMA_CALLBACK); - /* Do nothing on the TX callback unless an error is reported. This - * callback is not really important because the SSC exchange is not - * complete until the RX callback is received. - */ + /* Then schedule completion of the transfer to occur on the worker thread */ - if (result != OK && priv->result == -EBUSY) - { - /* Save the result of the transfer if an error is reported */ - - priv->result = result; - } + ssc_txschedule(priv, result); } +#endif /**************************************************************************** * Name: ssc_frequency @@ -806,41 +1368,100 @@ static uint32_t ssc_frequency(struct i2s_dev_s *dev, uint32_t frequency) return actual; } -/*************************************************************************** +/**************************************************************************** * Name: ssc_send * * Description: - * Send a block of data on SSC using DMA is possible + * Send a block of data on I2S. * * Input Parameters: - * dev - Device-specific state data + * dev - Device-specific state data * buffer - A pointer to the buffer of data to be sent * nbytes - the length of data to send from the buffer in number of bytes. + * callback - A user provided callback function that will be called at + * the completion of the transfer. The callback will be + * performed in the context of the worker thread. + * arg - An opaque argument that will be provided to the callback + * when the transfer complete + * timeout - The timeout value to use. The transfer will be canceled + * and an ETIMEDOUT error will be reported if this timeout + * elapsed without completion of the DMA transfer. Units + * are system clock ticks. Zero means no timeout. * * Returned Value: - * None + * OK on success; a negated errno value on failure. NOTE: This function + * only enqueues the transfer and returns immediately. Success here only + * means that the transfer was enqueued correctly. + * + * When the transfer is complete, a 'result' value will be provided as + * an argument to the callback function that will indicate if the transfer + * failed. * ****************************************************************************/ -static void ssc_send(struct i2s_dev_s *dev, const void *buffer, size_t nbytes) +static int ssc_send(FAR struct i2s_dev_s *dev, FAR const void *buffer, + size_t nbytes, i2s_callback_t callback, + FAR void *arg, uint32_t timeout) { struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; + struct sam_buffer_s *bfcontainer; + irqstate_t flags; + int ret; i2svdbg("buffer=%p nbytes=%d\n", buffer, (int)nbytes); DEBUGASSERT(priv); #ifdef SSC_HAVE_TX + /* Allocate a buffer container in advance */ + + bfcontainer = ssc_bfallocate(priv); + DEBUGASSERT(bfcontainer); + + /* Get exclusive access to the SSC driver data */ + + ssc_exclsemtake(priv); + /* Has the TX channel been configured? */ if (!priv->tx) { i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno); - return; + ret = -EAGAIN; + goto errout_with_exclsem; } -#warning Missing logic + /* Initialize the buffer container structure */ + + bfcontainer->callback = callback; /* Function to call when the transfer completes */ + bfcontainer->timeout = timeout; /* The timeout value to use with DMA transfers */ + bfcontainer->arg = arg; /* The argument to be returned with the callback */ + bfcontainer->buffer = (FAR void *)buffer; /* I/O buffer */ + bfcontainer->nbytes = nbytes; /* Number of valid bytes in the buffer */ + bfcontainer->result = -EBUSY; /* The result of the transfer */ + + /* Add the buffer container to the end of the TX pending queue */ + + flags = irqsave(); + sq_addlast((sq_entry_t *)bfcontainer, &priv->txpend); + + /* Then start the next transfer. If there is already a transfer in progess, + * then this will do nothing. + */ + + ret = ssc_txdmasetup(priv); + DEBUGASSERT(ret == OK); + irqrestore(flags); + ssc_exclsemgive(priv); + return OK; + +errout_with_exclsem: + ssc_exclsemgive(priv); + ssc_bffree(priv, bfcontainer); + return ret; + #else i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno); + return -ENOSYS; #endif } @@ -848,38 +1469,97 @@ static void ssc_send(struct i2s_dev_s *dev, const void *buffer, size_t nbytes) * Name: ssc_receive * * Description: - * Receive a block of data from SSC using DMA is possible + * Receive a block of data from I2S. * * Input Parameters: - * dev - Device-specific state data - * buffer - A pointer to the buffer in which to recieve data - * nbytes - the length of data that can be received in the buffer in number - * of bytes. + * dev - Device-specific state data + * buffer - A pointer to the buffer in which to recieve data + * nbytes - The length of data that can be received in the buffer in number + * of bytes. + * callback - A user provided callback function that will be called at + * the completion of the transfer. The callback will be + * performed in the context of the worker thread. + * arg - An opaque argument that will be provided to the callback + * when the transfer complete + * timeout - The timeout value to use. The transfer will be canceled + * and an ETIMEDOUT error will be reported if this timeout + * elapsed without completion of the DMA transfer. Units + * are system clock ticks. Zero means no timeout. * * Returned Value: - * None + * OK on success; a negated errno value on failure. NOTE: This function + * only enqueues the transfer and returns immediately. Success here only + * means that the transfer was enqueued correctly. + * + * When the transfer is complete, a 'result' value will be provided as + * an argument to the callback function that will indicate if the transfer + * failed. * ****************************************************************************/ -static void ssc_receive(struct i2s_dev_s *dev, void *buffer, size_t nbytes) +static int ssc_receive(FAR struct i2s_dev_s *dev, FAR void *buffer, + size_t nbytes, i2s_callback_t callback, + FAR void *arg, uint32_t timeout) { struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; + struct sam_buffer_s *bfcontainer; + irqstate_t flags; + int ret; i2svdbg("buffer=%p nbytes=%d\n", buffer, (int)nbytes); DEBUGASSERT(priv); #ifdef SSC_HAVE_RX + /* Allocate a buffer container in advance */ + + bfcontainer = ssc_bfallocate(priv); + DEBUGASSERT(bfcontainer); + + /* Get exclusive access to the SSC driver data */ + + ssc_exclsemtake(priv); + /* Has the RX channel been configured? */ if (!priv->rx) { i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno); - return; + ret = -EAGAIN; + goto errout_with_exclsem; } -#warning Missing logic + /* Initialize the buffer container structure */ + + bfcontainer->callback = callback; /* Function to call when the transfer completes */ + bfcontainer->timeout = timeout; /* The timeout value to use with DMA transfers */ + bfcontainer->arg = arg; /* The argument to be returned with the callback */ + bfcontainer->buffer = buffer; /* I/O buffer */ + bfcontainer->nbytes = nbytes; /* Number of valid bytes in the buffer */ + bfcontainer->result = -EBUSY; /* The result of the transfer */ + + /* Add the buffer container to the end of the RX pending queue */ + + flags = irqsave(); + sq_addlast((sq_entry_t *)bfcontainer, &priv->rxpend); + + /* Then start the next transfer. If there is already a transfer in progess, + * then this will do nothing. + */ + + ret = ssc_rxdmasetup(priv); + DEBUGASSERT(ret == OK); + irqrestore(flags); + ssc_exclsemgive(priv); + return OK; + +errout_with_exclsem: + ssc_exclsemgive(priv); + ssc_bffree(priv, bfcontainer); + return ret; + #else i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno); + return -ENOSYS; #endif } @@ -1403,7 +2083,7 @@ static void ssc1_configure(struct sam_ssc_s *priv) * Initialize the selected SSC port * * Input Parameter: - * cs - Chip select number (identifying the "logical" SSC port) + * port - I2S "port" number (identifying the "logical" SSC port) * * Returned Value: * Valid SSC device structure reference on succcess; a NULL on failure @@ -1443,6 +2123,10 @@ struct i2s_dev_s *sam_ssc_initialize(int port) priv->dev.ops = &g_sscops; priv->sscno = port; + /* Initialize buffering */ + + ssc_bfinitialize(priv); + flags = irqsave(); #ifdef CONFIG_SAMA5_SSC0 if (port == 0) @@ -1471,38 +2155,49 @@ struct i2s_dev_s *sam_ssc_initialize(int port) #ifdef SSC_HAVE_RX if (priv->rx) { + /* Allocate an RX DMA channel */ + priv->rxdma = sam_dmachannel(port, 0); if (!priv->rxdma) { i2sdbg("ERROR: Failed to allocate the RX DMA channel\n"); goto errout_with_alloc; } + + /* Create a watchdog time to catch RX DMA timeouts */ + + priv->rxdog = wd_create(); + if (!priv->rxdog) + { + i2sdbg("ERROR: Failed to create the RX DMA watchdog\n"); + goto errout_with_rxdma; + } } #endif #ifdef SSC_HAVE_TX if (priv->tx) { + /* Allocate a TX DMA channel */ + priv->txdma = sam_dmachannel(port, 0); if (!priv->txdma) { i2sdbg("ERROR: Failed to allocate the TX DMA channel\n"); - goto errout_with_rxdma; + goto errout_with_rxdog; + } + + /* Create a watchdog time to catch TX DMA timeouts */ + + priv->txdog = wd_create(); + if (!priv->txdog) + { + i2sdbg("ERROR: Failed to create the TX DMA watchdog\n"); + goto errout_with_txdma; } } #endif - /* Initialize the SSC semaphore that is used to wake up the waiting - * thread when the DMA transfer completes. - */ - - sem_init(&priv->dmawait, 0, 0); - - /* Create a watchdog time to catch DMA timeouts */ - - priv->dmadog = wd_create(); - DEBUGASSERT(priv->dmadog); - /* Initialize I2S hardware */ /* Set the maximum SSC peripheral clock frequency */ @@ -1561,15 +2256,30 @@ struct i2s_dev_s *sam_ssc_initialize(int port) errout_with_clocking: sam_disableperiph1(priv->pid); + #ifdef SSC_HAVE_TX + if (priv->txdog) + { + wd_delete(priv->txdog); + } + +errout_with_txdma: if (priv->txdma) { sam_dmafree(priv->txdma); } - -errout_with_rxdma: #endif + +errout_with_rxdog: #ifdef SSC_HAVE_RX + if (priv->rxdog) + { + wd_delete(priv->rxdog); + } +#endif + +#ifdef SSC_HAVE_RX +errout_with_rxdma: if (priv->rxdma) { sam_dmafree(priv->rxdma); diff --git a/include/nuttx/audio/i2s.h b/include/nuttx/audio/i2s.h index 7f18f3e61d..e9df057fa8 100644 --- a/include/nuttx/audio/i2s.h +++ b/include/nuttx/audio/i2s.h @@ -76,53 +76,89 @@ * Name: I2S_SEND * * Description: - * Send a block of data on I2S. Required. + * Send a block of data on I2S. * * Input Parameters: * dev - Device-specific state data * buffer - A pointer to the buffer of data to be sent * nbytes - the length of data to send from the buffer in number of bytes. + * callback - A user provided callback function that will be called at + * the completion of the transfer. The callback will be + * performed in the context of the worker thread. + * arg - An opaque argument that will be provided to the callback + * when the transfer complete. + * timeout - The timeout value to use. The transfer will be canceled + * and an ETIMEDOUT error will be reported if this timeout + * elapsed without completion of the DMA transfer. Units + * are system clock ticks. Zero means no timeout. * * Returned Value: - * None + * OK on success; a negated errno value on failure. NOTE: This function + * only enqueues the transfer and returns immediately. Success here only + * means that the transfer was enqueued correctly. + * + * When the transfer is complete, a 'result' value will be provided as + * an argument to the callback function that will indicate if the transfer + * failed. * ****************************************************************************/ -#define I2S_SEND(d,b,n) ((d)->ops->i2s_send(d,b,n)) +#define I2S_SEND(d,b,n,c,a,t) ((d)->ops->i2s_send(d,b,n,c,a,t)) /**************************************************************************** * Name: I2S_RECEIVE * * Description: - * Receive a block of data from I2S. Required. + * Receive a block of data from I2S. * * Input Parameters: - * dev - Device-specific state data - * buffer - A pointer to the buffer in which to recieve data - * nbytes - The length of data that can be received in the buffer in number - * of bytes. + * dev - Device-specific state data + * buffer - A pointer to the buffer in which to recieve data + * nbytes - The length of data that can be received in the buffer in number + * of bytes. + * callback - A user provided callback function that will be called at + * the completion of the transfer. The callback will be + * performed in the context of the worker thread. + * arg - An opaque argument that will be provided to the callback + * when the transfer complete. + * timeout - The timeout value to use. The transfer will be canceled + * and an ETIMEDOUT error will be reported if this timeout + * elapsed without completion of the DMA transfer. Units + * are system clock ticks. Zero means no timeout. * * Returned Value: - * None + * OK on success; a negated errno value on failure. NOTE: This function + * only enqueues the transfer and returns immediately. Success here only + * means that the transfer was enqueued correctly. + * + * When the transfer is complete, a 'result' value will be provided as + * an argument to the callback function that will indicate if the transfer + * failed. * ****************************************************************************/ -#define I2S_RECEIVE(d,b,n) ((d)->ops->i2s_receive(d,b,n)) +#define I2S_RECEIVE(d,b,n,c,a,t) ((d)->ops->i2s_receive(d,b,n,c,a,t)) /**************************************************************************** * Public Types ****************************************************************************/ +/* Transfer complete callback */ +typedef CODE void (*i2s_callback_t)(FAR struct i2s_dev_s *dev, + FAR const void *buffer, size_t nbytes, + FAR void *arg, int result); /* The I2S vtable */ struct i2s_dev_s; struct i2s_ops_s { uint32_t (*i2s_frequency)(FAR struct i2s_dev_s *dev, uint32_t frequency); - void (*i2s_send)(FAR struct i2s_dev_s *dev, FAR const void *buffer, - size_t nbytes); - void (*i2s_receive)(FAR struct i2s_dev_s *dev, FAR void *buffer, - size_t nbytes); + int (*i2s_send)(FAR struct i2s_dev_s *dev, FAR const void *buffer, + size_t nbytes, i2s_callback_t callback, + FAR void *arg, uint32_t timeout); + int (*i2s_receive)(FAR struct i2s_dev_s *dev, FAR void *buffer, + size_t nbytes, i2s_callback_t callback, + FAR void *arg, uint32_t timeout); }; /* I2S private data. This structure only defines the initial fields of the