nuttx/arch/arm/src/stm32/stm32_i2s.c

2668 lines
75 KiB
C

/****************************************************************************
* arm/arm/src/stm32/stm32_i2s.c
*
* Copyright (C) 2017 Gregory Nutt. All rights reserved.
* Author: Taras Drozdovskiy <t.drozdovskiy@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* The external functions, stm32_spi1/2/3select and stm32_spi1/2/3status
* must be provided by board-specific logic. They are implementations of
* the select and status methods of the SPI interface defined by struct
* spi_ops_s (see include/nuttx/spi/spi.h). All other methods (including
* up_spiinitialize()) are provided by common STM32 logic. To use this
* common SPI logic on your board:
*
* 1. Provide logic in stm32_boardinitialize() to configure I2S chip select
* pins.
* 2. Provide stm32_i2s2/3select() and stm32_i2s2/3status() functions in your
* board-specific logic. These functions will perform chip selection and
* status operations using GPIOs in the way your board is configured.
* 3. Add a calls to up_spiinitialize() in your low level application
* initialization logic
* 4. The handle returned by stm32_i2sdev_initialize() may then be used to
* bind the I2S driver to higher level logic
*
****************************************************c***********************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#include <errno.h>
#include <assert.h>
#include <queue.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/spi/spi.h>
#include <arch/board/board.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/wdog.h>
#include <nuttx/wqueue.h>
#include <nuttx/audio/audio.h>
#include <nuttx/audio/i2s.h>
#include "up_internal.h"
#include "up_arch.h"
#include "stm32_dma.h"
#include "stm32_spi.h"
#include "stm32_rcc.h"
#if defined(CONFIG_STM32_I2S2) || defined(CONFIG_STM32_I2S3)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_SCHED_WORKQUEUE
# error Work queue support is required (CONFIG_SCHED_WORKQUEUE)
#endif
#ifndef CONFIG_AUDIO
# error CONFIG_AUDIO required by this driver
#endif
#ifndef CONFIG_STM32_I2S_MAXINFLIGHT
# define CONFIG_STM32_I2S_MAXINFLIGHT 16
#endif
/* Assume no RX/TX support until we learn better */
#undef I2S_HAVE_RX
#undef I2S_HAVE_TX
/* Check for I2S RX support */
# if defined(CONFIG_STM32_I2S3_RX)
# define I2S_HAVE_RX 1
# ifdef CONFIG_STM32_I2S_MCK
# define I2S_HAVE_MCK 1
# endif
# endif
/* Check for I2S3 TX support */
# if defined(CONFIG_STM32_I2S3_TX)
# define I2S_HAVE_TX 1
# ifdef CONFIG_STM32_I2S_MCK
# define I2S_HAVE_MCK 1
# endif
# endif
/* Configuration ********************************************************************/
/* I2S interrupts */
#ifdef CONFIG_STM32_SPI_INTERRUPTS
# error "Interrupt driven I2S not yet supported"
#endif
/* Can't have both interrupt driven SPI and SPI DMA */
#if defined(CONFIG_STM32_SPI_INTERRUPTS) && defined(CONFIG_STM32_SPI_DMA)
# error "Cannot enable both interrupt mode and DMA mode for SPI"
#endif
/* SPI DMA priority */
#ifdef CONFIG_STM32_SPI_DMA
# if defined(CONFIG_SPI_DMAPRIO)
# define SPI_DMA_PRIO CONFIG_SPI_DMAPRIO
# elif defined(CONFIG_STM32_STM32F10XX) || defined(CONFIG_STM32_STM32L15XX)
# define SPI_DMA_PRIO DMA_CCR_PRIMED
# elif defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F4XXX)
# define SPI_DMA_PRIO DMA_SCR_PRIMED
# else
# error "Unknown STM32 DMA"
# endif
# if defined(CONFIG_STM32_STM32F10XX) || defined(CONFIG_STM32_STM32L15XX)
# if (SPI_DMA_PRIO & ~DMA_CCR_PL_MASK) != 0
# error "Illegal value for CONFIG_SPI_DMAPRIO"
# endif
# elif defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F4XXX)
# if (SPI_DMA_PRIO & ~DMA_SCR_PL_MASK) != 0
# error "Illegal value for CONFIG_SPI_DMAPRIO"
# endif
# else
# error "Unknown STM32 DMA"
# endif
#endif
/* DMA channel configuration */
#if defined(CONFIG_STM32_STM32F10XX) || defined(CONFIG_STM32_STM32F30XX) || \
defined(CONFIG_STM32_STM32L15XX)
# define SPI_RXDMA16_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_16BITS|DMA_CCR_PSIZE_16BITS|DMA_CCR_MINC )
# define SPI_RXDMA8_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_8BITS |DMA_CCR_MINC )
# define SPI_RXDMA16NULL_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_16BITS )
# define SPI_RXDMA8NULL_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_8BITS )
# define SPI_TXDMA16_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_16BITS|DMA_CCR_PSIZE_16BITS|DMA_CCR_MINC|DMA_CCR_DIR)
# define SPI_TXDMA8_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_8BITS |DMA_CCR_MINC|DMA_CCR_DIR)
# define SPI_TXDMA16NULL_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_16BITS |DMA_CCR_DIR)
# define SPI_TXDMA8NULL_CONFIG (SPI_DMA_PRIO|DMA_CCR_MSIZE_8BITS |DMA_CCR_PSIZE_8BITS |DMA_CCR_DIR)
#elif defined(CONFIG_STM32_STM32F20XX) || defined(CONFIG_STM32_STM32F4XXX)
# define SPI_RXDMA16_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_16BITS|DMA_SCR_PSIZE_16BITS|DMA_SCR_MINC|DMA_SCR_DIR_P2M)
# define SPI_RXDMA8_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_8BITS |DMA_SCR_PSIZE_8BITS |DMA_SCR_MINC|DMA_SCR_DIR_P2M)
# define SPI_RXDMA16NULL_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_8BITS |DMA_SCR_PSIZE_16BITS |DMA_SCR_DIR_P2M)
# define SPI_RXDMA8NULL_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_8BITS |DMA_SCR_PSIZE_8BITS |DMA_SCR_DIR_P2M)
# define SPI_TXDMA16_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_16BITS|DMA_SCR_PSIZE_16BITS|DMA_SCR_MINC|DMA_SCR_DIR_M2P)
# define SPI_TXDMA8_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_8BITS |DMA_SCR_PSIZE_8BITS |DMA_SCR_MINC|DMA_SCR_DIR_M2P)
# define SPI_TXDMA16NULL_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_8BITS |DMA_SCR_PSIZE_16BITS |DMA_SCR_DIR_M2P)
# define SPI_TXDMA8NULL_CONFIG (SPI_DMA_PRIO|DMA_SCR_MSIZE_8BITS |DMA_SCR_PSIZE_8BITS |DMA_SCR_DIR_M2P)
#else
# error "Unknown STM32 DMA"
#endif
/* Debug *******************************************************************/
/* Check if SSC debug is enabled (non-standard.. no support in
* include/debug.h
*/
#ifndef CONFIG_DEBUG_I2S_INFO
# undef CONFIG_STM32_I2S_DMADEBUG
# undef CONFIG_STM32_I2S_REGDEBUG
# undef CONFIG_STM32_I2S_QDEBUG
# undef CONFIG_STM32_I2S_DUMPBUFFERS
#endif
/* The I2S can handle most any bit width from 8 to 32. However, the DMA
* logic here is constrained to byte, half-word, and word sizes.
*/
#ifndef CONFIG_STM32_I2S3_DATALEN
# define CONFIG_STM32_I2S3_DATALEN 16
#endif
#if CONFIG_STM32_I2S3_DATALEN == 8
# define STM32_I2S3_DATAMASK 0
#elif CONFIG_STM32_I2S3_DATALEN == 16
# define STM32_I2S3_DATAMASK 1
#elif CONFIG_STM32_I2S3_DATALEN < 8 || CONFIG_STM32_I2S3_DATALEN > 16
# error Invalid value for CONFIG_STM32_I2S3_DATALEN
#else
# error Valid but supported value for CONFIG_STM32_I2S3_DATALEN
#endif
/* Check if we need to build RX and/or TX support */
#if defined(I2S_HAVE_RX) || defined(I2S_HAVE_TX)
#ifndef CONFIG_DEBUG_DMA
# undef CONFIG_STM32_I2S_DMADEBUG
#endif
#define DMA_INITIAL 0
#define DMA_AFTER_SETUP 1
#define DMA_AFTER_START 2
#define DMA_CALLBACK 3
#define DMA_TIMEOUT 3
#define DMA_END_TRANSFER 4
#define DMA_NSAMPLES 5
/****************************************************************************
* Private Types
****************************************************************************/
/* I2S buffer container */
struct stm32_buffer_s
{
struct stm32_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 */
struct ap_buffer_s *apb; /* The audio buffer */
int result; /* The result of the transfer */
};
/* This structure describes the state of one receiver or transmitter transport */
struct stm32_transport_s
{
DMA_HANDLE dma; /* I2S DMA handle */
WDOG_ID dog; /* Watchdog that handles DMA timeouts */
sq_queue_t pend; /* A queue of pending transfers */
sq_queue_t act; /* A queue of active transfers */
sq_queue_t done; /* A queue of completed transfers */
struct work_s work; /* Supports worker thread operations */
#ifdef CONFIG_STM32_I2S_DMADEBUG
struct stm32_dmaregs_s dmaregs[DMA_NSAMPLES];
#endif
};
/* The state of the one I2S peripheral */
struct stm32_i2s_s
{
struct i2s_dev_s dev; /* Externally visible I2S interface */
uintptr_t base; /* I2S controller register base address */
sem_t exclsem; /* Assures mutually exclusive acess to I2S */
bool initialized; /* Has I2S interface been initialized */
uint8_t datalen; /* Data width (8 or 16) */
#ifdef CONFIG_DEBUG_FEATURES
uint8_t align; /* Log2 of data width (0 or 1) */
#endif
uint8_t rxenab:1; /* True: RX transfers enabled */
uint8_t txenab:1; /* True: TX transfers enabled */
uint8_t i2sno:6; /* I2S controller number (0 or 1) */
#ifdef I2S_HAVE_MCK
uint32_t samplerate; /* Data sample rate (determines only MCK divider) */
#endif
uint32_t rxccr; /* DMA control register for RX transfers */
uint32_t txccr; /* DMA control register for TX transfers */
#ifdef I2S_HAVE_RX
struct stm32_transport_s rx; /* RX transport state */
#endif
#ifdef I2S_HAVE_TX
struct stm32_transport_s tx; /* TX transport state */
#endif
/* Pre-allocated pool of buffer containers */
sem_t bufsem; /* Buffer wait semaphore */
struct stm32_buffer_s *freelist; /* A list a free buffer containers */
struct stm32_buffer_s containers[CONFIG_STM32_I2S_MAXINFLIGHT];
/* Debug stuff */
#ifdef CONFIG_STM32_I2S_REGDEBUG
bool wr; /* Last was a write */
uint32_t regaddr; /* Last address */
uint16_t regval; /* Last value */
int count; /* Number of times */
#endif /* CONFIG_STM32_I2S_REGDEBUG */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Register helpers */
#ifdef CONFIG_STM32_I2S_REGDEBUG
static bool i2s_checkreg(struct stm32_i2s_s *priv, bool wr, uint16_t regval,
uint32_t regaddr);
#else
# define i2s_checkreg(priv,wr,regval,regaddr) (false)
#endif
static inline uint16_t i2s_getreg(struct stm32_i2s_s *priv, uint8_t offset);
static inline void i2s_putreg(struct stm32_i2s_s *priv, uint8_t offset,
uint16_t regval);
#if defined(CONFIG_DEBUG_I2S_INFO)
static void i2s_dump_regs(struct stm32_i2s_s *priv, const char *msg);
#else
# define i2s_dump_regs(s,m)
#endif
#ifdef CONFIG_STM32_I2S_DUMPBUFFERS
# define i2s_init_buffer(b,s) memset(b, 0x55, s);
# define i2s_dump_buffer(m,b,s) lib_dumpbuffer(m,b,s)
#else
# define i2s_init_buffer(b,s)
# define i2s_dump_buffer(m,b,s)
#endif
/* Semaphore helpers */
static void i2s_exclsem_take(struct stm32_i2s_s *priv);
#define i2s_exclsem_give(priv) nxsem_post(&priv->exclsem)
static void i2s_bufsem_take(struct stm32_i2s_s *priv);
#define i2s_bufsem_give(priv) nxsem_post(&priv->bufsem)
/* Buffer container helpers */
static struct stm32_buffer_s *
i2s_buf_allocate(struct stm32_i2s_s *priv);
static void i2s_buf_free(struct stm32_i2s_s *priv,
struct stm32_buffer_s *bfcontainer);
static void i2s_buf_initialize(struct stm32_i2s_s *priv);
/* DMA support */
#ifdef CONFIG_STM32_I2S_DMADEBUG
static void i2s_dma_sampleinit(struct stm32_i2s_s *priv,
struct stm32_transport_s *xpt);
#endif
#if defined(CONFIG_STM32_I2S_DMADEBUG) && defined(I2S_HAVE_RX)
# define i2s_rxdma_sample(s,i) stm32_dmasample((s)->rx.dma, &(s)->rx.dmaregs[i])
# define i2s_rxdma_sampleinit(s) i2s_dma_sampleinit(s, &(s)->rx)
static void i2s_rxdma_sampledone(struct stm32_i2s_s *priv, int result);
#else
# define i2s_rxdma_sample(s,i)
# define i2s_rxdma_sampleinit(s)
# define i2s_rxdma_sampledone(s,r)
#endif
#if defined(CONFIG_STM32_I2S_DMADEBUG) && defined(I2S_HAVE_TX)
# define i2s_txdma_sample(s,i) stm32_dmasample((s)->tx.dma, &(s)->tx.dmaregs[i])
# define i2s_txdma_sampleinit(s) i2s_dma_sampleinit(s, &(s)->tx)
static void i2s_txdma_sampledone(struct stm32_i2s_s *priv, int result);
#else
# define i2s_txdma_sample(s,i)
# define i2s_txdma_sampleinit(s)
# define i2s_txdma_sampledone(s,r)
#endif
#ifdef I2S_HAVE_RX
static void i2s_rxdma_timeout(int argc, uint32_t arg);
static int i2s_rxdma_setup(struct stm32_i2s_s *priv);
static void i2s_rx_worker(void *arg);
static void i2s_rx_schedule(struct stm32_i2s_s *priv, int result);
static void i2s_rxdma_callback(DMA_HANDLE handle, uint8_t result, void *arg);
#endif
#ifdef I2S_HAVE_TX
static void i2s_txdma_timeout(int argc, uint32_t arg);
static int i2s_txdma_setup(struct stm32_i2s_s *priv);
static void i2s_tx_worker(void *arg);
static void i2s_tx_schedule(struct stm32_i2s_s *priv, int result);
static void i2s_txdma_callback(DMA_HANDLE handle, uint8_t result, void *arg);
#endif
/* I2S methods (and close friends) */
static int i2s_checkwidth(struct stm32_i2s_s *priv, int bits);
static uint32_t stm32_i2s_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate);
static uint32_t stm32_i2s_rxdatawidth(struct i2s_dev_s *dev, int bits);
static int stm32_i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg, uint32_t timeout);
static uint32_t stm32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate);
static uint32_t stm32_i2s_txdatawidth(struct i2s_dev_s *dev, int bits);
static int stm32_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg,
uint32_t timeout);
/* Initialization */
static uint32_t i2s_mckdivider(struct stm32_i2s_s *priv);
static int i2s_dma_flags(struct stm32_i2s_s *priv);
static int i2s_dma_allocate(struct stm32_i2s_s *priv);
static void i2s_dma_free(struct stm32_i2s_s *priv);
#ifdef CONFIG_STM32_I2S2
static void i2s2_configure(struct stm32_i2s_s *priv);
#endif
#ifdef CONFIG_STM32_I2S3
static void i2s3_configure(struct stm32_i2s_s *priv);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/* I2S device operations */
static const struct i2s_ops_s g_i2sops =
{
/* Receiver methods */
.i2s_rxsamplerate = stm32_i2s_rxsamplerate,
.i2s_rxdatawidth = stm32_i2s_rxdatawidth,
.i2s_receive = stm32_i2s_receive,
/* Transmitter methods */
.i2s_txsamplerate = stm32_i2s_txsamplerate,
.i2s_txdatawidth = stm32_i2s_txdatawidth,
.i2s_send = stm32_i2s_send,
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: i2s_checkreg
*
* Description:
* Check if the current register access is a duplicate of the preceding.
*
* Input Parameters:
* regval - The value to be written
* regaddr - The address of the register to write to
*
* Returned Value:
* true: This is the first register access of this type.
* flase: This is the same as the preceding register access.
*
****************************************************************************/
#ifdef CONFIG_STM32_I2S_REGDEBUG
static bool i2s_checkreg(struct stm32_i2s_s *priv, bool wr, uint16_t regval,
uint32_t regaddr)
{
if (wr == priv->wr && /* Same kind of access? */
regval == priv->regval && /* Same value? */
regaddr == priv->regaddr) /* Same address? */
{
/* Yes, then just keep a count of the number of times we did this. */
priv->count++;
return false;
}
else
{
/* Did we do the previous operation more than once? */
if (priv->count > 0)
{
/* Yes... show how many times we did it */
i2sinfo("...[Repeats %d times]...\n", priv->count);
}
/* Save information about the new access */
priv->wr = wr;
priv->regval = regval;
priv->regaddr = regaddr;
priv->count = 0;
}
/* Return true if this is the first time that we have done this operation */
return true;
}
#endif
/****************************************************************************
* Name: i2s_getreg
*
* Description:
* Get the contents of the I2S register at offset
*
* Input Parameters:
* priv - private I2S device structure
* offset - offset to the register of interest
*
* Returned Value:
* The contents of the 16-bit register
*
****************************************************************************/
static inline uint16_t i2s_getreg(FAR struct stm32_i2s_s *priv,
uint8_t offset)
{
uint32_t regaddr = priv->base + offset;
uint16_t regval = getreg16(regaddr);
#ifdef CONFIG_STM32_I2S_REGDEBUG
if (i2s_checkreg(priv, false, regval, regaddr))
{
i2sinfo("%08x->%04x\n", regaddr, regval);
}
#endif
return regval;
}
/****************************************************************************
* Name: spi_putreg
*
* Description:
* Write a 16-bit value to the SPI register at offset
*
* Input Parameters:
* priv - private SPI device structure
* offset - offset to the register of interest
* value - the 16-bit value to be written
*
* Returned Value:
* The contents of the 16-bit register
*
****************************************************************************/
static inline void i2s_putreg(FAR struct stm32_i2s_s *priv, uint8_t offset,
uint16_t regval)
{
uint32_t regaddr = priv->base + offset;
#ifdef CONFIG_STM32_I2S_REGDEBUG
if (i2s_checkreg(priv, true, regval, regaddr))
{
i2sinfo("%08x<-%04x\n", regaddr, regval);
}
#endif
putreg16(regval, regaddr);
}
/****************************************************************************
* Name: i2s_dump_regs
*
* Description:
* Dump the contents of all I2S registers
*
* Input Parameters:
* priv - The I2S controller to dump
* msg - Message to print before the register data
*
* Returned Value:
* None
*
****************************************************************************/
#if defined(CONFIG_DEBUG_I2S)
static void i2s_dump_regs(struct stm32_i2s_s *priv, const char *msg)
{
i2sinfo("I2S%d: %s\n", priv->i2sno, msg);
i2sinfo(" CR1:%04x CR2:%04x SR:%04x DR:%04x\n",
i2s_getreg(priv, STM32_SPI_CR1_OFFSET),
i2s_getreg(priv, STM32_SPI_CR2_OFFSET),
i2s_getreg(priv, STM32_SPI_SR_OFFSET),
i2s_getreg(priv, STM32_SPI_DR_OFFSET));
i2sinfo(" I2SCFGR:%04x I2SPR:%04x\n",
i2s_getreg(priv, STM32_SPI_I2SCFGR_OFFSET),
i2s_getreg(priv, STM32_SPI_I2SPR_OFFSET));
}
#endif
/****************************************************************************
* Name: i2s_exclsem_take
*
* Description:
* Take the exclusive access semaphore handling any exceptional conditions
*
* Input Parameters:
* priv - A reference to the i2s peripheral state
*
* Returned Value:
* None
*
****************************************************************************/
static void i2s_exclsem_take(struct stm32_i2s_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 = nxsem_wait(&priv->exclsem);
DEBUGASSERT(ret == 0 || ret == -EINTR);
}
while (ret == -EINTR);
}
/****************************************************************************
* Name: i2s_bufsem_take
*
* Description:
* Take the buffer semaphore handling any exceptional conditions
*
* Input Parameters:
* priv - A reference to the i2s peripheral state
*
* Returned Value:
* None
*
****************************************************************************/
static void i2s_bufsem_take(struct stm32_i2s_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 = nxsem_wait(&priv->bufsem);
DEBUGASSERT(ret == 0 || ret == -EINTR);
}
while (ret == -EINTR);
}
/****************************************************************************
* Name: i2s_buf_allocate
*
* Description:
* Allocate a buffer container by removing the one at the head of the
* free list
*
* Input Parameters:
* priv - I2S 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 access to the I2S state structure.
* That would result in a deadlock!
*
****************************************************************************/
static struct stm32_buffer_s *i2s_buf_allocate(struct stm32_i2s_s *priv)
{
struct stm32_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.
*/
i2s_bufsem_take(priv);
/* Get the buffer from the head of the free list */
flags = enter_critical_section();
bfcontainer = priv->freelist;
DEBUGASSERT(bfcontainer);
/* Unlink the buffer from the freelist */
priv->freelist = bfcontainer->flink;
leave_critical_section(flags);
return bfcontainer;
}
/****************************************************************************
* Name: i2s_buf_free
*
* Description:
* Free buffer container by adding it to the head of the free list
*
* Input Parameters:
* priv - I2S state instance
* bfcontainer - The buffer container to be freed
*
* Returned Value:
* None
*
* Assumptions:
* The caller has exclusive access to the I2S state structure
*
****************************************************************************/
static void i2s_buf_free(struct stm32_i2s_s *priv, struct stm32_buffer_s *bfcontainer)
{
irqstate_t flags;
/* Put the buffer container back on the free list */
flags = enter_critical_section();
bfcontainer->flink = priv->freelist;
priv->freelist = bfcontainer;
leave_critical_section(flags);
/* Wake up any threads waiting for a buffer container */
i2s_bufsem_give(priv);
}
/****************************************************************************
* Name: i2s_buf_initialize
*
* Description:
* Initialize the buffer container allocator by adding all of the
* pre-allocated buffer containers to the free list
*
* Input Parameters:
* priv - I2S state instance
*
* Returned Value:
* None
*
* Assumptions:
* Called early in I2S initialization so that there are no issues with
* concurrency.
*
****************************************************************************/
static void i2s_buf_initialize(struct stm32_i2s_s *priv)
{
int i;
priv->freelist = NULL;
nxsem_init(&priv->bufsem, 0, CONFIG_STM32_I2S_MAXINFLIGHT);
for (i = 0; i < CONFIG_STM32_I2S_MAXINFLIGHT; i++)
{
i2s_buf_free(priv, &priv->containers[i]);
}
}
/****************************************************************************
* Name: i2s_dma_sampleinit
*
* Description:
* Initialize sampling of DMA registers (if CONFIG_STM32_I2S_DMADEBUG)
*
* Input Parameters:
* priv - I2S state instance
*
* Returned Value:
* None
*
****************************************************************************/
#if defined(CONFIG_STM32_I2S_DMADEBUG)
static void i2s_dma_sampleinit(struct stm32_i2s_s *priv,
struct stm32_transport_s *xpt)
{
/* Put contents of register samples into a known state */
memset(xpt->dmaregs, 0xff, DMA_NSAMPLES * sizeof(struct stm32_dmaregs_s));
/* Then get the initial samples */
stm32_dmasample(xpt->dma, &xpt->dmaregs[DMA_INITIAL]);
}
#endif
/****************************************************************************
* Name: i2s_rxdma_sampledone
*
* Description:
* Dump sampled RX DMA registers
*
* Input Parameters:
* priv - I2S state instance
*
* Returned Value:
* None
*
****************************************************************************/
#if defined(CONFIG_STM32_I2S_DMADEBUG) && defined(I2S_HAVE_RX)
static void i2s_rxdma_sampledone(struct stm32_i2s_s *priv, int result)
{
i2sinfo("result: %d\n", result);
/* Sample the final registers */
stm32_dmasample(priv->rx.dma, &priv->rx.dmaregs[DMA_END_TRANSFER]);
/* Then dump the sampled DMA registers */
/* Initial register values */
stm32_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_INITIAL],
"RX: Initial Registers");
/* Register values after DMA setup */
stm32_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_AFTER_SETUP],
"RX: After DMA Setup");
/* Register values after DMA start */
stm32_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_AFTER_START],
"RX: After DMA Start");
/* Register values at the time of the TX and RX DMA callbacks
* -OR- DMA timeout.
*
* If the DMA timedout, then there will not be any RX DMA
* callback samples. There is probably no TX DMA callback
* samples either, but we don't know for sure.
*/
if (result == -ETIMEDOUT || result == -EINTR)
{
stm32_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_TIMEOUT],
"RX: At DMA timeout");
}
else
{
stm32_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_CALLBACK],
"RX: At DMA callback");
}
stm32_dmadump(priv->rx.dma, &priv->rx.dmaregs[DMA_END_TRANSFER],
"RX: At End-of-Transfer");
i2s_dump_regs(priv, "RX: At End-of-Transfer");
}
#endif
/****************************************************************************
* Name: i2s_txdma_sampledone
*
* Description:
* Dump sampled DMA registers
*
* Input Parameters:
* priv - I2S state instance
*
* Returned Value:
* None
*
****************************************************************************/
#if defined(CONFIG_STM32_I2S_DMADEBUG) && defined(I2S_HAVE_TX)
static void i2s_txdma_sampledone(struct stm32_i2s_s *priv, int result)
{
i2sinfo("result: %d\n", result);
/* Sample the final registers */
stm32_dmasample(priv->tx.dma, &priv->tx.dmaregs[DMA_END_TRANSFER]);
/* Then dump the sampled DMA registers */
/* Initial register values */
stm32_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_INITIAL],
"TX: Initial Registers");
/* Register values after DMA setup */
stm32_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_AFTER_SETUP],
"TX: After DMA Setup");
/* Register values after DMA start */
stm32_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_AFTER_START],
"TX: After DMA Start");
/* Register values at the time of the TX and RX DMA callbacks
* -OR- DMA timeout.
*/
if (result == -ETIMEDOUT || result == -EINTR)
{
stm32_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_TIMEOUT],
"TX: At DMA timeout");
}
else
{
stm32_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_CALLBACK],
"TX: At DMA callback");
}
stm32_dmadump(priv->tx.dma, &priv->tx.dmaregs[DMA_END_TRANSFER],
"TX: At End-of-Transfer");
i2s_dump_regs(priv, "TX: At End-of-Transfer");
}
#endif
/****************************************************************************
* Name: i2s_rxdma_timeout
*
* 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 I2S_HAVE_RX
static void i2s_rxdma_timeout(int argc, uint32_t arg)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)arg;
DEBUGASSERT(priv != NULL);
/* Sample DMA registers at the time of the timeout */
i2s_rxdma_sample(priv, DMA_TIMEOUT);
/* Cancel the DMA */
stm32_dmastop(priv->rx.dma);
/* Then schedule completion of the transfer to occur on the worker thread.
* NOTE: stm32_dmastop() will call the DMA complete callback with an error
* of -EINTR. So the following is just insurance and should have no
* effect if the worker is already schedule.
*/
i2s_rx_schedule(priv, -ETIMEDOUT);
}
#endif
/****************************************************************************
* Name: i2s_rxdma_setup
*
* Description:
* Setup and initiate the next RX DMA transfer
*
* Input Parameters:
* priv - I2S state instance
*
* Returned Value:
* OK on success; a negated errno value on failure
*
* Assumptions:
* Interrupts are disabled
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static int i2s_rxdma_setup(struct stm32_i2s_s *priv)
{
struct stm32_buffer_s *bfcontainer;
struct ap_buffer_s *apb;
uintptr_t samp;
uint32_t timeout;
bool notimeout;
int ret;
/* If there is already an active transmission in progress, then bail
* returning success.
*/
if (!sq_empty(&priv->rx.act))
{
return OK;
}
/* If there are no pending transfer, then bail returning success */
if (sq_empty(&priv->rx.pend))
{
return OK;
}
/* Initialize DMA register sampling */
i2s_rxdma_sampleinit(priv);
/* Loop, adding each pending DMA */
timeout = 0;
notimeout = false;
do
{
/* Remove the pending RX transfer at the head of the RX pending queue. */
bfcontainer = (struct stm32_buffer_s *)sq_remfirst(&priv->rx.pend);
DEBUGASSERT(bfcontainer && bfcontainer->apb);
apb = bfcontainer->apb;
DEBUGASSERT(((uintptr_t)apb->samp % priv->align) == 0);
/* No data received yet */
apb->nbytes = 0;
apb->curbyte = 0;
samp = (uintptr_t)&apb->samp[apb->curbyte];
/* Configure the RX DMA */
stm32_dmasetup(priv->rx.dma, priv->base + STM32_SPI_DR_OFFSET,
(uint32_t)samp, apb->nmaxbytes, priv->rxccr);
/* Increment the DMA timeout */
if (bfcontainer->timeout > 0)
{
timeout += bfcontainer->timeout;
}
else
{
notimeout = true;
}
/* Add the container to the list of active DMAs */
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act);
}
#if 1 /* REVISIT: Chained RX transfers */
while (0);
#else
while (!sq_empty(&priv->rx.pend));
#endif
/* Sample DMA registers */
i2s_rxdma_sample(priv, DMA_AFTER_SETUP);
/* Start the DMA, saving the container as the current active transfer */
stm32_dmastart(priv->rx.dma, i2s_rxdma_callback, priv, false);
i2s_rxdma_sample(priv, DMA_AFTER_START);
/* Enable the receiver */
i2s_putreg(priv, STM32_SPI_CR2_OFFSET,
i2s_getreg(priv, STM32_SPI_CR2_OFFSET) | SPI_CR2_RXDMAEN);
/* Start a watchdog to catch DMA timeouts */
if (!notimeout)
{
ret = wd_start(priv->rx.dog, timeout, (wdentry_t)i2s_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)
{
i2serr("ERROR: wd_start failed: %d\n", errno);
}
}
return OK;
}
#endif
/****************************************************************************
* Name: i2s_rx_worker
*
* Description:
* RX transfer done worker
*
* Input Parameters:
* arg - the I2S device instance cast to void*
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static void i2s_rx_worker(void *arg)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)arg;
struct stm32_buffer_s *bfcontainer;
struct ap_buffer_s *apb;
irqstate_t flags;
DEBUGASSERT(priv);
/* When the transfer was started, the active buffer containers were removed
* from the rx.pend queue and saved in the rx.act queue. We get here when the
* DMA is finished... either successfully, with a DMA error, or with a DMA
* timeout.
*
* In any case, the buffer containers in rx.act will be moved to the end
* of the rx.done queue and rx.act queue will be emptied 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_rxsetup() from the interrupt level.
* So we have to start the next DMA here.
*/
i2sinfo("rx.act.head=%p rx.done.head=%p\n",
priv->rx.act.head, priv->rx.done.head);
/* Check if the DMA is IDLE */
if (sq_empty(&priv->rx.act))
{
#ifdef CONFIG_STM32_I2S_DMADEBUG
bfcontainer = (struct stm32_buffer_s *)sq_peek(&priv->rx.done);
if (bfcontainer)
{
/* Dump the DMA registers */
i2s_rxdma_sampledone(priv, bfcontainer->result);
}
#endif
/* Then start the next DMA. This must be done with interrupts
* disabled.
*/
flags = enter_critical_section();
(void)i2s_rxdma_setup(priv);
leave_critical_section(flags);
}
/* Process each buffer in the rx.done queue */
while (sq_peek(&priv->rx.done) != NULL)
{
/* Remove the buffer container from the rx.done queue. NOTE that
* interrupts must be enabled to do this because the rx.done queue is
* also modified from the interrupt level.
*/
flags = enter_critical_section();
bfcontainer = (struct stm32_buffer_s *)sq_remfirst(&priv->rx.done);
leave_critical_section(flags);
DEBUGASSERT(bfcontainer && bfcontainer->apb && bfcontainer->callback);
apb = bfcontainer->apb;
/* If the DMA was successful, then update the number of valid bytes in
* the audio buffer.
*/
if (bfcontainer->result == OK)
{
apb->nbytes = apb->nmaxbytes;
}
i2s_dump_buffer("Received", apb->samp, apb->nbytes);
/* Perform the RX transfer done callback */
bfcontainer->callback(&priv->dev, apb, bfcontainer->arg,
bfcontainer->result);
/* Release our reference on the audio buffer. This may very likely
* cause the audio buffer to be freed.
*/
apb_free(apb);
/* And release the buffer container */
i2s_buf_free(priv, bfcontainer);
}
}
#endif
/****************************************************************************
* Name: i2s_rx_schedule
*
* 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 I2S_HAVE_RX
static void i2s_rx_schedule(struct stm32_i2s_s *priv, int result)
{
struct stm32_buffer_s *bfcontainer;
int ret;
/* Upon entry, the transfer(s) that just completed are the ones in the
* priv->rx.act queue. NOTE: In certain conditions, this function may
* be called an additional time, hence, we can't assert this to be true.
* For example, in the case of a timeout, this function will be called by
* both indirectly via the stm32_dmastop() logic and directly via the
* i2s_rxdma_timeout() logic.
*/
/* Move all entries from the rx.act queue to the rx.done queue */
while (!sq_empty(&priv->rx.act))
{
/* Remove the next buffer container from the rx.act list */
bfcontainer = (struct stm32_buffer_s *)sq_remfirst(&priv->rx.act);
/* Report the result of the transfer */
bfcontainer->result = result;
/* Add the completed buffer container to the tail of the rx.done queue */
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.done);
}
/* If the worker has completed running, then reschedule the working thread.
* REVISIT: There may be a race condition here. So we do nothing is the
* worker is not available.
*/
if (work_available(&priv->rx.work))
{
/* Schedule the TX DMA done processing to occur on the worker thread. */
ret = work_queue(HPWORK, &priv->rx.work, i2s_rx_worker, priv, 0);
if (ret != 0)
{
i2serr("ERROR: Failed to queue RX work: %d\n", ret);
}
}
}
#endif
/****************************************************************************
* Name: i2s_rxdma_callback
*
* Description:
* This callback function is invoked at the completion of the I2S RX DMA.
*
* Input Parameters:
* handle - The DMA handler
* arg - A pointer to the chip select struction
* result - The result of the DMA transfer
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static void i2s_rxdma_callback(DMA_HANDLE handle, uint8_t result, void *arg)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)arg;
DEBUGASSERT(priv != NULL);
/* Cancel the watchdog timeout */
(void)wd_cancel(priv->rx.dog);
/* Sample DMA registers at the time of the DMA completion */
i2s_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 stm32_dmasetup() from an interrupt handler!
*/
/* Then schedule completion of the transfer to occur on the worker thread */
i2s_rx_schedule(priv, result);
}
#endif
/****************************************************************************
* Name: i2s_txdma_timeout
*
* 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 I2S_HAVE_TX
static void i2s_txdma_timeout(int argc, uint32_t arg)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)arg;
DEBUGASSERT(priv != NULL);
/* Sample DMA registers at the time of the timeout */
i2s_txdma_sample(priv, DMA_TIMEOUT);
/* Cancel the DMA */
stm32_dmastop(priv->tx.dma);
/* Then schedule completion of the transfer to occur on the worker thread.
* NOTE: stm32_dmastop() will call the DMA complete callback with an error
* of -EINTR. So the following is just insurance and should have no
* effect if the worker is already schedule.
*/
i2s_tx_schedule(priv, -ETIMEDOUT);
}
#endif
/****************************************************************************
* Name: i2s_txdma_setup
*
* Description:
* Setup and initiate the next TX DMA transfer
*
* Input Parameters:
* priv - I2S state instance
*
* Returned Value:
* OK on success; a negated errno value on failure
*
* Assumptions:
* Interrupts are disabled
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static int i2s_txdma_setup(struct stm32_i2s_s *priv)
{
struct stm32_buffer_s *bfcontainer;
struct ap_buffer_s *apb;
uintptr_t samp;
uint32_t timeout;
apb_samp_t nbytes;
bool notimeout;
int ret;
/* If there is already an active transmission in progress, then bail
* returning success.
*/
if (!sq_empty(&priv->tx.act))
{
return OK;
}
/* If there are no pending transfer, then bail returning success */
if (sq_empty(&priv->tx.pend))
{
return OK;
}
/* Initialize DMA register sampling */
i2s_txdma_sampleinit(priv);
/* Loop, adding each pending DMA */
timeout = 0;
notimeout = false;
do
{
/* Remove the pending TX transfer at the head of the TX pending queue. */
bfcontainer = (struct stm32_buffer_s *)sq_remfirst(&priv->tx.pend);
DEBUGASSERT(bfcontainer && bfcontainer->apb);
apb = bfcontainer->apb;
/* Get the transfer information, accounting for any data offset */
samp = (uintptr_t)&apb->samp[apb->curbyte];
nbytes = apb->nbytes - apb->curbyte;
DEBUGASSERT((samp & priv->align) == 0 && (nbytes & priv->align) == 0);
/* Configure DMA stream */
stm32_dmasetup(priv->tx.dma, priv->base + STM32_SPI_DR_OFFSET,
(uint32_t)samp, nbytes/2, priv->txccr);
/* Increment the DMA timeout */
if (bfcontainer->timeout > 0)
{
timeout += bfcontainer->timeout;
}
else
{
notimeout = true;
}
/* Add the container to the list of active DMAs */
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.act);
}
#if 1 /* REVISIT: Chained TX transfers */
while (0);
#else
while (!sq_empty(&priv->tx.pend));
#endif
/* Sample DMA registers */
i2s_txdma_sample(priv, DMA_AFTER_SETUP);
/* Start the DMA, saving the container as the current active transfer */
stm32_dmastart(priv->tx.dma, i2s_txdma_callback, priv, true);
i2s_txdma_sample(priv, DMA_AFTER_START);
/* Enable the transmitter */
i2s_putreg(priv, STM32_SPI_CR2_OFFSET, i2s_getreg(priv, STM32_SPI_CR2_OFFSET) | SPI_CR2_TXDMAEN);
/* Start a watchdog to catch DMA timeouts */
if (!notimeout)
{
ret = wd_start(priv->tx.dog, timeout, (wdentry_t)i2s_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)
{
i2serr("ERROR: wd_start failed: %d\n", errno);
}
}
return OK;
}
#endif
/****************************************************************************
* Name: i2s_tx_worker
*
* Description:
* TX transfer done worker
*
* Input Parameters:
* arg - the I2S device instance cast to void*
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static void i2s_tx_worker(void *arg)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)arg;
struct stm32_buffer_s *bfcontainer;
irqstate_t flags;
DEBUGASSERT(priv);
/* When the transfer was started, the active buffer containers were removed
* from the tx.pend queue and saved in the tx.act queue. We get here when the
* DMA is finished... either successfully, with a DMA error, or with a DMA
* timeout.
*
* In any case, the buffer containers in tx.act will be moved to the end
* of the tx.done queue and tx.act will be emptied 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.
*/
i2sinfo("tx.act.head=%p tx.done.head=%p\n",
priv->tx.act.head, priv->tx.done.head);
/* Check if the DMA is IDLE */
if (sq_empty(&priv->tx.act))
{
#ifdef CONFIG_STM32_I2S_DMADEBUG
bfcontainer = (struct stm32_buffer_s *)sq_peek(&priv->tx.done);
if (bfcontainer)
{
/* Dump the DMA registers */
i2s_txdma_sampledone(priv, bfcontainer->result);
}
#endif
/* Then start the next DMA. This must be done with interrupts
* disabled.
*/
flags = enter_critical_section();
(void)i2s_txdma_setup(priv);
leave_critical_section(flags);
}
/* Process each buffer in the tx.done queue */
while (sq_peek(&priv->tx.done) != NULL)
{
/* Remove the buffer container from the tx.done queue. NOTE that
* interupts must be enabled to do this because the tx.done queue is
* also modified from the interrupt level.
*/
flags = enter_critical_section();
bfcontainer = (struct stm32_buffer_s *)sq_remfirst(&priv->tx.done);
leave_critical_section(flags);
/* Perform the TX transfer done callback */
DEBUGASSERT(bfcontainer && bfcontainer->callback);
bfcontainer->callback(&priv->dev, bfcontainer->apb,
bfcontainer->arg, bfcontainer->result);
/* Release our reference on the audio buffer. This may very likely
* cause the audio buffer to be freed.
*/
apb_free(bfcontainer->apb);
/* And release the buffer container */
i2s_buf_free(priv, bfcontainer);
}
}
#endif
/****************************************************************************
* Name: i2s_tx_schedule
*
* 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 I2S_HAVE_TX
static void i2s_tx_schedule(struct stm32_i2s_s *priv, int result)
{
struct stm32_buffer_s *bfcontainer;
int ret;
/* Upon entry, the transfer(s) that just completed are the ones in the
* priv->tx.act queue. NOTE: In certain conditions, this function may
* be called an additional time, hence, we can't assert this to be true.
* For example, in the case of a timeout, this function will be called by
* both indirectly via the stm32_dmastop() logic and directly via the
* i2s_txdma_timeout() logic.
*/
/* Move all entries from the tx.act queue to the tx.done queue */
while (!sq_empty(&priv->tx.act))
{
/* Remove the next buffer container from the tx.act list */
bfcontainer = (struct stm32_buffer_s *)sq_remfirst(&priv->tx.act);
/* Report the result of the transfer */
bfcontainer->result = result;
/* Add the completed buffer container to the tail of the tx.done queue */
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.done);
}
/* If the worker has completed running, then reschedule the working thread.
* REVISIT: There may be a race condition here. So we do nothing is the
* worker is not available.
*/
if (work_available(&priv->tx.work))
{
/* Schedule the TX DMA done processing to occur on the worker thread. */
ret = work_queue(HPWORK, &priv->tx.work, i2s_tx_worker, priv, 0);
if (ret != 0)
{
i2serr("ERROR: Failed to queue TX work: %d\n", ret);
}
}
}
#endif
/****************************************************************************
* Name: i2s_txdma_callback
*
* Description:
* This callback function is invoked at the completion of the I2S TX DMA.
*
* Input Parameters:
* handle - The DMA handler
* arg - A pointer to the chip select struction
* result - The result of the DMA transfer
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static void i2s_txdma_callback(DMA_HANDLE handle, uint8_t result, void *arg)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)arg;
DEBUGASSERT(priv != NULL);
/* Cancel the watchdog timeout */
(void)wd_cancel(priv->tx.dog);
/* Sample DMA registers at the time of the DMA completion */
i2s_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 stm32_dmasetup() from an interrupt handler!
*/
/* Then schedule completion of the transfer to occur on the worker thread */
i2s_tx_schedule(priv, result);
}
#endif
/****************************************************************************
* Name: i2s_checkwidth
*
* Description:
* Check for a valid bit width. The I2S is capable of handling most any
* bit width from 8 to 16, but the DMA logic in this driver is constrained
* to 8- and 16-bit data widths
*
* Input Parameters:
* dev - Device-specific state data
* rate - The I2S sample rate in samples (not bits) per second
*
* Returned Value:
* Returns the resulting bitrate
*
****************************************************************************/
static int i2s_checkwidth(struct stm32_i2s_s *priv, int bits)
{
/* The I2S can handle most any bit width from 8 to 32. However, the DMA
* logic here is constrained to byte, half-word, and word sizes.
*/
switch (bits)
{
case 8:
#ifdef CONFIG_DEBUG
priv->align = 0;
#endif
break;
case 16:
#ifdef CONFIG_DEBUG
priv->align = 1;
#endif
break;
default:
i2serr("ERROR: Unsupported or invalid data width: %d\n", bits);
return (bits < 8 || bits > 16) ? -EINVAL : -ENOSYS;
}
/* Save the new data width */
priv->datalen = bits;
return OK;
}
/****************************************************************************
* Name: stm32_i2s_rxsamplerate
*
* Description:
* 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 I2S 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
* rate - The I2S sample rate in samples (not bits) per second
*
* Returned Value:
* Returns the resulting bitrate
*
****************************************************************************/
static uint32_t stm32_i2s_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate)
{
#if defined(I2S_HAVE_RX) && defined(I2S_HAVE_MCK)
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)dev;
DEBUGASSERT(priv && priv->samplerate >= 0 && rate > 0);
/* Check if the receiver is driven by the MCK */
if (priv->samplerate != rate)
{
/* Save the new sample rate and update the MCK divider */
priv->samplerate = rate;
return i2s_mckdivider(priv);
}
#endif
return 0;
}
/****************************************************************************
* Name: stm32_i2s_rxdatawidth
*
* Description:
* Set the I2S RX data width. The RX 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 stm32_i2s_rxdatawidth(struct i2s_dev_s *dev, int bits)
{
#ifdef I2S_HAVE_RX
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)dev;
int ret;
DEBUGASSERT(priv && bits > 1);
/* Check if this is a bit width that we are configured to handle */
ret = i2s_checkwidth(priv, bits);
if (ret < 0)
{
i2serr("ERROR: i2s_checkwidth failed: %d\n", ret);
return 0;
}
/* Update the DMA flags */
ret = i2s_dma_flags(priv);
if (ret < 0)
{
i2serr("ERROR: i2s_dma_flags failed: %d\n", ret);
return 0;
}
#endif
return 0;
}
/****************************************************************************
* Name: stm32_i2s_receive
*
* Description:
* Receive a block of data from I2S.
*
* Input Parameters:
* dev - Device-specific state data
* apb - A pointer to the audio buffer in which to receive data
* 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:
* 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 int stm32_i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg, uint32_t timeout)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)dev;
#ifdef I2S_HAVE_RX
struct stm32_buffer_s *bfcontainer;
irqstate_t flags;
int ret;
#endif
DEBUGASSERT(priv && apb && ((uintptr_t)apb->samp & priv->align) == 0);
i2sinfo("apb=%p nmaxbytes=%d arg=%p timeout=%d\n",
apb, apb->nmaxbytes, arg, timeout);
i2s_init_buffer(apb->samp, apb->nmaxbytes);
#ifdef I2S_HAVE_RX
/* Allocate a buffer container in advance */
bfcontainer = i2s_buf_allocate(priv);
DEBUGASSERT(bfcontainer);
/* Get exclusive access to the I2S driver data */
i2s_exclsem_take(priv);
/* Has the RX channel been enabled? */
if (!priv->rxenab)
{
i2serr("ERROR: I2S%d has no receiver\n", priv->i2sno);
ret = -EAGAIN;
goto errout_with_exclsem;
}
/* Add a reference to the audio buffer */
apb_reference(apb);
/* Initialize the buffer container structure */
bfcontainer->callback = (void *)callback;
bfcontainer->timeout = timeout;
bfcontainer->arg = arg;
bfcontainer->apb = apb;
bfcontainer->result = -EBUSY;
/* Add the buffer container to the end of the RX pending queue */
flags = enter_critical_section();
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.pend);
/* Then start the next transfer. If there is already a transfer in progess,
* then this will do nothing.
*/
ret = i2s_rxdma_setup(priv);
DEBUGASSERT(ret == OK);
leave_critical_section(flags);
i2s_exclsem_give(priv);
return OK;
errout_with_exclsem:
i2s_exclsem_give(priv);
i2s_buf_free(priv, bfcontainer);
return ret;
#else
i2serr("ERROR: I2S%d has no receiver\n", priv->i2sno);
UNUSED(priv);
return -ENOSYS;
#endif
}
static int roundf(float num)
{
if (((int)(num + 0.5f)) > num)
{
return num + 1;
}
return num;
}
/****************************************************************************
* Name: stm32_i2s_txsamplerate
*
* Description:
* Set the I2S TX sample rate. NOTE: This will have no effect if (1) the
* driver does not support an I2S transmitter or if (2) the sample rate is
* driven by the I2S 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
* rate - The I2S sample rate in samples (not bits) per second
*
* Returned Value:
* Returns the resulting bitrate
*
****************************************************************************/
static uint32_t stm32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate)
{
#if defined(I2S_HAVE_TX) && defined(I2S_HAVE_MCK)
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)dev;
DEBUGASSERT(priv && priv->samplerate >= 0 && rate > 0);
/* Check if the receiver is driven by the MCK/2 */
if (priv->samplerate != rate)
{
/* Save the new sample rate and update the MCK/2 divider */
priv->samplerate = rate;
return i2s_mckdivider(priv);
}
#endif
return 0;
}
/****************************************************************************
* Name: stm32_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
*
****************************************************************************/
static uint32_t stm32_i2s_txdatawidth(struct i2s_dev_s *dev, int bits)
{
#ifdef I2S_HAVE_TX
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)dev;
int ret;
i2sinfo("Data width bits of tx = %d\n",bits);
DEBUGASSERT(priv && bits > 1);
/* Check if this is a bit width that we are configured to handle */
ret = i2s_checkwidth(priv, bits);
if (ret < 0)
{
i2serr("ERROR: i2s_checkwidth failed: %d\n", ret);
return 0;
}
/* Upate the DMA flags */
ret = i2s_dma_flags(priv);
if (ret < 0)
{
i2serr("ERROR: i2s_dma_flags failed: %d\n", ret);
return 0;
}
#endif
return 0;
}
/****************************************************************************
* Name: stm32_i2s_send
*
* Description:
* Send a block of data on I2S.
*
* Input Parameters:
* dev - Device-specific state data
* apb - A pointer to the audio buffer from which to send data
* 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:
* 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 int stm32_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg, uint32_t timeout)
{
struct stm32_i2s_s *priv = (struct stm32_i2s_s *)dev;
#ifdef I2S_HAVE_TX
struct stm32_buffer_s *bfcontainer;
irqstate_t flags;
int ret;
#endif
/* Make sure that we have valid pointers that that the data has uint32_t
* alignment.
*/
DEBUGASSERT(priv && apb);
i2sinfo("apb=%p nbytes=%d arg=%p timeout=%d\n",
apb, apb->nbytes - apb->curbyte, arg, timeout);
i2s_dump_buffer("Sending", &apb->samp[apb->curbyte],
apb->nbytes - apb->curbyte);
DEBUGASSERT(((uintptr_t)&apb->samp[apb->curbyte] & priv->align) == 0);
#ifdef I2S_HAVE_TX
/* Allocate a buffer container in advance */
bfcontainer = i2s_buf_allocate(priv);
DEBUGASSERT(bfcontainer);
/* Get exclusive access to the I2S driver data */
i2s_exclsem_take(priv);
/* Has the TX channel been enabled? */
if (!priv->txenab)
{
i2serr("ERROR: I2S%d has no transmitter\n", priv->i2sno);
ret = -EAGAIN;
goto errout_with_exclsem;
}
/* Add a reference to the audio buffer */
apb_reference(apb);
/* Initialize the buffer container structure */
bfcontainer->callback = (void *)callback;
bfcontainer->timeout = timeout;
bfcontainer->arg = arg;
bfcontainer->apb = apb;
bfcontainer->result = -EBUSY;
/* Add the buffer container to the end of the TX pending queue */
flags = enter_critical_section();
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend);
/* Then start the next transfer. If there is already a transfer in progess,
* then this will do nothing.
*/
ret = i2s_txdma_setup(priv);
DEBUGASSERT(ret == OK);
leave_critical_section(flags);
i2s_exclsem_give(priv);
return OK;
errout_with_exclsem:
i2s_exclsem_give(priv);
i2s_buf_free(priv, bfcontainer);
return ret;
#else
i2serr("ERROR: I2S%d has no transmitter\n", priv->i2sno);
UNUSED(priv);
return -ENOSYS;
#endif
}
/****************************************************************************
* Name: i2s_mckdivider
*
* Description:
* Setup the MCK divider based on the currently selected data width and
* the sample rate
*
* Input Parameters:
* priv - I2C device structure (only the sample rate and data length is
* needed at this point).
*
* Returned Value:
* The current bitrate
*
****************************************************************************/
static uint32_t i2s_mckdivider(struct stm32_i2s_s *priv)
{
#ifdef I2S_HAVE_MCK
uint32_t bitrate;
uint32_t regval;
uint16_t pllr = 5, plln = 256, div = 12, odd = 1;
DEBUGASSERT(priv && priv->samplerate >= 0 && priv->datalen > 0);
/* A zero sample rate means to disable the MCK/2 clock */
if (priv->samplerate == 0)
{
bitrate = 0;
regval = 0;
}
else
{
int R, n, Od;
int Napprox;
int diff;
int diff_min = 500000000;
for (Od = 0; Od <= 1; ++Od)
{
for (R = 2; R <= 7; ++R)
{
for (n = 2; n <= 256; ++n)
{
Napprox = roundf(priv->samplerate / 1000000.0f * (8 * 32 * R * (2 * n + Od)));
if ((Napprox > 432) || (Napprox < 50))
{
continue;
}
diff = abs(priv->samplerate - 1000000 * Napprox / (8 * 32 * R * (2 * n + Od)));
if (diff_min > diff)
{
diff_min = diff;
plln = Napprox;
pllr = R;
div = n;
odd = Od;
}
}
}
}
/* Calculate the new bitrate in Hz */
bitrate = priv->samplerate * priv->datalen;
}
/* Configure MCK divider */
/* Disable I2S */
i2s_putreg(priv, STM32_SPI_I2SCFGR_OFFSET, 0);
/* I2S clock configuration */
putreg32((getreg32(STM32_RCC_CR) & (~RCC_CR_PLLI2SON)), STM32_RCC_CR);
/* PLLI2S clock used as I2S clock source */
putreg32(((getreg32(STM32_RCC_CFGR)) & (~RCC_CFGR_I2SSRC)), STM32_RCC_CFGR);
regval = (pllr << 28) | (plln << 6);
putreg32(regval, STM32_RCC_PLLI2SCFGR);
/* Enable PLLI2S and wait until it is ready */
putreg32((getreg32(STM32_RCC_CR) | RCC_CR_PLLI2SON), STM32_RCC_CR);
while (!(getreg32(STM32_RCC_CR) & RCC_CR_PLLI2SRDY));
i2s_putreg(priv, STM32_SPI_I2SPR_OFFSET,
div | (odd << 8) | SPI_I2SPR_MCKOE);
i2s_putreg(priv, STM32_SPI_I2SCFGR_OFFSET,
SPI_I2SCFGR_I2SMOD | SPI_I2SCFGR_I2SCFG_MTX | SPI_I2SCFGR_I2SE);
putreg32(((getreg32(STM32_DMA1_HIFCR)) | 0x80000000 /* DMA_HIFCR_CTCIF7 */),
STM32_DMA1_HIFCR);
return bitrate;
#else
return 0;
#endif
}
/****************************************************************************
* Name: i2s_dma_flags
*
* Description:
* Determine DMA FLAGS based on PID and data width
*
* Input Parameters:
* priv - Partially initialized I2C device structure.
*
* Returned Value:
* OK on success; a negated errno value on failure
*
****************************************************************************/
static int i2s_dma_flags(struct stm32_i2s_s *priv)
{
switch (priv->datalen)
{
case 8:
/* Reconfigure the RX DMA (and TX DMA if applicable) */
priv->rxccr = SPI_RXDMA8_CONFIG;
priv->txccr = SPI_TXDMA8_CONFIG;
break;
case 16:
priv->rxccr = SPI_RXDMA16_CONFIG;
priv->txccr = SPI_TXDMA16_CONFIG;
break;
default:
i2serr("ERROR: Unsupported data width: %d\n", priv->datalen);
return -ENOSYS;
}
return OK;
}
/****************************************************************************
* Name: i2s_dma_allocate
*
* Description:
* Allocate I2S DMA channels
*
* Input Parameters:
* priv - Partially initialized I2S 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 i2s_dma_allocate(struct stm32_i2s_s *priv)
{
int ret;
/* Get the DMA flags for this channel */
ret = i2s_dma_flags(priv);
if (ret < 0)
{
i2serr("ERROR: i2s_dma_flags failed: %d\n", ret);
return ret;
}
/* Allocate DMA channels. These allocations exploit that fact that
* I2S2 is managed by DMA1 and I2S3 is managed by DMA2. Hence,
* the I2S number (i2sno) is the same as the DMA number.
*/
#ifdef I2S_HAVE_RX
if (priv->rxenab)
{
/* Allocate an RX DMA channel */
priv->rx.dma = stm32_dmachannel(DMACHAN_I2S3_RX);
if (!priv->rx.dma)
{
i2serr("ERROR: Failed to allocate the RX DMA channel\n");
goto errout;
}
/* Create a watchdog time to catch RX DMA timeouts */
priv->rx.dog = wd_create();
if (!priv->rx.dog)
{
i2serr("ERROR: Failed to create the RX DMA watchdog\n");
goto errout;
}
}
#endif
#ifdef I2S_HAVE_TX
if (priv->txenab)
{
/* Allocate a TX DMA channel */
priv->tx.dma = stm32_dmachannel(DMACHAN_I2S3_TX);
if (!priv->tx.dma)
{
i2serr("ERROR: Failed to allocate the TX DMA channel\n");
goto errout;
}
/* Create a watchdog time to catch TX DMA timeouts */
priv->tx.dog = wd_create();
if (!priv->tx.dog)
{
i2serr("ERROR: Failed to create the TX DMA watchdog\n");
goto errout;
}
}
#endif
/* Success exit */
return OK;
/* Error exit */
errout:
i2s_dma_free(priv);
return -ENOMEM;
}
/****************************************************************************
* Name: i2s_dma_free
*
* Description:
* Release DMA-related resources allocated by i2s_dma_allocate()
*
* Input Parameters:
* priv - Partially initialized I2C device structure.
*
* Returned Value:
* None
*
****************************************************************************/
static void i2s_dma_free(struct stm32_i2s_s *priv)
{
#ifdef I2S_HAVE_TX
if (priv->tx.dog)
{
wd_delete(priv->tx.dog);
}
if (priv->tx.dma)
{
stm32_dmafree(priv->tx.dma);
}
#endif
#ifdef I2S_HAVE_RX
if (priv->rx.dog)
{
wd_delete(priv->rx.dog);
}
if (priv->rx.dma)
{
stm32_dmafree(priv->rx.dma);
}
#endif
}
/****************************************************************************
* Name: i2s2_configure
*
* Description:
* Configure I2S2
*
* Input Parameters:
* priv - Partially initialized I2C device structure. These functions
* will complete the I2S specific portions of the initialization
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_STM32_I2S2
static void i2s2_configure(struct stm32_i2s_s *priv)
{
/* Configure multiplexed pins as connected on the board. Chip
* select pins must be selected by board-specific logic.
*/
priv->base = STM32_I2S2_BASE;
#ifdef CONFIG_STM32_I2S2_RX
priv->rxenab = true;
if (!priv->initialized)
{
/* Configure I2S2 pins: MCK, SD, CK, WS */
stm32_configgpio(GPIO_I2S2_MCK);
stm32_configgpio(GPIO_I2S2_SD);
stm32_configgpio(GPIO_I2S2_CK);
stm32_configgpio(GPIO_I2S2_WS);
priv->initialized = true;
}
#endif /* CONFIG_STM32_I2S2_RX */
#ifdef CONFIG_STM32_I2S2_TX
priv->txenab = true;
/* Only configure if the port is not already configured */
if (!priv->initialized)
{
/* Configure I2S2 pins: MCK, SD, CK, WS */
stm32_configgpio(GPIO_I2S2_MCK);
stm32_configgpio(GPIO_I2S2_SD);
stm32_configgpio(GPIO_I2S2_CK);
stm32_configgpio(GPIO_I2S2_WS);
priv->initialized = true;
}
#endif /* CONFIG_STM32_I2S2_TX */
/* Configure driver state specific to this I2S peripheral */
priv->datalen = CONFIG_STM32_I2S2_DATALEN;
#ifdef CONFIG_DEBUG
priv->align = STM32_I2S2_DATAMASK;
#endif
}
#endif /* CONFIG_STM32_I2S2 */
/****************************************************************************
* Name: i2s3_configure
*
* Description:
* Configure I2S3
*
* Input Parameters:
* priv - Partially initialized I2C device structure. These functions
* will complete the I2S specific portions of the initialization
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_STM32_I2S3
static void i2s3_configure(struct stm32_i2s_s *priv)
{
/* Configure multiplexed pins as connected on the board. Chip
* select pins must be selected by board-specific logic.
*/
priv->base = STM32_I2S3_BASE;
#ifdef CONFIG_STM32_I2S3_RX
priv->rxenab = true;
if (!priv->initialized)
{
/* Configure I2S3 pins: MCK, SD, CK, WS */
stm32_configgpio(GPIO_I2S3_MCK);
stm32_configgpio(GPIO_I2S3_SD);
stm32_configgpio(GPIO_I2S3_CK);
stm32_configgpio(GPIO_I2S3_WS);
priv->initialized = true;
}
#endif /* CONFIG_STM32_I2S3_RX */
#ifdef CONFIG_STM32_I2S3_TX
priv->txenab = true;
/* Only configure if the port is not already configured */
if (!priv->initialized)
{
/* Configure I2S3 pins: MCK, SD, CK, WS */
stm32_configgpio(GPIO_I2S3_MCK);
stm32_configgpio(GPIO_I2S3_SD);
stm32_configgpio(GPIO_I2S3_CK);
stm32_configgpio(GPIO_I2S3_WS);
priv->initialized = true;
}
#endif /* CONFIG_STM32_I2S3_TX */
/* Configure driver state specific to this I2S peripheral */
priv->datalen = CONFIG_STM32_I2S3_DATALEN;
#ifdef CONFIG_DEBUG
priv->align = STM32_I2S3_DATAMASK;
#endif
}
#endif /* CONFIG_STM32_I2S3 */
/****************************************************************************
* Public Functions
****************************************************************************/
/************************************************************************************
* Name: stm32_i2sdev_initialize
*
* Description:
* Initialize the selected i2S port
*
* Input Parameters:
* Port number (for hardware that has mutiple I2S interfaces)
*
* Returned Value:
* Valid I2S device structure reference on succcess; a NULL on failure
*
************************************************************************************/
FAR struct i2s_dev_s *stm32_i2sdev_initialize(int port)
{
FAR struct stm32_i2s_s *priv = NULL;
irqstate_t flags;
int ret;
/* The support STM32 parts have only a single I2S port */
i2sinfo("port: %d\n", port);
/* Allocate a new state structure for this chip select. NOTE that there
* is no protection if the same chip select is used in two different
* chip select structures.
*/
priv = (struct stm32_i2s_s *)zalloc(sizeof(struct stm32_i2s_s));
if (!priv)
{
i2serr("ERROR: Failed to allocate a chip select structure\n");
return NULL;
}
/* Set up the initial state for this chip select structure. Other fields
* were zeroed by zalloc().
*/
/* Initialize the common parts for the I2S device structure */
nxsem_init(&priv->exclsem, 0, 1);
priv->dev.ops = &g_i2sops;
priv->i2sno = port;
/* Initialize buffering */
i2s_buf_initialize(priv);
flags = enter_critical_section();
#ifdef CONFIG_STM32_I2S2
if (port == 2)
{
/* Select I2S2 */
i2s2_configure(priv);
}
else
#endif
#ifdef CONFIG_STM32_I2S3
if (port == 3)
{
/* Select I2S3 */
i2s3_configure(priv);
}
else
#endif
{
i2serr("ERROR: Unsupported I2S port: %d\n", port);
return NULL;
}
/* Allocate DMA channels */
ret = i2s_dma_allocate(priv);
if (ret < 0)
{
goto errout_with_alloc;
}
leave_critical_section(flags);
i2s_dump_regs(priv, "After initialization");
/* Success exit */
return &priv->dev;
/* Failure exits */
errout_with_alloc:
nxsem_destroy(&priv->exclsem);
kmm_free(priv);
return NULL;
}
#endif /* I2S_HAVE_RX || I2S_HAVE_TX */
#endif /* CONFIG_STM32_I2S2 || CONFIG_STM32_I2S3 */