Загрузка данных
#define DT_DRV_COMPAT nxp_mcux_i2s
#include <errno.h>
#include <string.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/dt-bindings/clock/imx_ccm.h>
#include <zephyr/sys/barrier.h>
#include <zephyr/device.h>
#include <soc.h>
#include <fsl_sai.h>
#include <fsl_edma.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(dev_i2s_mcux, CONFIG_I2S_LOG_LEVEL);
#define NUM_DMA_BLOCKS_RX_PREP 3
#if defined(CONFIG_DMA_MCUX_EDMA)
BUILD_ASSERT(NUM_DMA_BLOCKS_RX_PREP >= 3,
"eDMA avoids TCD coherency issue if NUM_DMA_BLOCKS_RX_PREP >= 3");
#endif /* CONFIG_DMA_MCUX_EDMA */
#define MAX_TX_DMA_BLOCKS CONFIG_DMA_TCD_QUEUE_SIZE
BUILD_ASSERT(MAX_TX_DMA_BLOCKS > NUM_DMA_BLOCKS_RX_PREP,
"NUM_DMA_BLOCKS_RX_PREP must be < CONFIG_DMA_TCD_QUEUE_SIZE");
#define SAI_WORD_SIZE_BITS_MIN 8
#define SAI_WORD_SIZE_BITS_MAX 32
#define SAI_WORD_PER_FRAME_MIN 0
#define SAI_WORD_PER_FRAME_MAX 32
/*
* SAI driver uses source_gather_en/dest_scatter_en feature of DMA, and relies
* on DMA driver managing circular list of DMA blocks. Like eDMA driver links
* Transfer Control Descriptors (TCDs) in list, and manages the tcdpool.
* Calling dma_reload() adds new DMA block to DMA channel already configured,
* into the DMA driver's circular list of blocks.
* This indicates the Tx/Rx stream.
*
* in_queue and out_queue are used as follows
* transmit stream:
* application provided buffer is queued to in_queue until loaded to DMA.
* when DMA channel is idle, buffer is retrieved from in_queue and loaded
* to DMA and queued to out_queue. when DMA completes, buffer is retrieved
* from out_queue and freed.
*
* receive stream:
* driver allocates buffer from slab and loads DMA buffer is queued to
* in_queue when DMA completes, buffer is retrieved from in_queue
* and queued to out_queue when application reads, buffer is read
* (may optionally block) from out_queue and presented to application.
*/
struct stream {
enum i2s_state state;
uint32_t dma_channel;
uint32_t start_channel;
void (*irq_call_back)(void);
struct i2s_config cfg;
struct dma_config dma_cfg;
struct dma_block_config dma_block;
uint8_t free_tx_dma_blocks;
bool last_block;
struct k_msgq in_queue;
struct k_msgq out_queue;
};
struct i2s_mcux_config {
I2S_Type *base;
uint32_t clk_src;
uint32_t clk_pre_div;
uint32_t clk_src_div;
uint32_t pll_src;
uint32_t pll_lp;
uint32_t pll_pd;
uint32_t pll_num;
uint32_t pll_den;
uint32_t mclk_control_base;
uint32_t mclk_pin_mask;
uint32_t mclk_pin_offset;
bool mclk_output;
uint32_t tx_channel;
clock_control_subsys_t clk_sub_sys;
const struct device *ccm_dev;
const struct pinctrl_dev_config *pinctrl;
void (*irq_connect)(const struct device *dev);
sai_sync_mode_t rx_sync_mode;
sai_sync_mode_t tx_sync_mode;
};
/* Device run time data */
struct i2s_dev_data {
const struct device *dev_dma;
struct stream tx;
void *tx_in_msgs[CONFIG_I2S_TX_BLOCK_COUNT];
void *tx_out_msgs[CONFIG_I2S_TX_BLOCK_COUNT];
struct stream rx;
void *rx_in_msgs[CONFIG_I2S_RX_BLOCK_COUNT];
void *rx_out_msgs[CONFIG_I2S_RX_BLOCK_COUNT];
};
static void i2s_purge_stream_buffers(struct stream *strm, struct k_mem_slab *mem_slab, bool in_drop,
bool out_drop)
{
void *buffer;
if (in_drop) {
while (k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT) == 0) {
k_mem_slab_free(mem_slab, buffer);
}
}
if (out_drop) {
while (k_msgq_get(&strm->out_queue, &buffer, K_NO_WAIT) == 0) {
k_mem_slab_free(mem_slab, buffer);
}
}
}
static void i2s_tx_stream_disable(const struct device *dev, bool drop)
{
struct i2s_dev_data *dev_data = dev->data;
struct stream *strm = &dev_data->tx;
const struct device *dev_dma = dev_data->dev_dma;
const struct i2s_mcux_config *dev_cfg = dev->config;
LOG_DBG("Stopping DMA channel %u for TX stream", strm->dma_channel);
/* Disable FIFO DMA request */
SAI_TxEnableDMA(dev_cfg->base, kSAI_FIFORequestDMAEnable, false);
dma_stop(dev_dma, strm->dma_channel);
/* wait for TX FIFO to drain before disabling */
while ((dev_cfg->base->TCSR & I2S_TCSR_FWF_MASK) == 0) {
;
}
/* Disable the channel FIFO */
SAI_TxSetChannelFIFOMask(dev_cfg->base, 0);
/* Disable Tx */
SAI_TxEnable(dev_cfg->base, false);
/* If Tx is disabled, reset the FIFO pointer, clear error flags */
if ((dev_cfg->base->TCSR & I2S_TCSR_TE_MASK) == 0UL) {
dev_cfg->base->TCSR |= (I2S_TCSR_FR_MASK | I2S_TCSR_SR_MASK);
dev_cfg->base->TCSR &= ~I2S_TCSR_SR_MASK;
}
/* purge buffers queued in the stream */
i2s_purge_stream_buffers(strm, dev_data->tx.cfg.mem_slab, drop, drop);
}
static void i2s_rx_stream_disable(const struct device *dev, bool in_drop, bool out_drop)
{
struct i2s_dev_data *dev_data = dev->data;
struct stream *strm = &dev_data->rx;
const struct device *dev_dma = dev_data->dev_dma;
const struct i2s_mcux_config *dev_cfg = dev->config;
LOG_DBG("Stopping RX stream & DMA channel %u", strm->dma_channel);
dma_stop(dev_dma, strm->dma_channel);
/* Disable the channel FIFO */
SAI_RxSetChannelFIFOMask(dev_cfg->base, 0);
/* Disable DMA enable bit */
SAI_RxEnableDMA(dev_cfg->base, kSAI_FIFORequestDMAEnable, false);
/* Disable Rx */
SAI_RxEnable(dev_cfg->base, false);
/* wait for Receiver to disable */
while (dev_cfg->base->RCSR & I2S_RCSR_RE_MASK) {
;
}
/* reset the FIFO pointer and clear error flags */
dev_cfg->base->RCSR |= (I2S_RCSR_FR_MASK | I2S_RCSR_SR_MASK);
dev_cfg->base->RCSR &= ~I2S_RCSR_SR_MASK;
/* purge buffers queued in the stream */
i2s_purge_stream_buffers(strm, dev_data->rx.cfg.mem_slab, in_drop, out_drop);
}
static int i2s_tx_reload_multiple_dma_blocks(const struct device *dev, uint8_t *blocks_queued)
{
struct i2s_dev_data *dev_data = dev->data;
const struct i2s_mcux_config *dev_cfg = dev->config;
I2S_Type *base = (I2S_Type *)dev_cfg->base;
struct stream *strm = &dev_data->tx;
void *buffer = NULL;
int ret = 0;
unsigned int key;
*blocks_queued = 0;
key = irq_lock();
/* queue additional blocks to DMA if in_queue and DMA has free blocks */
while (strm->free_tx_dma_blocks) {
/* get the next buffer from queue */
ret = k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT);
if (ret) {
/* in_queue is empty, no more blocks to send to DMA */
ret = 0;
break;
}
/* reload the DMA */
ret = dma_reload(dev_data->dev_dma, strm->dma_channel, (uint32_t)buffer,
(uint32_t)&base->TDR[strm->start_channel], strm->cfg.block_size);
if (ret != 0) {
LOG_ERR("dma_reload() failed with error 0x%x", ret);
break;
}
(strm->free_tx_dma_blocks)--;
ret = k_msgq_put(&strm->out_queue, &buffer, K_NO_WAIT);
if (ret != 0) {
LOG_ERR("buffer %p -> out %p err %d", buffer, &strm->out_queue, ret);
break;
}
(*blocks_queued)++;
}
irq_unlock(key);
return ret;
}
/* This function is executed in the interrupt context */
static void i2s_dma_tx_callback(const struct device *dma_dev, void *arg, uint32_t channel,
int status)
{
const struct device *dev = (struct device *)arg;
struct i2s_dev_data *dev_data = dev->data;
struct stream *strm = &dev_data->tx;
uint8_t blocks_queued;
void *buffer = NULL;
int ret;
LOG_DBG("tx cb");
ret = k_msgq_get(&strm->out_queue, &buffer, K_NO_WAIT);
if (ret == 0) {
/* transmission complete. free the buffer */
k_mem_slab_free(strm->cfg.mem_slab, buffer);
(strm->free_tx_dma_blocks)++;
} else {
LOG_ERR("no buf in out_queue for channel %u", channel);
}
if (strm->free_tx_dma_blocks > MAX_TX_DMA_BLOCKS) {
strm->state = I2S_STATE_ERROR;
LOG_ERR("free_tx_dma_blocks exceeded maximum, now %d", strm->free_tx_dma_blocks);
goto disabled_exit_no_drop;
}
/* Received a STOP trigger, terminate TX immediately */
if (strm->last_block) {
strm->state = I2S_STATE_READY;
LOG_DBG("TX STOPPED last_block set");
goto disabled_exit_no_drop;
}
if (ret) {
/* k_msgq_get() returned error, and was not last_block */
strm->state = I2S_STATE_ERROR;
goto disabled_exit_no_drop;
}
if (strm->state != I2S_STATE_RUNNING && strm->state != I2S_STATE_STOPPING) {
goto disabled_exit_drop;
}
ret = i2s_tx_reload_multiple_dma_blocks(dev, &blocks_queued);
if (ret) {
strm->state = I2S_STATE_ERROR;
goto disabled_exit_no_drop;
}
if (blocks_queued || (strm->free_tx_dma_blocks < MAX_TX_DMA_BLOCKS)) {
goto enabled_exit;
}
/* all DMA blocks are free but no blocks were queued */
if (strm->state == I2S_STATE_STOPPING) {
/* TX queue has drained */
strm->state = I2S_STATE_READY;
LOG_DBG("TX stream has stopped");
goto disabled_exit_no_drop;
}
LOG_WRN("TX input queue empty!");
if (strm->free_tx_dma_blocks >= MAX_TX_DMA_BLOCKS) {
/* In running state, no TX blocks for transferring, so stop
* TX (This will disable bit clock to avoid dummy bits
* received in RX side.
*/
const struct i2s_mcux_config *dev_cfg = dev->config;
I2S_Type *base = (I2S_Type *)dev_cfg->base;
SAI_TxEnable(base, false);
LOG_WRN("TX is paused.");
}
goto enabled_exit;
disabled_exit_no_drop:
i2s_tx_stream_disable(dev, false);
return;
disabled_exit_drop:
i2s_tx_stream_disable(dev, true);
return;
enabled_exit:
return;
}