diff options
author | Gregory Nutt <gnutt@nuttx.org> | 2013-11-09 13:08:20 -0600 |
---|---|---|
committer | Gregory Nutt <gnutt@nuttx.org> | 2013-11-09 13:08:20 -0600 |
commit | ee46773cb27fe11a294700571bb81e3b6f505c98 (patch) | |
tree | 48a1b46afbe14aab563e38a09fa5470ac653bf82 | |
parent | 16f9cf4bd743906df59de8583e180c8ac9049225 (diff) | |
download | nuttx-ee46773cb27fe11a294700571bb81e3b6f505c98.tar.gz nuttx-ee46773cb27fe11a294700571bb81e3b6f505c98.tar.bz2 nuttx-ee46773cb27fe11a294700571bb81e3b6f505c98.zip |
SAMA5 I2S: Driver is code complete, untested and subject to some rethinking
-rw-r--r-- | nuttx/ChangeLog | 2 | ||||
-rw-r--r-- | nuttx/arch/arm/src/sama5/Kconfig | 26 | ||||
-rw-r--r-- | nuttx/arch/arm/src/sama5/sam_ssc.c | 1049 | ||||
-rw-r--r-- | nuttx/include/nuttx/audio/i2s.h | 119 |
4 files changed, 881 insertions, 315 deletions
diff --git a/nuttx/ChangeLog b/nuttx/ChangeLog index 4efbf55d9..6086cfd2f 100644 --- a/nuttx/ChangeLog +++ b/nuttx/ChangeLog @@ -5999,4 +5999,6 @@ provided by David Sidrane (2013-11-7). * configs/spark/usbserial.c: Add an apps/examples/usbserial configuration for the Spark. From David Sidran (2013-11-7). + * arch/arm/src/sama5/sam_ssc.c and .h: First cut of SAMA5 + SSC/I2S driver is code complete (2013-11-9). diff --git a/nuttx/arch/arm/src/sama5/Kconfig b/nuttx/arch/arm/src/sama5/Kconfig index 4db854804..74f211034 100644 --- a/nuttx/arch/arm/src/sama5/Kconfig +++ b/nuttx/arch/arm/src/sama5/Kconfig @@ -1402,7 +1402,8 @@ config SAMA5_SSC0_DATALEN int "Data width (bits)" default 16 ---help--- - Data width in bits. + Data width in bits. This is a default value and may be change + via the I2S interface config SAMA5_SSC0_RX bool "Enable I2C receiver" @@ -1516,16 +1517,16 @@ endchoice # Receiver output clock endif # !SAMA5_SSC0_TX_TKINPUT endif # SAMA5_SSC0_TX -config SAMA5_SSC0_MCKDIV_FREQUENCY +config SAMA5_SSC0_MCKDIV_SAMPLERATE int "Internal transmitter clock frequency" default 100 depends on SAMA5_SSC0_RX_MCKDIV || SAMA5_SSC0_TX_MCKDIV ---help--- If the either the receiver or transmitter clock is provided by MCK/2 divided - down, then the target frequency must be provided. The SSC driver will - determine the best divider to obtain that frequency (up to 4095). If the - frequency is too low to be obtained by dividing down the MCK/2, a compile - time error will occur. + down, then the samplerate must be provided. The bitrate will be the product + of the sample rate and the data width. The SSC driver will determine the best + divider to obtain that bitrate (up to 4095). If the bitrate can be realized + by dividing down the MCK/2, a compile time error will occur. endif # SAMA5_SSC0 @@ -1542,7 +1543,8 @@ config SAMA5_SSC1_DATALEN int "Data width (bits)" default 16 ---help--- - Data width in bits. + Data width in bits. This is a default value and may be change + via the I2S interface config SAMA5_SSC1_RX bool "Enable I2C receiver" @@ -1656,16 +1658,16 @@ endchoice # Receiver output clock endif # !SAMA5_SSC1_TX_TKINPUT endif # SAMA5_SSC1_TX -config SAMA5_SSC1_MCKDIV_FREQUENCY +config SAMA5_SSC1_MCKDIV_SAMPLERATE int "Internal transmitter clock frequency" default 100 depends on SAMA5_SSC1_RX_MCKDIV || SAMA5_SSC1_TX_MCKDIV ---help--- If the either the receiver or transmitter clock is provided by MCK/2 divided - down, then the target frequency must be provided. The SSC driver will - determine the best divider to obtain that frequency (up to 4095). If the - frequency is too low to be obtained by dividing down the MCK/2, a compile - time error will occur. + down, then the samplerate must be provided. The bitrate will be the product + of the sample rate and the data width. The SSC driver will determine the best + divider to obtain that bitrate (up to 4095). If the bitrate can be realized + by dividing down the MCK/2, a compile time error will occur. endif # SAMA5_SSC1 diff --git a/nuttx/arch/arm/src/sama5/sam_ssc.c b/nuttx/arch/arm/src/sama5/sam_ssc.c index 0ea089594..f24304229 100644 --- a/nuttx/arch/arm/src/sama5/sam_ssc.c +++ b/nuttx/arch/arm/src/sama5/sam_ssc.c @@ -59,6 +59,7 @@ #include "up_internal.h" #include "up_arch.h" +#include "cache.h" #include "chip.h" #include "sam_pio.h" @@ -118,6 +119,26 @@ #define SSC_DATNB (1) /* Data number per frame */ +/* Check if we need the sample rate to set MCK/2 divider */ + +#undef SSC_HAVE_MCK2 +#undef SSC0_HAVE_MCK2 +#undef SSC1_HAVE_MCK2 + +#if (defined(CONFIG_SAMA5_SSC0_RX) && defined(CONFIG_SAMA5_SSC0_RX_MCKDIV)) || \ + (defined(CONFIG_SAMA5_SSC0_TX) && defined(CONFIG_SAMA5_SSC0_TX_MCKDIV)) +# define SSC0_HAVE_MCK2 1 +#endif + +#if (defined(CONFIG_SAMA5_SSC1_RX) && defined(CONFIG_SAMA5_SSC1_RX_MCKDIV)) || \ + (defined(CONFIG_SAMA5_SSC1_TX) && defined(CONFIG_SAMA5_SSC1_TX_MCKDIV)) +# define SSC1_HAVE_MCK2 1 +#endif + +#if defined(SSC0_HAVE_MCK2) || defined(SSC1_HAVE_MCK2) +# define SSC_HAVE_MCK2 1 +#endif + /* Clocking *****************************************************************/ /* Select MCU-specific settings * @@ -156,6 +177,16 @@ #define SSC_CLKOUT_CONT 1 /* Continuous */ #define SSC_CLKOUT_XFER 2 /* Only output clock during transfers */ +/* DMA configuration */ + +#define DMA_FLAGS(pid) \ + (((pid) << DMACH_FLAG_PERIPHPID_SHIFT) | DMACH_FLAG_PERIPHAHB_AHB_IF2 | \ + DMACH_FLAG_PERIPHH2SEL | DMACH_FLAG_PERIPHISPERIPH | \ + DMACH_FLAG_PERIPHWIDTH_32BITS | DMACH_FLAG_PERIPHCHUNKSIZE_1 | \ + ((0x3f) << DMACH_FLAG_MEMPID_SHIFT) | DMACH_FLAG_MEMAHB_AHB_IF0 | \ + DMACH_FLAG_MEMWIDTH_32BITS | DMACH_FLAG_MEMINCREMENT | \ + DMACH_FLAG_MEMCHUNKSIZE_4) + /* DMA timeout. The value is not critical; we just don't want the system to * hang in the event that a DMA does not finish. This is set to */ @@ -236,7 +267,9 @@ 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 */ - uint32_t frequency; /* Target MCK frequency */ +#ifdef SSC_HAVE_MCK2 + uint32_t samplerate; /* Data sample rate (determines only MCK/2 divider) */ +#endif #ifdef SSC_HAVE_RX DMA_HANDLE rxdma; /* SSC RX DMA handle */ @@ -306,19 +339,19 @@ static void ssc_dumpregs(struct sam_ssc_s *priv, const char *msg); /* Semaphore helpers */ -static void ssc_exclsemtake(FAR struct sam_ssc_s *priv); -#define ssc_exclsemgive(priv) sem_post(&priv->exclsem) +static void ssc_exclsem_take(FAR struct sam_ssc_s *priv); +#define ssc_exclsem_give(priv) sem_post(&priv->exclsem) -static void ssc_bufsemtake(FAR struct sam_ssc_s *priv); -#define ssc_bufsemgive(priv) sem_post(&priv->bufsem) +static void ssc_bufsem_take(FAR struct sam_ssc_s *priv); +#define ssc_bufsem_give(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, + ssc_buf_allocate(struct sam_ssc_s *priv); +static void ssc_buf_free(struct sam_ssc_s *priv, struct sam_buffer_s *bfcontainer); -static void ssc_bfinitialize(struct sam_ssc_s *priv); +static void ssc_buf_initialize(struct sam_ssc_s *priv); /* DMA support */ @@ -347,27 +380,30 @@ static void ssc_txdma_sampledone(struct sam_ssc_s *priv); #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); +static void ssc_rxdma_timeout(int argc, uint32_t arg); +static int ssc_rxdma_setup(FAR struct sam_ssc_s *priv); +static void ssc_rx_worker(FAR void *arg); +static void ssc_rx_schedule(FAR struct sam_ssc_s *priv, int result); +static void ssc_rxdma_callback(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); +static void ssc_txdma_timeout(int argc, uint32_t arg); +static int ssc_txdma_setup(FAR struct sam_ssc_s *priv); +static void ssc_tx_worker(FAR void *arg); +static void ssc_tx_schedule(FAR struct sam_ssc_s *priv, int result); +static void ssc_txdma_callback(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 int ssc_send(FAR struct i2s_dev_s *dev, FAR const void *buffer, +static uint32_t ssc_rxsamplerate(FAR struct i2s_dev_s *dev, uint32_t rate); +static uint32_t ssc_rxdatawidth(FAR struct i2s_dev_s *dev, int bits); +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); -static int ssc_receive(FAR struct i2s_dev_s *dev, FAR void *buffer, +static uint32_t ssc_txsamplerate(FAR struct i2s_dev_s *dev, uint32_t rate); +static uint32_t ssc_txdatawidth(FAR struct i2s_dev_s *dev, int bits); +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); @@ -379,6 +415,9 @@ static int ssc_rx_configure(struct sam_ssc_s *priv); #ifdef SSC_HAVE_TX static int ssc_tx_configure(struct sam_ssc_s *priv); #endif +static void ssc_clocking(struct sam_ssc_s *priv); +static int ssc_dma_allocate(struct sam_ssc_s *priv); +static void ssc_dma_free(struct sam_ssc_s *priv); #ifdef CONFIG_SAMA5_SSC0 static void ssc0_configure(struct sam_ssc_s *priv); #endif @@ -393,9 +432,17 @@ static void ssc1_configure(struct sam_ssc_s *priv); static const struct i2s_ops_s g_sscops = { - .i2s_frequency = ssc_frequency, - .i2s_send = ssc_send, - .i2s_receive = ssc_receive, + /* Receiver methods */ + + .i2s_rxsamplerate = ssc_rxsamplerate, + .i2s_rxdatawidth = ssc_rxdatawidth, + .i2s_receive = ssc_receive, + + /* Transmitter methods */ + + .i2s_txsamplerate = ssc_txsamplerate, + .i2s_txdatawidth = ssc_txdatawidth, + .i2s_send = ssc_send, }; /**************************************************************************** @@ -559,7 +606,7 @@ static void ssc_dumpregs(struct sam_ssc_s *priv, const char *msg) /**************************************************************************** - * Name: ssc_exclsemtake + * Name: ssc_exclsem_take * * Description: * Take the exclusive access semaphore handling any exceptional conditions @@ -572,7 +619,7 @@ static void ssc_dumpregs(struct sam_ssc_s *priv, const char *msg) * ****************************************************************************/ -static void ssc_exclsemtake(FAR struct sam_ssc_s *priv) +static void ssc_exclsem_take(FAR struct sam_ssc_s *priv) { int ret; @@ -590,7 +637,7 @@ static void ssc_exclsemtake(FAR struct sam_ssc_s *priv) } /**************************************************************************** - * Name: ssc_exclsemtake + * Name: ssc_exclsem_take * * Description: * Take the buffer semaphore handling any exceptional conditions @@ -603,7 +650,7 @@ static void ssc_exclsemtake(FAR struct sam_ssc_s *priv) * ****************************************************************************/ -static void ssc_bufsemtake(FAR struct sam_ssc_s *priv) +static void ssc_bufsem_take(FAR struct sam_ssc_s *priv) { int ret; @@ -621,7 +668,7 @@ static void ssc_bufsemtake(FAR struct sam_ssc_s *priv) } /**************************************************************************** - * Name: ssc_bfallocate + * Name: ssc_buf_allocate * * Description: * Allocate a buffer container by removing the one at the head of the @@ -640,7 +687,7 @@ static void ssc_bufsemtake(FAR struct sam_ssc_s *priv) * ****************************************************************************/ -static struct sam_buffer_s *ssc_bfallocate(struct sam_ssc_s *priv) +static struct sam_buffer_s *ssc_buf_allocate(struct sam_ssc_s *priv) { struct sam_buffer_s *bfcontainer; irqstate_t flags; @@ -649,7 +696,7 @@ static struct sam_buffer_s *ssc_bfallocate(struct sam_ssc_s *priv) * have at least one free buffer container. */ - ssc_bufsemtake(priv); + ssc_bufsem_take(priv); /* Get the buffer from the head of the free list */ @@ -665,7 +712,7 @@ static struct sam_buffer_s *ssc_bfallocate(struct sam_ssc_s *priv) } /**************************************************************************** - * Name: ssc_bffree + * Name: ssc_buf_free * * Description: * Free buffer container by adding it to the head of the free list @@ -682,7 +729,7 @@ 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_buf_free(struct sam_ssc_s *priv, struct sam_buffer_s *bfcontainer) { irqstate_t flags; @@ -695,11 +742,11 @@ static void ssc_bffree(struct sam_ssc_s *priv, struct sam_buffer_s *bfcontainer) /* Wake up any threads waiting for a buffer container */ - ssc_bufsemgive(priv); + ssc_bufsem_give(priv); } /**************************************************************************** - * Name: ssc_bfinitialize + * Name: ssc_buf_initialize * * Description: * Initialize the buffer container allocator by adding all of the @@ -717,7 +764,7 @@ static void ssc_bffree(struct sam_ssc_s *priv, struct sam_buffer_s *bfcontainer) * ****************************************************************************/ -static void ssc_bfinitialize(struct sam_ssc_s *priv) +static void ssc_buf_initialize(struct sam_ssc_s *priv) { int i; @@ -726,7 +773,7 @@ static void ssc_bfinitialize(struct sam_ssc_s *priv) for (i = 0; i < SAMA5_SSC_MAXINFLIGHT; i++) { - ssc_bffree(priv, &priv->containers[i]); + ssc_buf_free(priv, &priv->containers[i]); } } @@ -745,7 +792,7 @@ static void ssc_bfinitialize(struct sam_ssc_s *priv) ****************************************************************************/ #if defined(CONFIG_SAMA5_SSC_DMADEBUG) && defined(SSC_HAVE_RX) -static void ssc_rxdma_sampleinit(struct sam_ssc_s *priv, bool tx) +static void ssc_rxdma_sampleinit(struct sam_ssc_s *priv) { /* Put contents of register samples into a known state */ @@ -903,7 +950,7 @@ static void ssc_txdma_sampledone(struct sam_ssc_s *priv) #endif /**************************************************************************** - * Name: ssc_rxdmatimeout + * Name: ssc_rxdma_timeout * * Description: * The RX watchdog timeout without completion of the RX DMA. @@ -921,7 +968,7 @@ static void ssc_txdma_sampledone(struct sam_ssc_s *priv) ****************************************************************************/ #ifdef SSC_HAVE_RX -static void ssc_rxdmatimeout(int argc, uint32_t arg) +static void ssc_rxdma_timeout(int argc, uint32_t arg) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; DEBUGASSERT(priv != NULL); @@ -931,16 +978,17 @@ static void ssc_rxdmatimeout(int argc, uint32_t arg) ssc_rxdma_sample(priv, DMA_TIMEOUT); /* Cancel the DMA */ -#warning Missing logic + + sam_dmastop(priv->rxdma); /* Then schedule completion of the transfer to occur on the worker thread */ - ssc_rxschedule(priv, -ETIMEDOUT); + ssc_rx_schedule(priv, -ETIMEDOUT); } #endif /**************************************************************************** - * Name: ssc_rxdmasetup + * Name: ssc_rxdma_setup * * Description: * Setup and initiate the next RX DMA transfer @@ -957,38 +1005,82 @@ static void ssc_rxdmatimeout(int argc, uint32_t arg) ****************************************************************************/ #ifdef SSC_HAVE_RX -static int ssc_rxdmasetup(struct sam_ssc_s *priv) +static int ssc_rxdma_setup(struct sam_ssc_s *priv) { + struct sam_buffer_s *bfcontainer; + uintptr_t paddr; + uintptr_t maddr; int ret; /* If there is already an active transmission in progress, then bail * returning success. */ - if (priv->rxact) + if (priv->rxact != NULL) + { + return OK; + } + + /* Remove the pending RX transfer at the head of the RX pending queue. */ + + bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->rxpend); + + /* If there are no pending transfer, then bail returning success */ + + if (!bfcontainer) { return OK; } - /* Removing the pending RX transfer at the head of the RX pending queue - * and save it as the new active transfer. + /* Initialize DMA register sampling */ + + ssc_rxdma_sampleinit(priv); + + /* Physical address of the SSC RHR register and of the buffer location in + * RAM. */ -#warning Missing logic - /* Start the RX DMA */ -#warning Missing logic + paddr = ssc_physregaddr(priv, SAM_SSC_RHR_OFFSET); + maddr = sam_physramaddr((uintptr_t)bfcontainer->buffer); + + /* Configure the RX DMA */ + + sam_dmarxsetup(priv->rxdma, paddr, maddr, bfcontainer->nbytes); + ssc_rxdma_sample(priv, DMA_AFTER_SETUP); + + /* Invalidate the data cache so that nothing gets flush into the + * DMA buffer after starting the DMA transfer. + */ + + cp15_invalidate_dcache((uintptr_t)bfcontainer->buffer, + (uintptr_t)bfcontainer->buffer + bfcontainer->nbytes); + + /* Start the DMA, saving the container as the current active transfer */ + + priv->rxact = bfcontainer; + sam_dmastart(priv->rxdma, ssc_rxdma_callback, priv); + ssc_rxdma_sample(priv, DMA_AFTER_START); + + /* Enable the receiver */ + + ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_RXEN); /* Start a watchdog to catch DMA timeouts */ - if (priv->txact->timeout > 0) + if (priv->rxact->timeout > 0) { - ret = wd_start(priv->txdog, priv->txact->timeout, - (wdentry_t)ssc_rxdmatimeout, 1, (uint32_t)priv); + ret = wd_start(priv->rxdog, priv->rxact->timeout, + (wdentry_t)ssc_rxdma_timeout, 1, (uint32_t)priv); + + /* Check if we have successfully started the watchdog timer. Note + * that we do nothing in the case of failure to start the timer. We + * are already committed to the DMA anyway. Let's just hope that the + * DMA does not hang. + */ + if (ret < 0) { - int errcode = errno; - i2slldbg("ERROR: wd_start failed: %d\n", errcode); - return -errcode; + i2slldbg("ERROR: wd_start failed: %d\n", errno); } } @@ -997,7 +1089,7 @@ static int ssc_rxdmasetup(struct sam_ssc_s *priv) #endif /**************************************************************************** - * Name: ssc_rxworker + * Name: ssc_rx_worker * * Description: * RX transfer done worker @@ -1011,23 +1103,78 @@ static int ssc_rxdmasetup(struct sam_ssc_s *priv) ****************************************************************************/ #ifdef SSC_HAVE_RX -static void ssc_rxworker(FAR void *arg) +static void ssc_rx_worker(FAR void *arg) { + struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; + struct sam_buffer_s *bfcontainer; + irqstate_t flags; + + DEBUGASSERT(priv); + /* When the transfer was started, the active buffer container was removed - * from the rxpend queue and saved in as rxact. + * from the rxpend queue and saved in as rxact. We get here when the + * DMA if finished... either successfully, with a DMA error, or with a DMA + * timeout. + * + * In any case, the buffer container in rxact will be moved to the end + * of the rxdone queue and rxact will be nullified before this worker is + * started. * - * 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. + * REVISIT: Normal DMA callback processing should restart the DMA + * immediately to avoid audio artifacts at the boundaries between DMA + * transfers. Unfortunately, the DMA callback occurs at the interrupt + * level and we cannot call dma_rxsetup() from the interrupt level. + * So we have to start the next DMA here. */ - i2svdbg("txact=%p txdone.head=%p\n", priv->txact, priv->tdone.head); -#warning Missing logic + i2svdbg("trxact=%p rxdone.head=%p\n", priv->trxact, priv->txdone.head); + + /* Check if the DMA is IDLE */ + + if (priv->rxact == NULL) + { + /* Dump the DMA registers only if the DMA is IDLE */ + + ssc_rxdma_sampledone(priv); + + /* Then start the next DMA. This must be done with interrupts + * disabled. + */ + + flags = irqsave(); + (void)ssc_rxdma_setup(priv); + irqrestore(flags); + } + + /* Process each buffer in the rxdone queue */ + + while (sq_peek(&priv->rxdone) != NULL) + { + /* Remove the buffer container from the rxdone queue. NOTE that + * interupts must be enabled to do this because the rxdone queue is + * also modified from the interrupt level. + */ + + flags = irqsave(); + bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->rxdone); + irqrestore(flags); + + /* Perform the TX transfer done callback */ + + DEBUGASSERT(bfcontainter && bfcontainer->callback); + bfcontainer->callback(&priv->dev, bfcontainer->buffer, + bfcontainer->nbytes, bfcontainer->arg, + bfcontainer->result); + + /* And release the buffer container */ + + ssc_buf_free(priv, bfcontainer); + } } #endif /**************************************************************************** - * Name: ssc_rxschedule + * Name: ssc_rx_schedule * * Description: * An RX DMA completion or timeout has occurred. Schedule processing on @@ -1047,7 +1194,7 @@ static void ssc_rxworker(FAR void *arg) ****************************************************************************/ #ifdef SSC_HAVE_RX -static void ssc_rxschedule(struct sam_ssc_s *priv, int result) +static void ssc_rx_schedule(struct sam_ssc_s *priv, int result) { int ret; @@ -1074,7 +1221,7 @@ static void ssc_rxschedule(struct sam_ssc_s *priv, int result) { /* Schedule the TX DMA done processing to occur on the worker thread. */ - ret = work_queue(HPWORK, &priv->rxwork, ssc_rxworker, priv, 0); + ret = work_queue(HPWORK, &priv->rxwork, ssc_rx_worker, priv, 0); if (ret != 0) { i2slldbg("ERROR: Failed to queue RX work: %d\n", ret); @@ -1084,7 +1231,7 @@ static void ssc_rxschedule(struct sam_ssc_s *priv, int result) #endif /**************************************************************************** - * Name: ssc_rxcallback + * Name: ssc_rxdma_callback * * Description: * This callback function is invoked at the completion of the SSC RX DMA. @@ -1100,7 +1247,7 @@ static void ssc_rxschedule(struct sam_ssc_s *priv, int result) ****************************************************************************/ #ifdef SSC_HAVE_RX -static void ssc_rxcallback(DMA_HANDLE handle, void *arg, int result) +static void ssc_rxdma_callback(DMA_HANDLE handle, void *arg, int result) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; DEBUGASSERT(priv != NULL); @@ -1113,14 +1260,19 @@ static void ssc_rxcallback(DMA_HANDLE handle, void *arg, int result) ssc_rxdma_sample(priv, DMA_CALLBACK); + /* REVISIT: We would like to the next DMA started here so that we do not + * get audio glitches at the boundaries between DMA transfers. + * Unfortunately, we cannot call sam_dmasetup() from an interrupt handler! + */ + /* Then schedule completion of the transfer to occur on the worker thread */ - ssc_rxschedule(priv, result); + ssc_rx_schedule(priv, result); } #endif /**************************************************************************** - * Name: ssc_txdmatimeout + * Name: ssc_txdma_timeout * * Description: * The RX watchdog timeout without completion of the RX DMA. @@ -1138,7 +1290,7 @@ static void ssc_rxcallback(DMA_HANDLE handle, void *arg, int result) ****************************************************************************/ #ifdef SSC_HAVE_TX -static void ssc_txdmatimeout(int argc, uint32_t arg) +static void ssc_txdma_timeout(int argc, uint32_t arg) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; DEBUGASSERT(priv != NULL); @@ -1148,16 +1300,17 @@ static void ssc_txdmatimeout(int argc, uint32_t arg) ssc_txdma_sample(priv, DMA_TIMEOUT); /* Cancel the DMA */ -#warning Missing logic + + sam_dmastop(priv->txdma); /* Then schedule completion of the transfer to occur on the worker thread */ - ssc_txschedule(priv, -ETIMEDOUT); + ssc_tx_schedule(priv, -ETIMEDOUT); } #endif /**************************************************************************** - * Name: ssc_txdmasetup + * Name: ssc_txdma_setup * * Description: * Setup and initiate the next TX DMA transfer @@ -1174,8 +1327,11 @@ static void ssc_txdmatimeout(int argc, uint32_t arg) ****************************************************************************/ #ifdef SSC_HAVE_TX -static int ssc_txdmasetup(struct sam_ssc_s *priv) +static int ssc_txdma_setup(struct sam_ssc_s *priv) { + struct sam_buffer_s *bfcontainer; + uintptr_t paddr; + uintptr_t maddr; int ret; /* If there is already an active transmission in progress, then bail @@ -1187,25 +1343,66 @@ static int ssc_txdmasetup(struct sam_ssc_s *priv) return OK; } - /* Removing the pending TX transfer at the head of the TX pending queue - * and save it as the new active transfer. + /* Remove the pending TX transfer at the head of the TX pending queue. */ + + bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->txpend); + + /* If there are no pending transfer, then bail returning success */ + + if (!bfcontainer) + { + return OK; + } + + /* Initialize DMA register sampling */ + + ssc_txdma_sampleinit(priv); + + /* Physical address of the SSC THR register and of the buffer location in + * RAM. */ -#warning Missing logic - /* Start the TX DMA */ -#warning Missing logic + paddr = ssc_physregaddr(priv, SAM_SSC_THR_OFFSET); + maddr = sam_physramaddr((uintptr_t)bfcontainer->buffer); + + /* Configure the TX DMA */ + + sam_dmatxsetup(priv->txdma, paddr, maddr, bfcontainer->nbytes); + ssc_txdma_sample(priv, DMA_AFTER_SETUP); + + /* Flush the data cache so that everything is in the physical memory + * before starting the DMA. + */ + + cp15_clean_dcache((uintptr_t)bfcontainer->buffer, + (uintptr_t)bfcontainer->buffer + bfcontainer->nbytes); + + /* Start the DMA, saving the container as the current active transfer */ + + priv->txact = bfcontainer; + sam_dmastart(priv->txdma, ssc_txdma_callback, priv); + ssc_txdma_sample(priv, DMA_AFTER_START); + + /* Enable the transmitter */ + + ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_TXEN); /* 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); + (wdentry_t)ssc_txdma_timeout, 1, (uint32_t)priv); + + /* Check if we have successfully started the watchdog timer. Note + * that we do nothing in the case of failure to start the timer. We + * are already committed to the DMA anyway. Let's just hope that the + * DMA does not hang. + */ + if (ret < 0) { - int errcode = errno; - i2slldbg("ERROR: wd_start failed: %d\n", errcode); - return -errcode; + i2slldbg("ERROR: wd_start failed: %d\n", errno); } } @@ -1214,7 +1411,7 @@ static int ssc_txdmasetup(struct sam_ssc_s *priv) #endif /**************************************************************************** - * Name: ssc_txworker + * Name: ssc_tx_worker * * Description: * TX transfer done worker @@ -1228,26 +1425,78 @@ static int ssc_txdmasetup(struct sam_ssc_s *priv) ****************************************************************************/ #ifdef SSC_HAVE_TX -static void ssc_txworker(FAR void *arg) +static void ssc_tx_worker(FAR void *arg) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; + struct sam_buffer_s *bfcontainer; + irqstate_t flags; + DEBUGASSERT(priv); /* When the transfer was started, the active buffer container was removed - * from the txpend queue and saved in as txact. + * from the txpend queue and saved in as txact. We get here when the + * DMA if finished... either successfully, with a DMA error, or with a DMA + * timeout. * - * 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. + * In any case, the buffer container in txact will be moved to the end + * of the txdone queue and txact will be nullified before this worker is + * started. + * + * REVISIT: Normal DMA callback processing should restart the DMA + * immediately to avoid audio artifacts at the boundaries between DMA + * transfers. Unfortunately, the DMA callback occurs at the interrupt + * level and we cannot call dma_txsetup() from the interrupt level. + * So we have to start the next DMA here. */ - i2svdbg("txact=%p txdone.head=%p\n", priv->txact, priv->tdone.head); -#warning Missing logic + i2svdbg("txact=%p txdone.head=%p\n", priv->txact, priv->txdone.head); + + /* Check if the DMA is IDLE */ + + if (priv->txact == NULL) + { + /* Dump the DMA registers only if the DMA is IDLE */ + + ssc_txdma_sampledone(priv); + + /* Then start the next DMA. This must be done with interrupts + * disabled. + */ + + flags = irqsave(); + (void)ssc_txdma_setup(priv); + irqrestore(flags); + } + + /* Process each buffer in the txdone queue */ + + while (sq_peek(&priv->txdone) != NULL) + { + /* Remove the buffer container from the txdone queue. NOTE that + * interupts must be enabled to do this because the txdone queue is + * also modified from the interrupt level. + */ + + flags = irqsave(); + bfcontainer = (struct sam_buffer_s *)sq_remfirst(&priv->txdone); + irqrestore(flags); + + /* Perform the TX transfer done callback */ + + DEBUGASSERT(bfcontainter && bfcontainer->callback); + bfcontainer->callback(&priv->dev, bfcontainer->buffer, + bfcontainer->nbytes, bfcontainer->arg, + bfcontainer->result); + + /* And release the buffer container */ + + ssc_buf_free(priv, bfcontainer); + } } #endif /**************************************************************************** - * Name: ssc_txschedule + * Name: ssc_tx_schedule * * Description: * An TX DMA completion or timeout has occurred. Schedule processing on @@ -1268,7 +1517,7 @@ static void ssc_txworker(FAR void *arg) ****************************************************************************/ #ifdef SSC_HAVE_TX -static void ssc_txschedule(struct sam_ssc_s *priv, int result) +static void ssc_tx_schedule(struct sam_ssc_s *priv, int result) { int ret; @@ -1295,7 +1544,7 @@ static void ssc_txschedule(struct sam_ssc_s *priv, int result) { /* Schedule the TX DMA done processing to occur on the worker thread. */ - ret = work_queue(HPWORK, &priv->txwork, ssc_txworker, priv, 0); + ret = work_queue(HPWORK, &priv->txwork, ssc_tx_worker, priv, 0); if (ret != 0) { i2slldbg("ERROR: Failed to queue TX work: %d\n", ret); @@ -1305,7 +1554,7 @@ static void ssc_txschedule(struct sam_ssc_s *priv, int result) #endif /**************************************************************************** - * Name: ssc_txcallback + * Name: ssc_txdma_callback * * Description: * This callback function is invoked at the completion of the SSC TX DMA. @@ -1321,7 +1570,7 @@ static void ssc_txschedule(struct sam_ssc_s *priv, int result) ****************************************************************************/ #ifdef SSC_HAVE_TX -static void ssc_txcallback(DMA_HANDLE handle, void *arg, int result) +static void ssc_txdma_callback(DMA_HANDLE handle, void *arg, int result) { struct sam_ssc_s *priv = (struct sam_ssc_s *)arg; DEBUGASSERT(priv != NULL); @@ -1334,50 +1583,108 @@ static void ssc_txcallback(DMA_HANDLE handle, void *arg, int result) ssc_txdma_sample(priv, DMA_CALLBACK); + /* REVISIT: We would like to the next DMA started here so that we do not + * get audio glitches at the boundaries between DMA transfers. + * Unfortunately, we cannot call sam_dmasetup() from an interrupt handler! + */ + /* Then schedule completion of the transfer to occur on the worker thread */ - ssc_txschedule(priv, result); + ssc_tx_schedule(priv, result); } #endif /**************************************************************************** - * Name: ssc_frequency + * Name: ssc_rxsamplerate * * Description: - * Set the SSC frequency. + * Set the I2S RX sample rate. NOTE: This will have no effect if (1) the + * driver does not support an I2C receiver or if (2) the sample rate is + * driven by the I2C frame clock. This may also have unexpected side- + * effects of the RX sample is coupled with the TX sample rate. * * Input Parameters: - * dev - Device-specific state data - * frequency - The SSC frequency requested + * dev - Device-specific state data + * rate - The I2S sample rate in samples (not bits) per second * * Returned Value: - * Returns the actual frequency selected + * Returns the resulting bitrate * ****************************************************************************/ -static uint32_t ssc_frequency(struct i2s_dev_s *dev, uint32_t frequency) +static uint32_t ssc_rxsamplerate(FAR struct i2s_dev_s *dev, uint32_t rate) { +#if defined(SSC_HAVE_RX) && defined(SSC_HAVE_MCK) struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; - uint32_t actual; - uint32_t regval; + DEBUGASSERT(priv && priv->samplerate > 0 && rate > 0); + + /* Check if the receiver is driven by the MCK/2 */ + + if (priv->rxclk == SSC_CLKSRC_MCKDIV) + { + /* Save the new sample rate and update the MCK/2 divider */ - i2svdbg("Frequency=%d\n", frequency); -#warning Missing logic + priv->samplerate = rate; + return ssc_mck2divider(priv); + } +#endif - i2sdbg("Frequency %d->%d\n", frequency, actual); - return actual; + return 0; } /**************************************************************************** - * Name: ssc_send + * Name: ssc_rxdatawidth * * Description: - * Send a block of data on I2S. + * Set the I2S RX data width. The RX bitrate is determined by + * sample_rate * data_width. * * 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. + * dev - Device-specific state data + * width - The I2S data with in bits. + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +static uint32_t ssc_rxdatawidth(FAR struct i2s_dev_s *dev, int bits) +{ +#ifdef SSC_HAVE_RX + struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; + DEBUGASSERT(priv && bits > 1); + + /* Save the new data width */ + + priv->datalen = bits; + +#ifdef SSC_HAVE_MCK + /* Check if the receiver is driven by the MCK/2 */ + + if (priv->rxclk == SSC_CLKSRC_MCKDIV) + { + /* Update the MCK/2 divider */ + + priv->samplerate = rate; + return ssc_mck2divider(priv); + } +#endif +#endif + + return 0; +} + +/**************************************************************************** + * Name: ssc_receive + * + * Description: + * 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. * 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. @@ -1399,9 +1706,9 @@ static uint32_t ssc_frequency(struct i2s_dev_s *dev, uint32_t frequency) * ****************************************************************************/ -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) { struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; struct sam_buffer_s *bfcontainer; @@ -1411,21 +1718,21 @@ static int ssc_send(FAR struct i2s_dev_s *dev, FAR const void *buffer, i2svdbg("buffer=%p nbytes=%d\n", buffer, (int)nbytes); DEBUGASSERT(priv); -#ifdef SSC_HAVE_TX +#ifdef SSC_HAVE_RX /* Allocate a buffer container in advance */ - bfcontainer = ssc_bfallocate(priv); + bfcontainer = ssc_buf_allocate(priv); DEBUGASSERT(bfcontainer); /* Get exclusive access to the SSC driver data */ - ssc_exclsemtake(priv); + ssc_exclsem_take(priv); - /* Has the TX channel been configured? */ + /* Has the RX channel been configured? */ - if (!priv->tx) + if (!priv->rx) { - i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno); + i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno); ret = -EAGAIN; goto errout_with_exclsem; } @@ -1435,47 +1742,126 @@ static int ssc_send(FAR struct i2s_dev_s *dev, FAR const void *buffer, 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->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 TX pending queue */ + /* Add the buffer container to the end of the RX pending queue */ flags = irqsave(); - sq_addlast((sq_entry_t *)bfcontainer, &priv->txpend); + 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_txdmasetup(priv); + ret = ssc_rxdma_setup(priv); DEBUGASSERT(ret == OK); irqrestore(flags); - ssc_exclsemgive(priv); + ssc_exclsem_give(priv); return OK; errout_with_exclsem: - ssc_exclsemgive(priv); - ssc_bffree(priv, bfcontainer); + ssc_exclsem_give(priv); + ssc_buf_free(priv, bfcontainer); return ret; #else - i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno); + i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno); return -ENOSYS; #endif } /**************************************************************************** - * Name: ssc_receive + * Name: ssc_txsamplerate * * Description: - * Receive a block of data from I2S. + * Set the I2S TX sample rate. NOTE: This will have no effect if (1) the + * driver does not support an I2C transmitter or if (2) the sample rate is + * driven by the I2C frame clock. This may also have unexpected side- + * effects of the TX sample is coupled with the RX sample rate. * * 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 + * rate - The I2S sample rate in samples (not bits) per second + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +static uint32_t ssc_txsamplerate(FAR struct i2s_dev_s *dev, uint32_t rate) +{ +#if defined(SSC_HAVE_TX) && defined(SSC_HAVE_MCK) + struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; + DEBUGASSERT(priv && priv->samplerate > 0 && rate > 0); + + /* Check if the receiver is driven by the MCK/2 */ + + if (priv->txclk == SSC_CLKSRC_MCKDIV) + { + /* Save the new sample rate and update the MCK/2 divider */ + + priv->samplerate = rate; + return ssc_mck2divider(priv); + } +#endif + + return 0; +} + +/**************************************************************************** + * Name: ssc_txdatawidth + * + * Description: + * Set the I2S TX data width. The TX bitrate is determined by + * sample_rate * data_width. + * + * Input Parameters: + * dev - Device-specific state data + * width - The I2S data with in bits. + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +static uint32_t ssc_txdatawidth(FAR struct i2s_dev_s *dev, int bits) +{ +#ifdef SSC_HAVE_TX + struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; + DEBUGASSERT(priv && bits > 1); + + /* Save the new data width */ + + priv->datalen = bits; + +#ifdef SSC_HAVE_MCK + /* Check if the receiver is driven by the MCK/2 */ + + if (priv->txclk == SSC_CLKSRC_MCKDIV) + { + /* Update the MCK/2 divider */ + + priv->samplerate = rate; + return ssc_mck2divider(priv); + } +#endif +#endif + + return 0; +} + +/**************************************************************************** + * Name: ssc_send + * + * Description: + * 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. @@ -1497,9 +1883,9 @@ errout_with_exclsem: * ****************************************************************************/ -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) +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; @@ -1509,21 +1895,21 @@ static int ssc_receive(FAR struct i2s_dev_s *dev, FAR void *buffer, i2svdbg("buffer=%p nbytes=%d\n", buffer, (int)nbytes); DEBUGASSERT(priv); -#ifdef SSC_HAVE_RX +#ifdef SSC_HAVE_TX /* Allocate a buffer container in advance */ - bfcontainer = ssc_bfallocate(priv); + bfcontainer = ssc_buf_allocate(priv); DEBUGASSERT(bfcontainer); /* Get exclusive access to the SSC driver data */ - ssc_exclsemtake(priv); + ssc_exclsem_take(priv); - /* Has the RX channel been configured? */ + /* Has the TX channel been configured? */ - if (!priv->rx) + if (!priv->tx) { - i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno); + i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno); ret = -EAGAIN; goto errout_with_exclsem; } @@ -1533,32 +1919,32 @@ static int ssc_receive(FAR struct i2s_dev_s *dev, FAR void *buffer, 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->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 RX pending queue */ + /* Add the buffer container to the end of the TX pending queue */ flags = irqsave(); - sq_addlast((sq_entry_t *)bfcontainer, &priv->rxpend); + 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_rxdmasetup(priv); + ret = ssc_txdma_setup(priv); DEBUGASSERT(ret == OK); irqrestore(flags); - ssc_exclsemgive(priv); + ssc_exclsem_give(priv); return OK; errout_with_exclsem: - ssc_exclsemgive(priv); - ssc_bffree(priv, bfcontainer); + ssc_exclsem_give(priv); + ssc_buf_free(priv, bfcontainer); return ret; #else - i2sdbg("ERROR: SSC%d has no receiver\n", priv->sscno); + i2sdbg("ERROR: SSC%d has no transmitter\n", priv->sscno); return -ENOSYS; #endif } @@ -1597,9 +1983,11 @@ static int ssc_rx_configure(struct sam_ssc_s *priv) break; case SSC_CLKSRC_MCKDIV: /* Clock source is MCK divided down */ - DEBUGASSERT(priv->frequency != 0); +#ifdef SSC_HAVE_MCK2 + DEBUGASSERT(priv->samplerate > 0); regval = SSC_RCMR_CKS_MCK; break; +#endif case SSC_CLKSRC_NONE: /* No clock */ default: @@ -1693,9 +2081,11 @@ static int ssc_tx_configure(struct sam_ssc_s *priv) break; case SSC_CLKSRC_MCKDIV: /* Clock source is MCK divided down */ - DEBUGASSERT(priv->frequency != 0); +#ifdef SSC_HAVE_MCK2 + DEBUGASSERT(priv->samplerate > 0); regval = SSC_TCMR_CKS_MCK; break; +#endif case SSC_CLKSRC_NONE: /* No clock */ default: @@ -1802,6 +2192,214 @@ static int ssc_tx_configure(struct sam_ssc_s *priv) } /**************************************************************************** + * Name: ssc_mck2divider + * + * Description: + * Setup the MCK/2 divider based on the currently selected data width and + * the sample rate + * + * Input Parameter: + * priv - I2C device structure (only the sample rate and data length is + * needed at this point). + * + * Returned Value: + * The current bitrate + * + ****************************************************************************/ + +static uint32_t ssc_mck2divider(struct sam_ssc_s *priv) +{ +#ifdef SSC_HAVE_MCK + struct sam_ssc_s *priv = (struct sam_ssc_s *)dev; + uint32_t bitrate; + uint32_t regval; + DEBUGASSERT(priv && priv->samplerate > 0 && rate > 0); + + /* A zero sample rate means to disable the MCK/2 clock */ + + if (priv->samplerate == 0) + { + bitrate = 0; + regval = 0; + } + else + { + /* Calculate the new bitrate in Hz */ + + bitrate = priv->samplerate * priv->datalen; + + /* Calculate the new MCK/2 divider from the bitrate */ + + regval = BOARD_MCK_FREQUENCY / (bitrate << 1); + } + + /* Configure MCK/2 divider */ + + ssc_putreg(priv, SAM_SSC_CMR_OFFSET, regval); + return bitrate; +#else + return 0; +#endif +} + +/**************************************************************************** + * Name: ssc_clocking + * + * Description: + * Enable and configure clocking to the SSC + * + * Input Parameter: + * priv - Partially initialized I2C device structure (only the PID is + * needed at this point). + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void ssc_clocking(struct sam_ssc_s *priv) +{ + uint32_t regval; + + /* Set the maximum SSC peripheral clock frequency */ + + regval = PMC_PCR_PID(priv->pid) | PMC_PCR_CMD | SSC_PCR_DIV | PMC_PCR_EN; + putreg32(regval, SAM_PMC_PCR); + + /* Reset, disable receiver & transmitter */ + + ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_RXDIS | SSC_CR_TXDIS | SSC_CR_SWRST); + + /* Configure MCK/2 divider */ + + (void)ssc_mck2divider(priv); + + /* Enable peripheral clocking */ + + sam_enableperiph1(priv->pid); +} + +/**************************************************************************** + * Name: ssc_dma_allocate + * + * Description: + * Allocate SCC DMA channels + * + * Input Parameters: + * priv - Partially initialized I2C device structure. This function + * will complete the DMA specific portions of the initialization + * + * Returned Value: + * OK on success; A negated errno value on failure. + * + ****************************************************************************/ + +static int ssc_dma_allocate(struct sam_ssc_s *priv) +{ + /* Allocate DMA channels. These allocations exploit that fact that + * SSC0 is managed by DMAC0 and SSC1 is managed by DMAC1. Hence, + * the SSC number (sscno) is the same as the DMAC number. + */ + +#ifdef SSC_HAVE_RX + if (priv->rx) + { + /* Allocate an RX DMA channel */ + + priv->rxdma = sam_dmachannel(priv->sscno, DMA_FLAGS(priv->pid)); + if (!priv->rxdma) + { + i2sdbg("ERROR: Failed to allocate the RX DMA channel\n"); + goto errout; + } + + /* 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; + } + } +#endif + +#ifdef SSC_HAVE_TX + if (priv->tx) + { + /* Allocate a TX DMA channel */ + + priv->txdma = sam_dmachannel(priv->sscno, DMA_FLAGS(priv->pid)); + if (!priv->txdma) + { + i2sdbg("ERROR: Failed to allocate the TX DMA channel\n"); + goto errout; + } + + /* 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; + } + } +#endif + + /* Success exit */ + + return OK; + + /* Error exit */ + +errout: + ssc_dma_free(priv); + return -ENOMEM; +} + +/**************************************************************************** + * Name: ssc_dma_free + * + * Description: + * Release DMA-related resources allocated by ssc_dma_allocate() + * + * Input Parameters: + * priv - Partially initialized I2C device structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void ssc_dma_free(struct sam_ssc_s *priv) +{ +#ifdef SSC_HAVE_TX + if (priv->txdog) + { + wd_delete(priv->txdog); + } + + if (priv->txdma) + { + sam_dmafree(priv->txdma); + } +#endif + +#ifdef SSC_HAVE_RX + if (priv->rxdog) + { + wd_delete(priv->rxdog); + } + + if (priv->rxdma) + { + sam_dmafree(priv->rxdma); + } +#endif +} + +/**************************************************************************** * Name: ssc0/1_configure * * Description: @@ -1809,7 +2407,7 @@ static int ssc_tx_configure(struct sam_ssc_s *priv) * * Input Parameters: * priv - Partially initialized I2C device structure. These functions - * will compolete the SSC specific portions of the initialization + * will complete the SSC specific portions of the initialization * * Returned Value: * None @@ -1921,13 +2519,11 @@ static void ssc0_configure(struct sam_ssc_s *priv) /* Does the receiver or transmitter need to have the MCK divider set up? */ -#if (defined(CONFIG_SAMA5_SSC0_RX) && defined(CONFIG_SAMA5_SSC0_RX_MCKDIV)) || \ - (defined(CONFIG_SAMA5_SSC0_TX) && defined(CONFIG_SAMA5_SSC0_TX_MCKDIV)) +#if defined(SSC0_HAVE_MCK2) + priv->samplerate = CONFIG_SAMA5_SSC0_MCKDIV_SAMPLERATE; - priv->frequency = CONFIG_SAMA5_SSC0_MCKDIV_FREQUENCY; - -#else - priv->frequency = 0; +#elif defined(SSC_HAVE_MCK2) + priv->samplerate = 0; #endif @@ -2049,13 +2645,11 @@ static void ssc1_configure(struct sam_ssc_s *priv) /* Does the receiver or transmitter need to have the MCK divider set up? */ -#if (defined(CONFIG_SAMA5_SSC1_RX) && defined(CONFIG_SAMA5_SSC1_RX_MCKDIV)) || \ - (defined(CONFIG_SAMA5_SSC1_TX) && defined(CONFIG_SAMA5_SSC1_TX_MCKDIV)) - - priv->frequency = CONFIG_SAMA5_SSC1_MCKDIV_FREQUENCY; +#if defined(SSC1_HAVE_MCK2) + priv->samplerate = CONFIG_SAMA5_SSC1_MCKDIV_SAMPLERATE; -#else - priv->frequency = 0; +#elif defined(SSC_HAVE_MCK2) + priv->samplerate = 0; #endif @@ -2094,7 +2688,6 @@ struct i2s_dev_s *sam_ssc_initialize(int port) { struct sam_ssc_s *priv; irqstate_t flags; - uint32_t regval; int ret; /* The support SAM parts have only a single SSC port */ @@ -2125,7 +2718,7 @@ struct i2s_dev_s *sam_ssc_initialize(int port) /* Initialize buffering */ - ssc_bfinitialize(priv); + ssc_buf_initialize(priv); flags = irqsave(); #ifdef CONFIG_SAMA5_SSC0 @@ -2147,83 +2740,17 @@ struct i2s_dev_s *sam_ssc_initialize(int port) goto errout_with_alloc; } - /* Allocate DMA channels. These allocations exploit that fact that - * SSC0 is managed by DMAC0 and SSC1 is managed by DMAC1. Hence, - * the SSC number (port) is the same as the DMAC number. - */ - -#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 + /* Allocate DMA channels */ -#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_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 I2S hardware */ - /* Set the maximum SSC peripheral clock frequency */ - - regval = PMC_PCR_PID(priv->pid) | PMC_PCR_CMD | SSC_PCR_DIV | PMC_PCR_EN; - putreg32(regval, SAM_PMC_PCR); - - /* Reset, disable receiver & transmitter */ - - ssc_putreg(priv, SAM_SSC_CR_OFFSET, SSC_CR_RXDIS | SSC_CR_TXDIS | SSC_CR_SWRST); - - /* Configure MCK/2 divider */ - - if (priv->frequency > 0) - { - regval = SSC_FREQUENCY / (2 * priv->frequency); - } - else + ret = ssc_dma_allocate(priv); + if (ret < 0) { - regval = 0; + goto errout_with_alloc; } - ssc_putreg(priv, SAM_SSC_CMR_OFFSET, regval); - - /* Enable peripheral clocking */ + /* Configure and enable clocking */ - sam_enableperiph1(priv->pid); + ssc_clocking(priv); /* Configure the receiver */ @@ -2243,8 +2770,6 @@ struct i2s_dev_s *sam_ssc_initialize(int port) goto errout_with_clocking; } - /* Configure DMA */ -#warning "Missing logic" irqrestore(flags); ssc_dumpregs(priv, "After initialization"); @@ -2256,35 +2781,7 @@ 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); - } -#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); - } -#endif + ssc_dma_free(priv); errout_with_alloc: sem_destroy(&priv->exclsem); diff --git a/nuttx/include/nuttx/audio/i2s.h b/nuttx/include/nuttx/audio/i2s.h index e9df057fa..71a3a435b 100644 --- a/nuttx/include/nuttx/audio/i2s.h +++ b/nuttx/include/nuttx/audio/i2s.h @@ -51,37 +51,57 @@ /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ -/* Configuration ************************************************************/ - /* Access macros ************************************************************/ /**************************************************************************** - * Name: I2S_FREQUENCY + * Name: I2S_RXSAMPLERATE * * Description: - * Set the I2S frequency. Required. + * Set the I2S RX sample rate. NOTE: This will have no effect if (1) the + * driver does not support an I2C receiver or if (2) the sample rate is + * driven by the I2C frame clock. This may also have unexpected side- + * effects of the RX sample is coupled with the TX sample rate. * * Input Parameters: - * dev - Device-specific state data - * frequency - The I2S frequency requested + * dev - Device-specific state data + * rate - The I2S sample rate in samples (not bits) per second * * Returned Value: - * Returns the actual frequency selected + * Returns the resulting bitrate * ****************************************************************************/ -#define I2S_FREQUENCY(d,f) ((d)->ops->i2s_frequency(d,f)) +#define I2S_RXSAMPLERATE(d,f) ((d)->ops->i2s_rxsamplerate(d,r)) /**************************************************************************** - * Name: I2S_SEND + * Name: I2S_RXDATAWIDTH * * Description: - * Send a block of data on I2S. + * Set the I2S RX data width. The RX bitrate is determined by + * sample_rate * data_width. * * 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. + * dev - Device-specific state data + * width - The I2S data with in bits. + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +#define I2S_RXDATAWIDTH(d,b) ((d)->ops->i2s_rxdatawidth(d,b)) + +/**************************************************************************** + * Name: I2S_RECEIVE + * + * Description: + * 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. * 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. @@ -103,19 +123,56 @@ * ****************************************************************************/ -#define I2S_SEND(d,b,n,c,a,t) ((d)->ops->i2s_send(d,b,n,c,a,t)) +#define I2S_RECEIVE(d,b,n,c,a,t) ((d)->ops->i2s_receive(d,b,n,c,a,t)) /**************************************************************************** - * Name: I2S_RECEIVE + * Name: I2S_TXSAMPLERATE * * Description: - * Receive a block of data from I2S. + * Set the I2S TX sample rate. NOTE: This will have no effect if (1) the + * driver does not support an I2C transmitter or if (2) the sample rate is + * driven by the I2C frame clock. This may also have unexpected side- + * effects of the TX sample is coupled with the RX sample rate. * * 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 + * rate - The I2S sample rate in samples (not bits) per second + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +#define I2S_TXSAMPLERATE(d,f) ((d)->ops->i2s_txsamplerate(d,r)) + +/**************************************************************************** + * Name: I2S_TXDATAWIDTH + * + * Description: + * Set the I2S TX data width. The TX bitrate is determined by + * sample_rate * data_width. + * + * Input Parameters: + * dev - Device-specific state data + * width - The I2S data with in bits. + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +#define I2S_TXDATAWIDTH(d,b) ((d)->ops->i2s_txdatawidth(d,b)) + +/**************************************************************************** + * Name: I2S_SEND + * + * Description: + * 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. @@ -137,7 +194,7 @@ * ****************************************************************************/ -#define I2S_RECEIVE(d,b,n,c,a,t) ((d)->ops->i2s_receive(d,b,n,c,a,t)) +#define I2S_SEND(d,b,n,c,a,t) ((d)->ops->i2s_send(d,b,n,c,a,t)) /**************************************************************************** * Public Types @@ -152,13 +209,21 @@ typedef CODE void (*i2s_callback_t)(FAR struct i2s_dev_s *dev, struct i2s_dev_s; struct i2s_ops_s { - uint32_t (*i2s_frequency)(FAR struct i2s_dev_s *dev, uint32_t frequency); - 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); + /* Receiver methods */ + + uint32_t (*i2s_rxsamplerate)(FAR struct i2s_dev_s *dev, uint32_t rate); + uint32_t (*i2s_rxdatawidth)(FAR struct i2s_dev_s *dev, int bits); 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); + size_t nbytes, i2s_callback_t callback, FAR void *arg, + uint32_t timeout); + + /* Transmitter methods */ + + uint32_t (*i2s_txsamplerate)(FAR struct i2s_dev_s *dev, uint32_t rate); + uint32_t (*i2s_txdatawidth)(FAR struct i2s_dev_s *dev, int bits); + 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); }; /* I2S private data. This structure only defines the initial fields of the |