blob: 10e8bfdf1283ca9892d2aa90d36da41c0a2d6038 [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/esp32s2/esp32s2_i2s.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#ifdef CONFIG_ESP32S2_I2S
#include <debug.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/clock.h>
#include <nuttx/semaphore.h>
#include <nuttx/mqueue.h>
#include <nuttx/mm/circbuf.h>
#include <nuttx/audio/audio.h>
#include <nuttx/audio/i2s.h>
#include <arch/board/board.h>
#include "esp32s2_i2s.h"
#include "esp32s2_gpio.h"
#include "esp32s2_irq.h"
#include "esp32s2_dma.h"
#include "xtensa.h"
#include "hardware/esp32s2_gpio_sigmap.h"
#include "hardware/esp32s2_system.h"
#include "hardware/esp32s2_i2s.h"
#include "hardware/esp32s2_soc.h"
#include "hardware/esp32s2_iomux.h"
#include "hardware/esp32s2_pinmap.h"
#include "hardware/esp32s2_dma.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* I2S DMA RX/TX description number */
#define I2S_DMADESC_NUM (CONFIG_I2S_DMADESC_NUM)
/* I2S Clock */
#define I2S_LL_BASE_CLK (2 * APB_CLK_FREQ)
#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH (6)
#define I2S_LL_MCLK_DIVIDER_MAX ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1)
/* I2S DMA channel number */
#define I2S_DMA_CHANNEL_MAX (2)
#ifdef CONFIG_ESP32S2_I2S_TX
# define I2S_TX_ENABLED 1
# define I2S_HAVE_TX 1
#else
# define I2S_TX_ENABLED 0
#endif
#ifdef CONFIG_ESP32S2_I2S_RX
# define I2S_RX_ENABLED 1
# define I2S_HAVE_RX 1
#else
# define I2S_RX_ENABLED 0
#endif
#ifndef ALIGN_UP
# define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#endif
/* Debug ********************************************************************/
#ifdef CONFIG_DEBUG_I2S_INFO
# define CONFIG_ESP32S2_I2S_DUMPBUFFERS
#else
# undef CONFIG_ESP32S2_I2S_DUMPBUFFERS
#endif
#ifndef CONFIG_ESP32S2_I2S_MAXINFLIGHT
# define CONFIG_ESP32S2_I2S_MAXINFLIGHT 4
#endif
#define I2S_GPIO_UNUSED -1 /* For signals which are not used */
/****************************************************************************
* Private Types
****************************************************************************/
/* Role of the I2S0 port */
typedef enum
{
I2S_ROLE_MASTER, /* I2S controller master role, bclk and ws signal will be set to output */
I2S_ROLE_SLAVE /* I2S controller slave role, bclk and ws signal will be set to input */
} i2s_role_t;
/* Data width of the I2S channel */
typedef enum
{
I2S_DATA_BIT_WIDTH_8BIT = 8, /* I2S channel data bit-width: 8 */
I2S_DATA_BIT_WIDTH_16BIT = 16, /* I2S channel data bit-width: 16 */
I2S_DATA_BIT_WIDTH_24BIT = 24, /* I2S channel data bit-width: 24 */
I2S_DATA_BIT_WIDTH_32BIT = 32, /* I2S channel data bit-width: 32 */
} i2s_data_bit_width_t;
/* Multiplier of MCLK to sample rate */
typedef enum
{
I2S_MCLK_MULTIPLE_128 = 128, /* mclk = sample_rate * 128 */
I2S_MCLK_MULTIPLE_256 = 256, /* mclk = sample_rate * 256 */
I2S_MCLK_MULTIPLE_384 = 384, /* mclk = sample_rate * 384 */
I2S_MCLK_MULTIPLE_512 = 512, /* mclk = sample_rate * 512 */
} i2s_mclk_multiple_t;
/* I2S Device hardware configuration */
struct esp32s2_i2s_config_s
{
uint32_t port; /* I2S port */
uint32_t role; /* I2S port role (master or slave) */
uint8_t data_width; /* I2S sample data width */
uint32_t rate; /* I2S sample-rate */
uint32_t total_slot; /* Total slot number */
bool is_apll; /* Select APLL as the source clock */
bool tx_en; /* Is TX enabled? */
bool rx_en; /* Is RX enabled? */
int8_t mclk_pin; /* MCLK pin, output */
/* BCLK pin, input in slave role, output in master role */
int8_t bclk_pin;
/* WS pin, input in slave role, output in master role */
int8_t ws_pin;
int8_t dout_pin; /* DATA pin, output */
int8_t din_pin; /* DATA pin, input */
uint8_t periph; /* peripher ID */
uint8_t irq; /* Interrupt ID */
uint32_t bclk_in_insig; /* RX channel BCK signal (slave mode) index */
uint32_t bclk_in_outsig; /* RX channel BCK signal (master mode) index */
uint32_t bclk_out_insig; /* TX channel BCK signal (slave mode) index */
uint32_t bclk_out_outsig; /* TX channel BCK signal (master mode) index */
uint32_t ws_in_insig; /* RX channel WS signal (slave mode) index */
uint32_t ws_in_outsig; /* RX channel WS signal (master mode) index */
uint32_t ws_out_insig; /* TX channel WS signal (slave mode) index */
uint32_t ws_out_outsig; /* TX channel WS signal (master mode) index */
uint32_t din_insig; /* RX channel Data Input signal index */
uint32_t dout_outsig; /* TX channel Data Output signal index */
uint32_t mclk_out_sig; /* Master clock output index */
bool bit_shift; /* Set to enable bit shift in Philips mode */
bool mono_en; /* Set to enable mono mode on slot */
/* WS signal width (the number of bclk ticks that ws signal is high) */
uint32_t ws_width;
/* WS signal polarity, set true to enable high lever first */
bool ws_pol;
};
struct esp32s2_buffer_s
{
struct esp32s2_buffer_s *flink; /* Supports a singly linked list */
/* The associated DMA in/outlink */
struct esp32s2_dmadesc_s dma_link[I2S_DMADESC_NUM];
i2s_callback_t callback; /* DMA completion callback */
uint32_t timeout; /* Timeout value of the DMA transfers */
void *arg; /* Callback's argument */
struct ap_buffer_s *apb; /* The audio buffer */
uint8_t *buf; /* The DMA's descriptor buffer */
uint32_t nbytes; /* The DMA's descriptor buffer size */
int result; /* The result of the transfer */
};
/* Internal buffer must be aligned to the bytes_per_sample. Sometimes,
* however, the audio buffer is not aligned and additional bytes must
* be copied to be inserted on the next buffer. This structure keeps
* track of the bytes that were not written to the internal buffer yet.
*/
struct esp32s2_buffer_carry_s
{
uint32_t value;
size_t bytes;
};
/* This structure describes the state of one receiver or transmitter
* transport.
*/
struct esp32s2_transport_s
{
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 */
/* Bytes to be written at the beginning of the next DMA buffer */
struct esp32s2_buffer_carry_s carry;
};
/* The state of the one I2S peripheral */
struct esp32s2_i2s_s
{
struct i2s_dev_s dev; /* Externally visible I2S interface */
mutex_t lock; /* Ensures mutually exclusive access */
int cpuint; /* I2S interrupt ID */
uint8_t cpu; /* CPU ID */
/* Port configuration */
const struct esp32s2_i2s_config_s *config;
uint32_t mclk_freq; /* I2S actual master clock */
uint32_t mclk_multiple; /* The multiple of mclk to the sample rate */
uint32_t channels; /* Audio channels (1:mono or 2:stereo) */
uint32_t rate; /* I2S actual configured sample-rate */
uint32_t data_width; /* I2S actual configured data_width */
#ifdef I2S_HAVE_TX
struct esp32s2_transport_s tx; /* TX transport state */
bool tx_started; /* TX channel started */
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
struct esp32s2_transport_s rx; /* RX transport state */
bool rx_started; /* RX channel started */
#endif /* I2S_HAVE_RX */
/* Pre-allocated pool of buffer containers */
sem_t bufsem; /* Buffer wait semaphore */
struct esp32s2_buffer_s *bf_freelist; /* A list a free buffer containers */
struct esp32s2_buffer_s containers[CONFIG_ESP32S2_I2S_MAXINFLIGHT];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Register helpers */
#ifdef CONFIG_ESP32S2_I2S_DUMPBUFFERS
# define i2s_dump_buffer(m,b,s) lib_dumpbuffer(m,b,s)
#else
# define i2s_dump_buffer(m,b,s)
#endif
/* Buffer container helpers */
static struct esp32s2_buffer_s *
i2s_buf_allocate(struct esp32s2_i2s_s *priv);
static void i2s_buf_free(struct esp32s2_i2s_s *priv,
struct esp32s2_buffer_s *bfcontainer);
static int i2s_buf_initialize(struct esp32s2_i2s_s *priv);
/* DMA support */
#ifdef I2S_HAVE_TX
static int i2s_txdma_setup(struct esp32s2_i2s_s *priv,
struct esp32s2_buffer_s *bfcontainer);
static void i2s_tx_worker(void *arg);
static void i2s_tx_schedule(struct esp32s2_i2s_s *priv,
struct esp32s2_dmadesc_s *outlink);
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
static int i2s_rxdma_setup(struct esp32s2_i2s_s *priv,
struct esp32s2_buffer_s *bfcontainer);
static void i2s_rx_worker(void *arg);
static void i2s_rx_schedule(struct esp32s2_i2s_s *priv,
struct esp32s2_dmadesc_s *outlink);
#endif /* I2S_HAVE_RX */
/* I2S methods (and close friends) */
static uint32_t i2s_set_datawidth(struct esp32s2_i2s_s *priv);
static uint32_t i2s_set_clock(struct esp32s2_i2s_s *priv);
static uint32_t i2s_getmclkfrequency(struct i2s_dev_s *dev);
static uint32_t i2s_setmclkfrequency(struct i2s_dev_s *dev,
uint32_t frequency);
static int i2s_ioctl(struct i2s_dev_s *dev, int cmd, unsigned long arg);
#ifdef I2S_HAVE_TX
static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv);
static void i2s_tx_channel_stop(struct esp32s2_i2s_s *priv);
static int i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels);
static uint32_t i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate);
static uint32_t i2s_txdatawidth(struct i2s_dev_s *dev, int bits);
static int i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg,
uint32_t timeout);
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
static void i2s_rx_channel_start(struct esp32s2_i2s_s *priv);
static void i2s_rx_channel_stop(struct esp32s2_i2s_s *priv);
static int i2s_rxchannels(struct i2s_dev_s *dev, uint8_t channels);
static uint32_t i2s_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate);
static uint32_t i2s_rxdatawidth(struct i2s_dev_s *dev, int bits);
static int i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg,
uint32_t timeout);
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Private Data
****************************************************************************/
static const struct i2s_ops_s g_i2sops =
{
#ifdef I2S_HAVE_TX
.i2s_txchannels = i2s_txchannels,
.i2s_txsamplerate = i2s_txsamplerate,
.i2s_txdatawidth = i2s_txdatawidth,
.i2s_send = i2s_send,
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
.i2s_rxchannels = i2s_rxchannels,
.i2s_rxsamplerate = i2s_rxsamplerate,
.i2s_rxdatawidth = i2s_rxdatawidth,
.i2s_receive = i2s_receive,
#endif /* I2S_HAVE_RX */
.i2s_ioctl = i2s_ioctl,
.i2s_getmclkfrequency = i2s_getmclkfrequency,
.i2s_setmclkfrequency = i2s_setmclkfrequency,
};
#ifdef CONFIG_ESP32S2_I2S
static const struct esp32s2_i2s_config_s esp32s2_i2s0_config =
{
.port = 0,
#ifdef CONFIG_ESP32S2_I2S_ROLE_MASTER
.role = I2S_ROLE_MASTER,
#else
.role = I2S_ROLE_SLAVE,
#endif /* CONFIG_ESP32S2_I2S_ROLE_MASTER */
.data_width = CONFIG_ESP32S2_I2S_DATA_BIT_WIDTH,
.rate = CONFIG_ESP32S2_I2S_SAMPLE_RATE,
.total_slot = 2,
.tx_en = I2S_TX_ENABLED,
.rx_en = I2S_RX_ENABLED,
#ifdef CONFIG_ESP32S2_I2S_MCLK
.mclk_pin = CONFIG_ESP32S2_I2S_MCLKPIN,
#else
.mclk_pin = I2S_GPIO_UNUSED,
#endif /* CONFIG_ESP32S2_I2S_MCLK */
.bclk_pin = CONFIG_ESP32S2_I2S_BCLKPIN,
.ws_pin = CONFIG_ESP32S2_I2S_WSPIN,
#ifdef CONFIG_ESP32S2_I2S_DOUTPIN
.dout_pin = CONFIG_ESP32S2_I2S_DOUTPIN,
#else
.dout_pin = I2S_GPIO_UNUSED,
#endif /* CONFIG_ESP32S2_I2S_DOUTPIN */
#ifdef CONFIG_ESP32S2_I2S_DINPIN
.din_pin = CONFIG_ESP32S2_I2S_DINPIN,
#else
.din_pin = I2S_GPIO_UNUSED,
#endif /* CONFIG_ESP32S2_I2S_DINPIN */
.periph = ESP32S2_PERIPH_I2S0,
.irq = ESP32S2_IRQ_I2S0,
.bclk_in_insig = I2S0I_BCK_IN_IDX,
.bclk_in_outsig = I2S0I_BCK_OUT_IDX,
.bclk_out_insig = I2S0O_BCK_IN_IDX,
.bclk_out_outsig = I2S0O_BCK_OUT_IDX,
.ws_in_insig = I2S0I_WS_IN_IDX,
.ws_in_outsig = I2S0I_WS_OUT_IDX,
.ws_out_insig = I2S0O_WS_IN_IDX,
.ws_out_outsig = I2S0O_WS_OUT_IDX,
.din_insig = I2S0I_DATA_IN15_IDX,
.dout_outsig = I2S0O_DATA_OUT23_IDX,
.mclk_out_sig = CLK_I2S_MUX_IDX,
.bit_shift = true,
.mono_en = false,
.ws_width = CONFIG_ESP32S2_I2S_DATA_BIT_WIDTH,
};
static struct esp32s2_i2s_s esp32s2_i2s0_priv =
{
.dev =
{
.ops = &g_i2sops
},
.lock = NXMUTEX_INITIALIZER,
.config = &esp32s2_i2s0_config,
.bufsem = SEM_INITIALIZER(0)
};
#endif /* CONFIG_ESP32S2_I2S */
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: i2s_buf_allocate
*
* Description:
* Allocate a buffer container by removing the one at the head of the
* free list
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* 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 esp32s2_buffer_s *i2s_buf_allocate(struct esp32s2_i2s_s *priv)
{
struct esp32s2_buffer_s *bfcontainer;
irqstate_t flags;
int ret;
/* Set aside a buffer container. By doing this, we guarantee that we will
* have at least one free buffer container.
*/
ret = nxsem_wait_uninterruptible(&priv->bufsem);
if (ret < 0)
{
return NULL;
}
/* Get the buffer from the head of the free list */
flags = enter_critical_section();
bfcontainer = priv->bf_freelist;
DEBUGASSERT(bfcontainer);
/* Unlink the buffer from the freelist */
priv->bf_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 - Initialized I2S device structure.
* 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 esp32s2_i2s_s *priv,
struct esp32s2_buffer_s *bfcontainer)
{
irqstate_t flags;
/* Put the buffer container back on the free list (circbuf) */
flags = enter_critical_section();
bfcontainer->apb = NULL;
bfcontainer->buf = NULL;
bfcontainer->nbytes = 0;
bfcontainer->flink = priv->bf_freelist;
priv->bf_freelist = bfcontainer;
leave_critical_section(flags);
/* Wake up any threads waiting for a buffer container */
nxsem_post(&priv->bufsem);
}
/****************************************************************************
* 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 - Initialized I2S device structure.
*
* Returned Value:
* OK on success; A negated errno value on failure.
*
* Assumptions:
* Called early in I2S initialization so that there are no issues with
* concurrency.
*
****************************************************************************/
static int i2s_buf_initialize(struct esp32s2_i2s_s *priv)
{
#ifdef I2S_HAVE_TX
priv->tx.carry.bytes = 0;
priv->tx.carry.value = 0;
#endif /* I2S_HAVE_TX */
priv->bf_freelist = NULL;
for (int i = 0; i < CONFIG_ESP32S2_I2S_MAXINFLIGHT; i++)
{
i2s_buf_free(priv, &priv->containers[i]);
}
return OK;
}
/****************************************************************************
* Name: i2s_txdma_start
*
* Description:
* Initiate the next TX DMA transfer. The DMA outlink was previously bound
* so it is safe to start the next DMA transfer at interrupt level.
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* OK on success; a negated errno value on failure
*
* Assumptions:
* Interrupts are disabled
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static int i2s_txdma_start(struct esp32s2_i2s_s *priv)
{
struct esp32s2_buffer_s *bfcontainer;
/* 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;
}
bfcontainer = (struct esp32s2_buffer_s *)sq_remfirst(&priv->tx.pend);
/* If there isn't already an active transmission in progress,
* then start it.
*/
modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_ADDR_M,
FIELD_TO_VALUE(I2S_OUTLINK_ADDR,
(uintptr_t) bfcontainer->dma_link));
modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_STOP, I2S_OUTLINK_START);
modifyreg32(I2S_CONF_REG, 0, I2S_TX_START);
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.act);
return OK;
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_rxdma_start
*
* Description:
* Initiate the next RX DMA transfer. Assuming the DMA inlink is already
* bound, it's safe to start the next DMA transfer in an interrupt context.
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* OK on success; a negated errno value on failure
*
* Assumptions:
* Interrupts are disabled
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static int i2s_rxdma_start(struct esp32s2_i2s_s *priv)
{
struct esp32s2_buffer_s *bfcontainer;
/* 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;
}
bfcontainer = (struct esp32s2_buffer_s *)sq_remfirst(&priv->rx.pend);
/* If there isn't already an active transmission in progress,
* then start it.
*/
modifyreg32(I2S_RXEOF_NUM_REG, I2S_RX_EOF_NUM_M,
FIELD_TO_VALUE(I2S_RX_EOF_NUM, bfcontainer->nbytes));
modifyreg32(I2S_IN_LINK_REG, I2S_INLINK_ADDR_M,
FIELD_TO_VALUE(I2S_INLINK_ADDR,
(uintptr_t) bfcontainer->dma_link));
modifyreg32(I2S_IN_LINK_REG, I2S_INLINK_STOP, I2S_INLINK_START);
modifyreg32(I2S_CONF_REG, 0, I2S_RX_START);
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act);
return OK;
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Name: i2s_txdma_setup
*
* Description:
* Setup the next TX DMA transfer
*
* Input Parameters:
* priv - Initialized I2S device structure.
* bfcontainer - The buffer container to be set up
*
* 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 esp32s2_i2s_s *priv,
struct esp32s2_buffer_s *bfcontainer)
{
int ret = OK;
size_t carry_size;
uint32_t bytes_queued;
uint32_t data_copied;
struct ap_buffer_s *apb;
struct esp32s2_dmadesc_s *outlink;
apb_samp_t samp_size;
irqstate_t flags;
uint8_t *buf;
uint8_t *samp;
DEBUGASSERT(bfcontainer && bfcontainer->apb);
apb = bfcontainer->apb;
outlink = bfcontainer->dma_link;
/* Get the transfer information, accounting for any data offset */
const apb_samp_t bytes_per_sample = priv->data_width / 8;
samp = &apb->samp[apb->curbyte];
samp_size = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes;
carry_size = samp_size % bytes_per_sample;
/* Allocate the current audio buffer considering the remaining bytes
* carried from the last upper half audio buffer.
*/
bfcontainer->buf = calloc(bfcontainer->nbytes, 1);
data_copied = 0;
buf = bfcontainer->buf;
/* Copy the remaining bytes from the last audio buffer to the current
* audio buffer. The remaining bytes are part of a sample that was split
* between the last and the current audio buffer. Also, copy the bytes
* from that split sample that are on the current buffer to the internal
* buffer.
*/
if (priv->tx.carry.bytes)
{
memcpy(buf, &priv->tx.carry.value, priv->tx.carry.bytes);
buf += priv->tx.carry.bytes;
data_copied += priv->tx.carry.bytes;
memcpy(buf, samp, (bytes_per_sample - priv->tx.carry.bytes));
buf += (bytes_per_sample - priv->tx.carry.bytes);
samp += (bytes_per_sample - priv->tx.carry.bytes);
data_copied += (bytes_per_sample - priv->tx.carry.bytes);
}
/* Copy the upper half buffer to the internal buffer considering that
* the current upper half buffer may not contain a complete sample at
* the end of the buffer (and those bytes needs to be carried to the
* next audio buffer).
*/
memcpy(buf, samp, samp_size - (data_copied + carry_size));
buf += samp_size - (data_copied + carry_size);
samp += samp_size - (data_copied + carry_size);
data_copied += samp_size - (data_copied + carry_size);
/* If the audio buffer's size is not a multiple of the sample size,
* it's necessary to carry the remaining bytes that are part of what
* would be the last sample on this buffer. These bytes will then be
* saved and inserted at the beginning of the next DMA buffer to
* rebuild the sample correctly.
*/
priv->tx.carry.bytes = carry_size;
if (priv->tx.carry.bytes)
{
memcpy(&priv->tx.carry.value, samp, priv->tx.carry.bytes);
}
/* Release our reference on the audio buffer. This may very likely
* cause the audio buffer to be freed.
*/
apb_free(bfcontainer->apb);
/* Configure DMA stream */
bytes_queued = esp32s2_dma_init(outlink, I2S_DMADESC_NUM,
bfcontainer->buf, bfcontainer->nbytes);
if (bytes_queued != bfcontainer->nbytes)
{
i2serr("Failed to enqueue I2S buffer "
"(%" PRIu32 " bytes of %" PRIu32 ")\n",
bytes_queued, bfcontainer->nbytes);
return bytes_queued;
}
flags = enter_critical_section();
/* Add the buffer container to the end of the TX pending queue */
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend);
/* Trigger DMA transfer if no transmission is in progress */
ret = i2s_txdma_start(priv);
leave_critical_section(flags);
return ret;
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_rxdma_setup
*
* Description:
* Setup the next RX DMA transfer
*
* Input Parameters:
* priv - Initialized I2S device structure.
* bfcontainer - The buffer container to be set up
*
* 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 esp32s2_i2s_s *priv,
struct esp32s2_buffer_s *bfcontainer)
{
int ret = OK;
struct esp32s2_dmadesc_s *inlink;
uint32_t bytes_queued;
irqstate_t flags;
DEBUGASSERT(bfcontainer && bfcontainer->apb);
inlink = bfcontainer->dma_link;
/* Configure DMA stream */
bytes_queued = esp32s2_dma_init(inlink, I2S_DMADESC_NUM,
bfcontainer->apb->samp,
bfcontainer->nbytes);
if (bytes_queued != bfcontainer->nbytes)
{
i2serr("Failed to enqueue I2S buffer "
"(%" PRIu32 " bytes of %" PRIu32 ")\n",
bytes_queued, bfcontainer->nbytes);
return bytes_queued;
}
flags = enter_critical_section();
/* Add the buffer container to the end of the RX pending queue */
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.pend);
/* Trigger DMA transfer if no transmission is in progress */
ret = i2s_rxdma_start(priv);
leave_critical_section(flags);
return ret;
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Name: i2s_tx_schedule
*
* Description:
* An TX DMA completion has occurred. Schedule processing on
* the working thread.
*
* Input Parameters:
* priv - Initialized I2S device structure.
* outlink - DMA outlink descriptor that triggered the interrupt.
*
* Returned Value:
* None
*
* Assumptions:
* - Interrupts are disabled
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static void i2s_tx_schedule(struct esp32s2_i2s_s *priv,
struct esp32s2_dmadesc_s *outlink)
{
struct esp32s2_buffer_s *bfcontainer;
struct esp32s2_dmadesc_s *bfdesc;
int ret;
/* Upon entry, the transfer(s) that just completed are the ones in the
* priv->tx.act queue.
*/
/* Move all entries from the tx.act queue to the tx.done queue */
if (!sq_empty(&priv->tx.act))
{
/* Remove the next buffer container from the tx.act list */
bfcontainer = (struct esp32s2_buffer_s *)sq_peek(&priv->tx.act);
/* Check if the DMA descriptor that generated an EOF interrupt is the
* last descriptor of the current buffer container's DMA outlink.
* REVISIT: what to do if we miss syncronization and the descriptor
* that generated the interrupt is different from the expected (the
* oldest of the list containing active transmissions)?
*/
/* Find the last descriptor of the current buffer container */
bfdesc = bfcontainer->dma_link;
while (!(bfdesc->ctrl & DMA_CTRL_EOF))
{
DEBUGASSERT(bfdesc->next);
bfdesc = bfdesc->next;
}
if (bfdesc == outlink)
{
sq_remfirst(&priv->tx.act);
/* Report the result of the transfer */
bfcontainer->result = OK;
/* Add the completed buffer container to the tail of the tx.done
* queue
*/
sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.done);
/* Check if the DMA is IDLE */
if (sq_empty(&priv->tx.act))
{
/* Then start the next DMA. */
i2s_txdma_start(priv);
}
}
/* If the worker has completed running, then reschedule the working
* thread.
*/
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 /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_rx_schedule
*
* Description:
* An RX DMA completion has occurred. Schedule processing on
* the working thread.
*
* Input Parameters:
* priv - Initialized I2S device structure.
* inlink - DMA inlink descriptor that triggered the interrupt.
*
* Returned Value:
* None
*
* Assumptions:
* - Interrupts are disabled
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static void i2s_rx_schedule(struct esp32s2_i2s_s *priv,
struct esp32s2_dmadesc_s *inlink)
{
struct esp32s2_buffer_s *bfcontainer;
struct esp32s2_dmadesc_s *bfdesc;
int ret;
/* Upon entry, the transfer(s) that just completed are the ones in the
* priv->rx.act queue.
*/
/* Move all entries from the rx.act queue to the rx.done queue */
if (!sq_empty(&priv->rx.act))
{
/* Remove the next buffer container from the rx.act list */
bfcontainer = (struct esp32s2_buffer_s *)sq_peek(&priv->rx.act);
/* Check if the DMA descriptor that generated an EOF interrupt is the
* last descriptor of the current buffer container's DMA inlink.
* REVISIT: what to do if we miss syncronization and the descriptor
* that generated the interrupt is different from the expected (the
* oldest of the list containing active transmissions)?
*/
/* Find the last descriptor of the current buffer container */
bfdesc = bfcontainer->dma_link;
while (!(bfdesc->ctrl & DMA_CTRL_EOF))
{
DEBUGASSERT(bfdesc->next);
bfdesc = bfdesc->next;
}
if (bfdesc == inlink)
{
sq_remfirst(&priv->rx.act);
/* Report the result of the transfer */
bfcontainer->result = OK;
/* Add the completed buffer container to the tail of the rx.done
* queue
*/
sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.done);
/* Check if the DMA is IDLE */
if (sq_empty(&priv->rx.act))
{
/* Then start the next DMA. */
i2s_rxdma_start(priv);
}
}
/* If the worker has completed running, then reschedule the working
* thread.
*/
if (work_available(&priv->rx.work))
{
/* Schedule the RX 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 /* I2S_HAVE_RX */
/****************************************************************************
* 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 esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)arg;
struct esp32s2_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.
*
* 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.
*
*/
i2sinfo("tx.act.head=%p tx.done.head=%p\n",
priv->tx.act.head, priv->tx.done.head);
/* 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
* interrupts must be disabled to do this because the tx.done queue is
* also modified from the interrupt level.
*/
flags = enter_critical_section();
bfcontainer = (struct esp32s2_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 the internal buffer used by the DMA outlink */
free(bfcontainer->buf);
/* And release the buffer container */
i2s_buf_free(priv, bfcontainer);
}
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* 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 esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)arg;
struct esp32s2_buffer_s *bfcontainer;
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.
*
* In any case, the buffer containers in rx.act will be moved to the end
* of the rx.done queue and rx.act will be emptied before this worker is
* started.
*
*/
i2sinfo("rx.act.head=%p rx.done.head=%p\n",
priv->rx.act.head, priv->rx.done.head);
/* 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 disabled to do this because the rx.done queue is
* also modified from the interrupt level.
*/
flags = enter_critical_section();
bfcontainer = (struct esp32s2_buffer_s *)sq_remfirst(&priv->rx.done);
leave_critical_section(flags);
bfcontainer->apb->nbytes = (getreg32(I2S_RXEOF_NUM_REG));
/* Perform the RX 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 /* I2S_HAVE_RX */
/****************************************************************************
* Name: i2s_configure
*
* Description:
* Configure I2S
*
* Input Parameters:
* priv - Partially initialized I2S device structure. This function
* will complete the I2S specific portions of the initialization
*
* Returned Value:
* None
*
****************************************************************************/
static void i2s_configure(struct esp32s2_i2s_s *priv)
{
/* Set peripheral clock and clear reset */
modifyreg32(SYSTEM_PERIP_CLK_EN0_REG, 0, SYSTEM_I2S0_CLK_EN);
modifyreg32(SYSTEM_PERIP_RST_EN0_REG, 0, SYSTEM_I2S0_RST);
modifyreg32(SYSTEM_PERIP_RST_EN0_REG, SYSTEM_I2S0_RST, 0);
/* I2S module general init, enable I2S clock */
if (!(getreg32(I2S_CLKM_CONF_REG) & I2S_CLK_EN))
{
i2sinfo("Enabling I2S port clock...\n");
modifyreg32(I2S_CLKM_CONF_REG, I2S_CLK_SEL_M,
FIELD_TO_VALUE(I2S_CLK_SEL, 2));
modifyreg32(I2S_CLKM_CONF_REG, 0, I2S_CLK_EN);
putreg32(0, I2S_CONF2_REG);
}
/* Configure multiplexed pins as connected on the board */
/* TODO: check for loopback mode */
/* Enable TX channel */
if (priv->config->dout_pin != I2S_GPIO_UNUSED)
{
esp32s2_gpiowrite(priv->config->dout_pin, 1);
esp32s2_configgpio(priv->config->dout_pin, OUTPUT_FUNCTION_2);
esp32s2_gpio_matrix_out(priv->config->dout_pin,
priv->config->dout_outsig, 0, 0);
}
/* Enable RX channel */
if (priv->config->din_pin != I2S_GPIO_UNUSED)
{
esp32s2_configgpio(priv->config->din_pin, INPUT_FUNCTION_2);
esp32s2_gpio_matrix_in(priv->config->din_pin,
priv->config->din_insig, 0);
}
if (priv->config->role == I2S_ROLE_SLAVE)
{
if (priv->config->tx_en && !priv->config->rx_en)
{
/* For "tx + slave" mode, select TX signal index for ws and bck */
esp32s2_gpiowrite(priv->config->ws_pin, 1);
esp32s2_configgpio(priv->config->ws_pin, INPUT_FUNCTION_2);
esp32s2_gpio_matrix_in(priv->config->ws_pin,
priv->config->ws_out_insig, 0);
esp32s2_gpiowrite(priv->config->bclk_pin, 1);
esp32s2_configgpio(priv->config->bclk_pin, INPUT_FUNCTION_2);
esp32s2_gpio_matrix_in(priv->config->bclk_pin,
priv->config->bclk_out_insig, 0);
}
else
{
/* For "tx + rx + slave" or "rx + slave" mode, select RX signal
* index for ws and bck.
*/
esp32s2_gpiowrite(priv->config->ws_pin, 1);
esp32s2_configgpio(priv->config->ws_pin, INPUT_FUNCTION_2);
esp32s2_gpio_matrix_in(priv->config->ws_pin,
priv->config->ws_in_insig, 0);
esp32s2_gpiowrite(priv->config->bclk_pin, 1);
esp32s2_configgpio(priv->config->bclk_pin, INPUT_FUNCTION_2);
esp32s2_gpio_matrix_in(priv->config->bclk_pin,
priv->config->bclk_in_insig, 0);
}
}
else
{
/* Considering master role for the I2S port */
/* Set MCLK pin */
if (priv->config->mclk_pin != I2S_GPIO_UNUSED)
{
i2sinfo("Configuring GPIO%" PRIu8 " to output master clock\n",
priv->config->mclk_pin);
esp32s2_gpiowrite(priv->config->mclk_pin, 1);
esp32s2_configgpio(priv->config->mclk_pin, OUTPUT_FUNCTION_2);
esp32s2_gpio_matrix_out(priv->config->mclk_pin,
priv->config->mclk_out_sig, 0, 0);
}
if (priv->config->tx_en && !priv->config->rx_en)
{
/* For "tx + master" mode, select TX signal index for ws and bck */
esp32s2_gpiowrite(priv->config->ws_pin, 1);
esp32s2_configgpio(priv->config->ws_pin, OUTPUT_FUNCTION_2);
esp32s2_gpio_matrix_out(priv->config->ws_pin,
priv->config->ws_out_outsig, 0, 0);
esp32s2_gpiowrite(priv->config->bclk_pin, 1);
esp32s2_configgpio(priv->config->bclk_pin, OUTPUT_FUNCTION_2);
esp32s2_gpio_matrix_out(priv->config->bclk_pin,
priv->config->bclk_out_outsig, 0, 0);
}
else
{
/* For "tx + rx + master" or "rx + master" mode, select RX signal
* index for ws and bck.
*/
esp32s2_gpiowrite(priv->config->ws_pin, 1);
esp32s2_configgpio(priv->config->ws_pin, OUTPUT_FUNCTION_2);
esp32s2_gpio_matrix_out(priv->config->ws_pin,
priv->config->ws_in_outsig, 0, 0);
esp32s2_gpiowrite(priv->config->bclk_pin, 1);
esp32s2_configgpio(priv->config->bclk_pin, OUTPUT_FUNCTION_2);
esp32s2_gpio_matrix_out(priv->config->bclk_pin,
priv->config->bclk_in_outsig, 0, 0);
}
}
/* Share BCLK and WS if in full-duplex mode */
if (priv->config->tx_en && priv->config->rx_en)
{
modifyreg32(I2S_CONF_REG, 0, I2S_SIG_LOOPBACK);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_SIG_LOOPBACK, 0);
}
/* Configure the TX module */
if (priv->config->tx_en)
{
/* Reset I2S TX module */
modifyreg32(I2S_CONF_REG, 0, I2S_TX_RESET);
modifyreg32(I2S_CONF_REG, I2S_TX_RESET, 0);
modifyreg32(I2S_LC_CONF_REG, 0, I2S_OUT_RST);
modifyreg32(I2S_LC_CONF_REG, I2S_OUT_RST, 0);
/* Enable/disable I2S TX slave mode */
if (priv->config->role == I2S_ROLE_SLAVE)
{
modifyreg32(I2S_CONF_REG, 0, I2S_TX_SLAVE_MOD);
}
else
{
/* Since BCLK and WS are shared, only TX or RX can be master. In
* this case, force TX as slave to avoid conflict of clock signal.
*/
if (priv->config->rx_en)
{
modifyreg32(I2S_CONF_REG, 0, I2S_TX_SLAVE_MOD);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_TX_SLAVE_MOD, 0);
}
}
/* Configure TX chan bit, audio data bit and mono mode.
* On ESP32-S2, sample_bit should equals to data_bit.
*/
/* Set TX data width */
priv->data_width = priv->config->data_width;
i2s_set_datawidth(priv);
/* Set I2S tx chan mode */
modifyreg32(I2S_CONF_CHAN_REG, I2S_TX_CHAN_MOD_M,
FIELD_TO_VALUE(I2S_TX_CHAN_MOD,
priv->config->mono_en ? 4 : 0));
/* Enable/disable TX MSB shift, the data will be launch at the first
* BCK clock.
*/
if (priv->config->bit_shift)
{
modifyreg32(I2S_CONF_REG, 0, I2S_TX_MSB_SHIFT);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_TX_MSB_SHIFT, 0);
}
/* Configure TX WS signal width. Set to to enable transmitter in PCM
* standard mode.
*/
if (priv->config->ws_width == 1)
{
modifyreg32(I2S_CONF_REG, 0, I2S_TX_SHORT_SYNC);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_TX_SHORT_SYNC, 0);
}
/* Set I2S tx right channel first */
if (priv->config->ws_pol == 1)
{
modifyreg32(I2S_CONF_REG, 0, I2S_TX_RIGHT_FIRST);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_TX_RIGHT_FIRST, 0);
}
/* I2S tx fifo module force enable */
modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_TX_FIFO_MOD_FORCE_EN);
/* The default value for the master clock frequency (MCLK frequency)
* can be set from the sample rate multiplied by a fixed value, known
* as MCLK multiplier. This multiplier, however, should be divisible
* by the number of bytes from a sample, i.e, for 24 bits, the
* multiplier should be divisible by 3. NOTE: the MCLK frequency can
* be adjusted on runtime, so this value remains valid only if the
* upper half does not implement the `i2s_setmclkfrequency` method.
*/
if (priv->config->data_width == I2S_DATA_BIT_WIDTH_24BIT)
{
priv->mclk_multiple = I2S_MCLK_MULTIPLE_384;
}
else
{
priv->mclk_multiple = I2S_MCLK_MULTIPLE_256;
}
i2s_setmclkfrequency((struct i2s_dev_s *)priv, (priv->config->rate *
priv->mclk_multiple));
priv->rate = priv->config->rate;
i2s_set_clock(priv);
}
/* Configure the RX module */
if (priv->config->rx_en)
{
/* Reset I2S RX module */
modifyreg32(I2S_CONF_REG, 0, I2S_RX_RESET);
modifyreg32(I2S_CONF_REG, I2S_RX_RESET, 0);
modifyreg32(I2S_LC_CONF_REG, 0, I2S_IN_RST);
modifyreg32(I2S_LC_CONF_REG, I2S_IN_RST, 0);
/* Enable/disable I2S RX slave mode */
if (priv->config->role == I2S_ROLE_SLAVE)
{
modifyreg32(I2S_CONF_REG, 0, I2S_RX_SLAVE_MOD);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_RX_SLAVE_MOD, 0);
}
/* Congfigure RX chan bit, audio data bit and mono mode.
* On ESP32-S2, sample_bit should equals to data_bit.
*/
/* Set RX data width */
priv->data_width = priv->config->data_width;
i2s_set_datawidth(priv);
/* Set I2S RX chan mode */
modifyreg32(I2S_CONF_CHAN_REG, I2S_RX_CHAN_MOD_M, 0);
/* Enable/disable RX MSB shift, the data will be read at the first
* BCK clock.
*/
if (priv->config->bit_shift)
{
modifyreg32(I2S_CONF_REG, 0, I2S_RX_MSB_SHIFT);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_RX_MSB_SHIFT, 0);
}
/* Configure RX WS signal width. Set to to enable receiver in PCM
* standard mode.
*/
if (priv->config->ws_width == 1)
{
modifyreg32(I2S_CONF_REG, 0, I2S_RX_SHORT_SYNC);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_RX_SHORT_SYNC, 0);
}
/* Set I2S RX right channel first */
if (priv->config->ws_pol == 1)
{
modifyreg32(I2S_CONF_REG, 0, I2S_RX_RIGHT_FIRST);
}
else
{
modifyreg32(I2S_CONF_REG, I2S_RX_RIGHT_FIRST, 0);
}
/* I2S RX fifo module force enable */
modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_RX_FIFO_MOD_FORCE_EN);
/* The default value for the master clock frequency (MCLK frequency)
* can be set from the sample rate multiplied by a fixed value, known
* as MCLK multiplier. This multiplier, however, should be divisible
* by the number of bytes from a sample, i.e, for 24 bits, the
* multiplier should be divisible by 3. NOTE: the MCLK frequency can
* be adjusted on runtime, so this value remains valid only if the
* upper half does not implement the `i2s_setmclkfrequency` method.
*/
if (priv->config->data_width == I2S_DATA_BIT_WIDTH_24BIT)
{
priv->mclk_multiple = I2S_MCLK_MULTIPLE_384;
}
else
{
priv->mclk_multiple = I2S_MCLK_MULTIPLE_256;
}
i2s_setmclkfrequency((struct i2s_dev_s *)priv, (priv->config->rate *
priv->mclk_multiple));
priv->rate = priv->config->rate;
i2s_set_clock(priv);
}
}
/****************************************************************************
* Name: i2s_set_datawidth
*
* Description:
* Set the I2S TX/RX data width.
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* Returns the resulting data width
*
****************************************************************************/
static uint32_t i2s_set_datawidth(struct esp32s2_i2s_s *priv)
{
#ifdef I2S_HAVE_TX
if (priv->config->tx_en)
{
modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_TX_BITS_MOD_M,
FIELD_TO_VALUE(I2S_TX_BITS_MOD, priv->data_width));
/* Set TX FIFO operation mode */
modifyreg32(I2S_FIFO_CONF_REG, I2S_TX_FIFO_MOD_M,
priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ?
FIELD_TO_VALUE(I2S_TX_FIFO_MOD,
0 + priv->config->mono_en) :
FIELD_TO_VALUE(I2S_TX_FIFO_MOD,
2 + priv->config->mono_en));
/* I2S TX MSB right enable */
modifyreg32(I2S_CONF_REG, 0, I2S_TX_MSB_RIGHT);
}
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
if (priv->config->rx_en)
{
modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_RX_BITS_MOD_M,
FIELD_TO_VALUE(I2S_RX_BITS_MOD, priv->data_width));
/* Set RX FIFO operation mode */
modifyreg32(I2S_FIFO_CONF_REG, I2S_RX_FIFO_MOD_M,
priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ?
FIELD_TO_VALUE(I2S_RX_FIFO_MOD,
0 + priv->config->mono_en) :
FIELD_TO_VALUE(I2S_RX_FIFO_MOD,
2 + priv->config->mono_en));
/* I2S RX MSB right enable */
modifyreg32(I2S_CONF_REG, 0, I2S_RX_MSB_RIGHT);
}
#endif /* I2S_HAVE_RX */
return priv->data_width;
}
/****************************************************************************
* Name: i2s_set_clock
*
* Description:
* Set the I2S TX sample rate by adjusting I2S clock.
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* Returns the resulting bitrate
*
****************************************************************************/
static uint32_t i2s_set_clock(struct esp32s2_i2s_s *priv)
{
uint32_t rate;
uint32_t bclk;
uint32_t mclk;
uint32_t sclk;
uint32_t mclk_div;
int denominator;
int numerator;
uint32_t regval;
uint32_t freq_diff;
uint16_t bclk_div;
/* TODO: provide APLL clock support */
sclk = I2S_LL_BASE_CLK;
/* fmclk = bck_div * fbclk = fsclk / (mclk_div + b / a)
* mclk_div is the I2S clock divider's integral value
* b is the fraction clock divider's numerator value
* a is the fraction clock divider's denominator value
*/
if (priv->config->role == I2S_ROLE_MASTER)
{
bclk = priv->rate * priv->config->total_slot * priv->data_width;
mclk = priv->mclk_freq;
bclk_div = mclk / bclk;
}
else
{
/* For slave mode, mclk >= bclk * 8, so fix bclk_div to 2 first */
bclk_div = 8;
bclk = priv->rate * priv->config->total_slot * priv->data_width;
mclk = bclk * bclk_div;
}
/* Calculate the nearest integer value of the I2S clock divider */
mclk_div = sclk / mclk;
i2sinfo("Clock division info: [sclk]%" PRIu32 " Hz [mdiv] %d "
"[mclk] %" PRIu32 " Hz [bdiv] %d [bclk] %" PRIu32 " Hz\n",
sclk, mclk_div, mclk, bclk_div, bclk);
freq_diff = abs((int)sclk - (int)(mclk * mclk_div));
denominator = 1;
numerator = 0;
if (freq_diff)
{
float decimal = freq_diff / (float)mclk;
/* Carry bit if the decimal is greater than
* 1.0 - 1.0 / (63.0 * 2) = 125.0 / 126.0
*/
if (decimal > 125.0f / 126.0f)
{
mclk_div++;
}
else
{
uint32_t min = UINT32_MAX;
for (int a = 2; a <= I2S_LL_MCLK_DIVIDER_MAX; a++)
{
int b = (int)(a * (freq_diff / (double)mclk) + 0.5);
int ma = freq_diff * a;
int mb = mclk * b;
if (ma == mb)
{
denominator = a;
numerator = b;
break;
}
if (abs((mb - ma)) < min)
{
denominator = a;
numerator = b;
min = abs(mb - ma);
}
}
}
}
i2sinfo("Clock register: [mclk] %" PRIu32 " Hz [numerator] %d "
"[denominator] %d\n", mclk, numerator, denominator);
regval = getreg32(I2S_CLKM_CONF_REG);
regval &= ~I2S_CLKM_DIV_NUM_M;
regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_NUM, mclk_div);
regval &= ~I2S_CLKM_DIV_B_M;
regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_B, numerator);
regval &= ~I2S_CLKM_DIV_A_M;
regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_A, denominator);
putreg32(regval, I2S_CLKM_CONF_REG);
/* Set I2S TX bck div num */
#ifdef I2S_HAVE_TX
modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_TX_BCK_DIV_NUM_M,
FIELD_TO_VALUE(I2S_TX_BCK_DIV_NUM, bclk_div));
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_RX_BCK_DIV_NUM_M,
FIELD_TO_VALUE(I2S_RX_BCK_DIV_NUM, bclk_div));
#endif /* I2S_HAVE_RX */
/* Returns the actual sample rate */
bclk = sclk / (float)((mclk_div + numerator / (float)denominator) *
bclk_div);
rate = bclk / (float)(priv->config->total_slot * priv->data_width);
return rate;
}
/****************************************************************************
* Name: i2s_tx_channel_start
*
* Description:
* Start TX channel for the I2S port
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv)
{
if (priv->config->tx_en)
{
if (priv->tx_started)
{
i2swarn("TX channel was previously started\n");
return;
}
/* Reset the TX channel */
modifyreg32(I2S_CONF_REG, 0, I2S_TX_RESET);
modifyreg32(I2S_CONF_REG, I2S_TX_RESET, 0);
/* Reset the DMA operation */
modifyreg32(I2S_LC_CONF_REG, 0, I2S_OUT_RST);
modifyreg32(I2S_LC_CONF_REG, I2S_OUT_RST, 0);
/* Reset TX FIFO */
modifyreg32(I2S_CONF_REG, 0, I2S_TX_FIFO_RESET);
modifyreg32(I2S_CONF_REG, I2S_TX_FIFO_RESET, 0);
/* Enable DMA interrupt */
up_enable_irq(priv->config->irq);
modifyreg32(I2S_INT_ENA_REG, 0, I2S_OUT_EOF_INT_ENA);
/* Enable DMA operation mode */
modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_DSCR_EN);
/* Unset the DMA outlink */
putreg32(0, I2S_OUT_LINK_REG);
priv->tx_started = true;
i2sinfo("Started TX channel on I2S0\n");
}
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_rx_channel_start
*
* Description:
* Start RX channel for the I2S port
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static void i2s_rx_channel_start(struct esp32s2_i2s_s *priv)
{
if (priv->config->rx_en)
{
if (priv->rx_started)
{
i2swarn("RX channel was previously started\n");
return;
}
/* Reset the RX channel */
modifyreg32(I2S_CONF_REG, 0, I2S_RX_RESET);
modifyreg32(I2S_CONF_REG, I2S_RX_RESET, 0);
/* Reset the DMA operation */
modifyreg32(I2S_LC_CONF_REG, 0, I2S_IN_RST);
modifyreg32(I2S_LC_CONF_REG, I2S_IN_RST, 0);
/* Reset RX FIFO */
modifyreg32(I2S_CONF_REG, 0, I2S_RX_FIFO_RESET);
modifyreg32(I2S_CONF_REG, I2S_RX_FIFO_RESET, 0);
/* Enable DMA interrupt */
up_enable_irq(priv->config->irq);
modifyreg32(I2S_INT_ENA_REG, 0, I2S_IN_SUC_EOF_INT_ENA);
/* Enable DMA operation mode */
modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_DSCR_EN);
/* Unset the DMA inlink */
putreg32(0, I2S_IN_LINK_REG);
priv->rx_started = true;
i2sinfo("Started RX channel on I2S0\n");
}
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Name: i2s_tx_channel_stop
*
* Description:
* Stop TX channel for the I2S port
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static void i2s_tx_channel_stop(struct esp32s2_i2s_s *priv)
{
if (priv->config->tx_en)
{
if (!priv->tx_started)
{
i2swarn("TX channel was previously stopped\n");
return;
}
/* Stop TX channel */
modifyreg32(I2S_CONF_REG, I2S_TX_START, 0);
/* Stop outlink */
modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_START, I2S_OUTLINK_STOP);
/* Disable DMA interrupt */
modifyreg32(I2S_INT_ENA_REG, I2S_OUT_EOF_INT_ENA, 0);
/* Disable DMA operation mode */
modifyreg32(I2S_FIFO_CONF_REG, I2S_DSCR_EN, 0);
up_disable_irq(priv->config->irq);
priv->tx_started = false;
i2sinfo("Stopped TX channel on I2S0\n");
}
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_rx_channel_stop
*
* Description:
* Stop RX channel for the I2S port
*
* Input Parameters:
* priv - Initialized I2S device structure.
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static void i2s_rx_channel_stop(struct esp32s2_i2s_s *priv)
{
if (priv->config->rx_en)
{
if (!priv->rx_started)
{
i2swarn("RX channel was previously stopped\n");
return;
}
/* Stop RX channel */
modifyreg32(I2S_CONF_REG, I2S_RX_START, 0);
/* Stop outlink */
modifyreg32(I2S_IN_LINK_REG, I2S_INLINK_START, I2S_INLINK_STOP);
/* Disable DMA interrupt */
modifyreg32(I2S_INT_ENA_REG, I2S_IN_SUC_EOF_INT_ENA, 0);
/* Disable DMA operation mode */
modifyreg32(I2S_FIFO_CONF_REG, I2S_DSCR_EN, 0);
up_disable_irq(priv->config->irq);
priv->rx_started = false;
i2sinfo("Stopped RX channel on I2S0\n");
}
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Name: i2s_interrupt
*
* Description:
* Common I2S DMA interrupt handler
*
* Input Parameters:
* irq - Number of the IRQ that generated the interrupt
* context - Interrupt register state save info
* arg - I2S controller private data
*
* Returned Value:
* Standard interrupt return value.
*
****************************************************************************/
static int i2s_interrupt(int irq, void *context, void *arg)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)arg;
struct esp32s2_dmadesc_s *cur = NULL;
uint32_t status = getreg32(I2S_INT_ST_REG);
putreg32(UINT32_MAX, I2S_INT_CLR_REG);
#ifdef I2S_HAVE_TX
if (priv->config->tx_en)
{
if (status & I2S_OUT_EOF_INT_ST)
{
cur = (struct esp32s2_dmadesc_s *)
getreg32(I2S_OUT_EOF_DES_ADDR_REG);
/* Schedule completion of the transfer on the worker thread */
i2s_tx_schedule(priv, cur);
}
}
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
if (priv->config->rx_en)
{
if (status & I2S_IN_SUC_EOF_INT_ST)
{
cur = (struct esp32s2_dmadesc_s *)
getreg32(I2S_IN_EOF_DES_ADDR_REG);
/* Schedule completion of the transfer on the worker thread */
i2s_rx_schedule(priv, cur);
}
}
#endif /* I2S_HAVE_RX */
return 0;
}
/****************************************************************************
* Name: i2s_getmclkfrequency
*
* Description:
* Get the current master clock frequency.
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Returns the current master clock.
*
****************************************************************************/
static uint32_t i2s_getmclkfrequency(struct i2s_dev_s *dev)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
return priv->mclk_freq;
}
/****************************************************************************
* Name: i2s_setmclkfrequency
*
* Description:
* Set the master clock frequency. Usually, the MCLK is a multiple of the
* sample rate. Most of the audio codecs require setting specific MCLK
* frequency according to the sample rate.
*
* Input Parameters:
* dev - Device-specific state data
* frequency - The I2S master clock's frequency
*
* Returned Value:
* Returns the resulting master clock or a negated errno value on failure.
*
****************************************************************************/
static uint32_t i2s_setmclkfrequency(struct i2s_dev_s *dev,
uint32_t frequency)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
/* Check if the master clock frequency is beyond the highest possible
* value and return an error.
*/
if (frequency >= (I2S_LL_BASE_CLK / 2))
{
return -EINVAL;
}
priv->mclk_freq = frequency;
return frequency;
}
/****************************************************************************
* Name: i2s_txchannels
*
* Description:
* Set the I2S TX number of channels.
*
* Input Parameters:
* dev - Device-specific state data
* channels - The I2S numbers of channels
*
* Returned Value:
* OK on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static int i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->tx_en)
{
if (channels != 1 && channels != 2)
{
return -EINVAL;
}
i2s_tx_channel_stop(priv);
priv->channels = channels;
modifyreg32(I2S_CONF_REG, priv->channels == 1 ? 0 : I2S_TX_DMA_EQUAL,
priv->channels == 1 ? I2S_TX_DMA_EQUAL : 0);
i2s_tx_channel_start(priv);
return OK;
}
return -ENOTTY;
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_rxchannels
*
* Description:
* Set the I2S RX number of channels.
*
* Input Parameters:
* dev - Device-specific state data
* channels - The I2S numbers of channels
*
* Returned Value:
* OK on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static int i2s_rxchannels(struct i2s_dev_s *dev, uint8_t channels)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->rx_en)
{
if (channels != 1 && channels != 2)
{
return -EINVAL;
}
i2s_rx_channel_stop(priv);
priv->channels = channels;
modifyreg32(I2S_CONF_REG, priv->channels == 1 ? 0 : I2S_RX_DMA_EQUAL,
priv->channels == 1 ? I2S_RX_DMA_EQUAL : 0);
i2s_rx_channel_start(priv);
return OK;
}
return -ENOTTY;
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Name: 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
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static uint32_t i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->tx_en)
{
i2s_tx_channel_stop(priv);
priv->rate = rate;
rate = i2s_set_clock(priv);
i2s_tx_channel_start(priv);
return rate;
}
return 0;
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_rxsamplerate
*
* Description:
* Set the I2S 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
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static uint32_t i2s_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->rx_en)
{
i2s_rx_channel_stop(priv);
priv->rate = rate;
rate = i2s_set_clock(priv);
i2s_rx_channel_start(priv);
return rate;
}
return 0;
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* 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 data width
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static uint32_t i2s_txdatawidth(struct i2s_dev_s *dev, int bits)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->tx_en)
{
i2s_tx_channel_stop(priv);
priv->data_width = bits;
i2s_set_datawidth(priv);
i2s_tx_channel_start(priv);
return bits;
}
return 0;
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: 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 data width
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static uint32_t i2s_rxdatawidth(struct i2s_dev_s *dev, int bits)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->rx_en)
{
i2s_rx_channel_stop(priv);
priv->data_width = bits;
i2s_set_datawidth(priv);
i2s_rx_channel_start(priv);
return bits;
}
return 0;
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Name: 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.
* 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 cancelled
* 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.
*
****************************************************************************/
#ifdef I2S_HAVE_TX
static int i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg, uint32_t timeout)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->tx_en)
{
struct esp32s2_buffer_s *bfcontainer;
int ret = OK;
uint32_t nbytes;
/* Check audio buffer data size from the upper half. If the buffer
* size is not a multiple of the data width, the remaining bytes
* must be sent along with the next audio buffer.
*/
nbytes = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes;
nbytes -= (nbytes % (priv->data_width / 8));
if (nbytes > (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM))
{
i2serr("Required buffer size can't fit into DMA outlink "
"(exceeds in %" PRIu32 " bytes). Try to increase the "
"number of the DMA descriptors (CONFIG_I2S_DMADESC_NUM).",
nbytes - (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM));
return -EFBIG;
}
/* Allocate a buffer container in advance */
bfcontainer = i2s_buf_allocate(priv);
DEBUGASSERT(bfcontainer);
/* Get exclusive access to the I2S driver data */
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
goto errout_with_buf;
}
/* Add a reference to the audio buffer */
apb_reference(apb);
/* Initialize the buffer container structure */
bfcontainer->callback = callback;
bfcontainer->timeout = timeout;
bfcontainer->arg = arg;
bfcontainer->apb = apb;
bfcontainer->nbytes = nbytes;
bfcontainer->result = -EBUSY;
ret = i2s_txdma_setup(priv, bfcontainer);
if (ret != OK)
{
goto errout_with_buf;
}
i2sinfo("Queued %d bytes into DMA buffers\n", apb->nbytes);
i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte],
apb->nbytes - apb->curbyte);
nxmutex_unlock(&priv->lock);
return OK;
errout_with_buf:
nxmutex_unlock(&priv->lock);
i2s_buf_free(priv, bfcontainer);
return ret;
}
return -ENOTTY;
}
#endif /* I2S_HAVE_TX */
/****************************************************************************
* Name: i2s_receive
*
* Description:
* Receive a block of data on 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.
* 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 cancelled
* 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.
*
****************************************************************************/
#ifdef I2S_HAVE_RX
static int i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
i2s_callback_t callback, void *arg, uint32_t timeout)
{
struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
if (priv->config->rx_en)
{
struct esp32s2_buffer_s *bfcontainer;
int ret = OK;
uint32_t nbytes;
/* Check max audio buffer data size from the upper half and align the
* receiving buffer according to the data width.
*/
nbytes = apb->nmaxbytes;
nbytes -= (nbytes % (priv->data_width / 8));
if (nbytes > (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM))
{
i2serr("Required buffer size can't fit into DMA inlink "
"(exceeds in %" PRIu32 " bytes). Try to increase the "
"number of the DMA descriptors (CONFIG_I2S_DMADESC_NUM).",
nbytes - (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM));
return -EFBIG;
}
/* Allocate a buffer container in advance */
bfcontainer = i2s_buf_allocate(priv);
DEBUGASSERT(bfcontainer);
/* Get exclusive access to the I2S driver data */
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
goto errout_with_buf;
}
/* Add a reference to the audio buffer */
apb_reference(apb);
/* Initialize the buffer container structure */
bfcontainer->callback = callback;
bfcontainer->timeout = timeout;
bfcontainer->arg = arg;
bfcontainer->apb = apb;
bfcontainer->nbytes = nbytes;
bfcontainer->result = -EBUSY;
ret = i2s_rxdma_setup(priv, bfcontainer);
if (ret != OK)
{
goto errout_with_buf;
}
i2sinfo("Prepared %d bytes to receive DMA buffers\n", apb->nmaxbytes);
i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte],
apb->nbytes - apb->curbyte);
nxmutex_unlock(&priv->lock);
return OK;
errout_with_buf:
nxmutex_unlock(&priv->lock);
i2s_buf_free(priv, bfcontainer);
return ret;
}
return -ENOTTY;
}
#endif /* I2S_HAVE_RX */
/****************************************************************************
* Name: i2s_ioctl
*
* Description:
* Perform a device ioctl
*
****************************************************************************/
static int i2s_ioctl(struct i2s_dev_s *dev, int cmd, unsigned long arg)
{
struct audio_buf_desc_s *bufdesc;
int ret = -ENOTTY;
switch (cmd)
{
/* AUDIOIOC_START - Start the audio stream.
*
* ioctl argument: Audio session
*/
case AUDIOIOC_START:
{
i2sinfo("AUDIOIOC_START\n");
ret = OK;
}
break;
/* AUDIOIOC_ALLOCBUFFER - Allocate an audio buffer
*
* ioctl argument: pointer to an audio_buf_desc_s structure
*/
case AUDIOIOC_ALLOCBUFFER:
{
i2sinfo("AUDIOIOC_ALLOCBUFFER\n");
bufdesc = (struct audio_buf_desc_s *) arg;
ret = apb_alloc(bufdesc);
}
break;
/* AUDIOIOC_FREEBUFFER - Free an audio buffer
*
* ioctl argument: pointer to an audio_buf_desc_s structure
*/
case AUDIOIOC_FREEBUFFER:
{
i2sinfo("AUDIOIOC_FREEBUFFER\n");
bufdesc = (struct audio_buf_desc_s *) arg;
DEBUGASSERT(bufdesc->u.buffer != NULL);
apb_free(bufdesc->u.buffer);
ret = sizeof(struct audio_buf_desc_s);
}
break;
default:
break;
}
return ret;
}
/****************************************************************************
* Name: i2s_dma_setup
*
* Description:
* Configure the DMA for the I2S peripheral
*
* Input Parameters:
* priv - Partially initialized I2S device structure. This function
* will complete the I2S specific portions of the initialization
* regarding the DMA operation.
*
* Returned Value:
* OK on success; A negated errno value on failure.
*
****************************************************************************/
static int i2s_dma_setup(struct esp32s2_i2s_s *priv)
{
int ret;
/* Clear the interrupts */
putreg32(UINT32_MAX, I2S_INT_CLR_REG);
/* Set up to receive peripheral interrupts on the current CPU */
priv->cpu = up_cpu_index();
priv->cpuint = esp32s2_setup_irq(priv->config->periph, 1,
ESP32S2_CPUINT_LEVEL);
if (priv->cpuint < 0)
{
i2serr("Failed to allocate a CPU interrupt.\n");
return priv->cpuint;
}
ret = irq_attach(priv->config->irq, i2s_interrupt, priv);
if (ret != OK)
{
i2serr("Couldn't attach IRQ to handler.\n");
esp32s2_teardown_irq(priv->config->periph, priv->cpuint);
return ret;
}
return OK;
}
/****************************************************************************
* Name: esp32s2_i2sbus_initialize
*
* Description:
* Initialize the I2S peripheral
*
* Input Parameters:
* None
*
* Returned Value:
* Valid I2S device structure reference on success; a NULL on failure
*
****************************************************************************/
struct i2s_dev_s *esp32s2_i2sbus_initialize(void)
{
int ret;
struct esp32s2_i2s_s *priv = NULL;
irqstate_t flags;
/* Statically allocated I2S' device strucuture */
priv = &esp32s2_i2s0_priv;
flags = enter_critical_section();
i2s_configure(priv);
/* Allocate buffer containers */
ret = i2s_buf_initialize(priv);
if (ret < 0)
{
goto err;
}
ret = i2s_dma_setup(priv);
if (ret < 0)
{
goto err;
}
#ifdef I2S_HAVE_TX
/* Start TX channel */
if (priv->config->tx_en)
{
priv->tx_started = false;
i2s_tx_channel_start(priv);
}
#endif /* I2S_HAVE_TX */
#ifdef I2S_HAVE_RX
/* Start RX channel */
if (priv->config->rx_en)
{
priv->rx_started = false;
i2s_rx_channel_start(priv);
}
#endif /* I2S_HAVE_RX */
leave_critical_section(flags);
/* Success exit */
i2sinfo("I2S0 was successfully initialized\n");
return &priv->dev;
/* Failure exit */
err:
leave_critical_section(flags);
return NULL;
}
#endif /* CONFIG_ESP32S2_I2S */