drivers: i2s: add sai support for stm32u5xx
This PR adds initial sai support for STM32u5xx Signed-off-by: Mario Paja <mario.paja@zal.aero>
This commit is contained in:
parent
9f78a6969e
commit
041f942ba8
4 changed files with 885 additions and 0 deletions
|
@ -16,3 +16,4 @@ zephyr_library_sources_ifdef(CONFIG_I2S_MCUX_SAI i2s_mcux_sai.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_I2S_ESP32 i2s_esp32.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2S_SILABS_SIWX91X i2s_silabs_siwx91x.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2S_TEST i2s_test.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2S_STM32_SAI i2s_stm32_sai.c)
|
||||
|
|
|
@ -25,3 +25,23 @@ config I2S_STM32_TX_BLOCK_COUNT
|
|||
default 4
|
||||
|
||||
endif # I2S_STM32
|
||||
|
||||
menuconfig I2S_STM32_SAI
|
||||
bool "STM32 MCU I2S controller driver"
|
||||
default y
|
||||
depends on DT_HAS_ST_STM32_SAI_ENABLED
|
||||
select CACHE_MANAGEMENT if CPU_HAS_DCACHE
|
||||
select DMA
|
||||
select USE_STM32_HAL_DMA
|
||||
select USE_STM32_HAL_DMA_EX
|
||||
select USE_STM32_HAL_SAI
|
||||
help
|
||||
Enable SAI support on the STM32U5 family of processors.
|
||||
|
||||
if I2S_STM32_SAI
|
||||
|
||||
config I2S_STM32_SAI_BLOCK_COUNT
|
||||
int "SAI queue length"
|
||||
default 4
|
||||
|
||||
endif # I2S_STM32_SAI
|
||||
|
|
820
drivers/i2s/i2s_stm32_sai.c
Normal file
820
drivers/i2s/i2s_stm32_sai.c
Normal file
|
@ -0,0 +1,820 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ZAL Zentrum für Angewandte Luftfahrtforschung GmbH
|
||||
* Copyright (c) 2024 Mario Paja
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT st_stm32_sai
|
||||
|
||||
#include <string.h>
|
||||
#include <zephyr/drivers/dma.h>
|
||||
#include <zephyr/drivers/i2s.h>
|
||||
#include <zephyr/drivers/dma/dma_stm32.h>
|
||||
#include <soc.h>
|
||||
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
|
||||
#include <zephyr/drivers/clock_control.h>
|
||||
#include <zephyr/drivers/pinctrl.h>
|
||||
#include <zephyr/cache.h>
|
||||
|
||||
#include <zephyr/drivers/dma/dma_stm32.h>
|
||||
#include <zephyr/drivers/dma.h>
|
||||
#include <stm32_ll_dma.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/irq.h>
|
||||
LOG_MODULE_REGISTER(i2s_stm32_sai, CONFIG_I2S_LOG_LEVEL);
|
||||
|
||||
struct queue_item {
|
||||
void *buffer;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct stream {
|
||||
DMA_TypeDef *reg;
|
||||
|
||||
const struct device *dma_dev;
|
||||
uint32_t dma_channel;
|
||||
struct dma_config dma_cfg;
|
||||
|
||||
/* DMA size of a source block transfer in bytes according SAI data size. */
|
||||
uint8_t dma_src_size;
|
||||
|
||||
struct i2s_config i2s_cfg;
|
||||
void *mem_block;
|
||||
size_t mem_block_len;
|
||||
|
||||
bool master;
|
||||
bool last_block;
|
||||
|
||||
int32_t state;
|
||||
struct k_msgq queue;
|
||||
|
||||
int (*stream_start)(const struct device *dev, enum i2s_dir dir);
|
||||
void (*queue_drop)(const struct device *dev);
|
||||
};
|
||||
|
||||
struct i2s_stm32_sai_data {
|
||||
SAI_HandleTypeDef hsai;
|
||||
DMA_HandleTypeDef hdma;
|
||||
struct stream stream;
|
||||
};
|
||||
|
||||
struct i2s_stm32_sai_cfg {
|
||||
const struct pinctrl_dev_config *pincfg;
|
||||
const struct stm32_pclken *pclken;
|
||||
size_t pclk_len;
|
||||
const struct pinctrl_dev_config *pcfg;
|
||||
|
||||
bool mclk_div_enable;
|
||||
bool mclk_enable;
|
||||
bool synchronous;
|
||||
};
|
||||
|
||||
void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = CONTAINER_OF(hsai, struct i2s_stm32_sai_data, hsai);
|
||||
struct stream *stream = &dev_data->stream;
|
||||
int ret;
|
||||
|
||||
/* Exit the callback, Stream is stopped */
|
||||
if (stream->state == I2S_STATE_ERROR) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (stream->mem_block == NULL) {
|
||||
if (stream->state != I2S_STATE_READY) {
|
||||
stream->state = I2S_STATE_ERROR;
|
||||
LOG_ERR("RX mem_block NULL");
|
||||
goto exit;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct queue_item item = {.buffer = stream->mem_block, .size = stream->mem_block_len};
|
||||
|
||||
ret = k_msgq_put(&stream->queue, &item, K_NO_WAIT);
|
||||
if (ret < 0) {
|
||||
stream->state = I2S_STATE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (stream->state == I2S_STATE_STOPPING) {
|
||||
stream->state = I2S_STATE_READY;
|
||||
LOG_DBG("Stopping RX ...");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = k_mem_slab_alloc(stream->i2s_cfg.mem_slab, &stream->mem_block, K_NO_WAIT);
|
||||
if (ret < 0) {
|
||||
stream->state = I2S_STATE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
stream->mem_block_len = stream->i2s_cfg.block_size;
|
||||
|
||||
if (HAL_SAI_Receive_DMA(hsai, stream->mem_block,
|
||||
stream->mem_block_len / stream->dma_src_size) != HAL_OK) {
|
||||
LOG_ERR("HAL_SAI_Receive_DMA: <FAILED>");
|
||||
}
|
||||
|
||||
exit:
|
||||
/* EXIT */
|
||||
}
|
||||
|
||||
void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = CONTAINER_OF(hsai, struct i2s_stm32_sai_data, hsai);
|
||||
struct stream *stream = &dev_data->stream;
|
||||
struct queue_item item;
|
||||
void *mem_block_tmp;
|
||||
int ret;
|
||||
|
||||
if (stream->state == I2S_STATE_ERROR) {
|
||||
LOG_ERR("TX bad status: %d, Stopping...", stream->state);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (stream->mem_block == NULL) {
|
||||
if (stream->state != I2S_STATE_READY) {
|
||||
stream->state = I2S_STATE_ERROR;
|
||||
LOG_ERR("TX mem_block NULL");
|
||||
goto exit;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream->last_block) {
|
||||
LOG_DBG("TX Stopped ...");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Exit callback, no more data in the queue */
|
||||
/* Reset I2S state */
|
||||
if (k_msgq_num_used_get(&stream->queue) == 0) {
|
||||
LOG_DBG("Exit TX callback, no more data in the queue");
|
||||
stream->state = I2S_STATE_READY;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = k_msgq_get(&stream->queue, &item, K_NO_WAIT);
|
||||
if (ret < 0) {
|
||||
stream->state = I2S_STATE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mem_block_tmp = stream->mem_block;
|
||||
|
||||
stream->mem_block = item.buffer;
|
||||
stream->mem_block_len = item.size;
|
||||
|
||||
sys_cache_data_flush_range(stream->mem_block, stream->mem_block_len);
|
||||
|
||||
if (HAL_SAI_Transmit_DMA(hsai, stream->mem_block,
|
||||
stream->mem_block_len / stream->dma_src_size) != HAL_OK) {
|
||||
LOG_ERR("HAL_SAI_Transmit_DMA: <FAILED>");
|
||||
}
|
||||
|
||||
k_mem_slab_free(stream->i2s_cfg.mem_slab, mem_block_tmp);
|
||||
exit:
|
||||
/* EXIT */
|
||||
}
|
||||
|
||||
void HAL_SAI_ErrorCallback(SAI_HandleTypeDef *hsai)
|
||||
{
|
||||
switch (HAL_SAI_GetError(hsai)) {
|
||||
case HAL_SAI_ERROR_NONE:
|
||||
LOG_INF("No error");
|
||||
break;
|
||||
case HAL_SAI_ERROR_OVR:
|
||||
LOG_WRN("Overrun Error");
|
||||
break;
|
||||
case HAL_SAI_ERROR_UDR:
|
||||
LOG_WRN("Underrun Error");
|
||||
break;
|
||||
case HAL_SAI_ERROR_AFSDET:
|
||||
LOG_WRN("Anticipated Frame synchronisation detection");
|
||||
break;
|
||||
case HAL_SAI_ERROR_LFSDET:
|
||||
LOG_WRN("Late Frame synchronisation detection");
|
||||
break;
|
||||
case HAL_SAI_ERROR_CNREADY:
|
||||
LOG_WRN("codec not ready");
|
||||
break;
|
||||
case HAL_SAI_ERROR_TIMEOUT:
|
||||
LOG_WRN("Timeout error");
|
||||
break;
|
||||
case HAL_SAI_ERROR_DMA:
|
||||
LOG_WRN("DMA error");
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
static int stm32_sai_enable_clock(const struct device *dev)
|
||||
{
|
||||
const struct i2s_stm32_sai_cfg *cfg = dev->config;
|
||||
const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
|
||||
int err;
|
||||
|
||||
if (!device_is_ready(clk)) {
|
||||
LOG_ERR("clock control device not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
LOG_DBG("Clock Control Device: <OK>");
|
||||
|
||||
/* Turn on SAI peripheral clock */
|
||||
err = clock_control_on(clk, (clock_control_subsys_t)&cfg->pclken[0]);
|
||||
if (err != 0) {
|
||||
LOG_ERR("I2S clock Enable: <FAILED>");
|
||||
return -EIO;
|
||||
}
|
||||
LOG_DBG("I2S clock Enable: <OK>");
|
||||
|
||||
if (cfg->pclk_len > 1) {
|
||||
/* Enable I2S clock source */
|
||||
err = clock_control_configure(clk, (clock_control_subsys_t)&cfg->pclken[1], NULL);
|
||||
if (err < 0) {
|
||||
LOG_ERR("I2S domain clock configuration: <FAILED>");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
LOG_DBG("I2S domain clock configuration: <OK>");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2s_stm32_sai_dma_init(const struct device *dev)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = dev->data;
|
||||
SAI_HandleTypeDef *hsai = &dev_data->hsai;
|
||||
DMA_HandleTypeDef *hdma = &dev_data->hdma;
|
||||
|
||||
struct stream *stream = &dev_data->stream;
|
||||
|
||||
struct dma_config dma_cfg = dev_data->stream.dma_cfg;
|
||||
|
||||
int ret;
|
||||
|
||||
if (!device_is_ready(stream->dma_dev)) {
|
||||
LOG_ERR("%s DMA device not ready", stream->dma_dev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Proceed to the minimum Zephyr DMA driver init */
|
||||
dma_cfg.user_data = hdma;
|
||||
|
||||
/* HACK: This field is used to inform driver that it is overridden */
|
||||
dma_cfg.linked_channel = STM32_DMA_HAL_OVERRIDE;
|
||||
|
||||
/* Because of the STREAM OFFSET, the DMA channel given here is from 1 - */
|
||||
ret = dma_config(stream->dma_dev, stream->dma_channel, &dma_cfg);
|
||||
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to configure DMA channel %d",
|
||||
stream->dma_channel + STM32_DMA_STREAM_OFFSET);
|
||||
return ret;
|
||||
}
|
||||
|
||||
hdma->Instance = LL_DMA_GET_CHANNEL_INSTANCE(stream->reg, stream->dma_channel);
|
||||
hdma->Init.Request = dma_cfg.dma_slot;
|
||||
hdma->Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
|
||||
hdma->Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
|
||||
hdma->Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;
|
||||
hdma->Init.Priority = DMA_HIGH_PRIORITY;
|
||||
hdma->Init.SrcBurstLength = 1;
|
||||
hdma->Init.DestBurstLength = 1;
|
||||
hdma->Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT0;
|
||||
hdma->Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
|
||||
hdma->Init.Mode = DMA_NORMAL;
|
||||
|
||||
if (stream->dma_cfg.channel_direction == (enum dma_channel_direction)MEMORY_TO_PERIPHERAL) {
|
||||
hdma->Init.Direction = DMA_MEMORY_TO_PERIPH;
|
||||
hdma->Init.SrcInc = DMA_SINC_INCREMENTED;
|
||||
hdma->Init.DestInc = DMA_DINC_FIXED;
|
||||
__HAL_LINKDMA(hsai, hdmatx, dev_data->hdma);
|
||||
} else {
|
||||
hdma->Init.Direction = DMA_PERIPH_TO_MEMORY;
|
||||
hdma->Init.SrcInc = DMA_SINC_FIXED;
|
||||
hdma->Init.DestInc = DMA_DINC_INCREMENTED;
|
||||
__HAL_LINKDMA(hsai, hdmarx, dev_data->hdma);
|
||||
}
|
||||
|
||||
if (HAL_DMA_Init(&dev_data->hdma) != HAL_OK) {
|
||||
LOG_ERR("HAL_DMA_Init: <FAILED>");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (HAL_DMA_ConfigChannelAttributes(&dev_data->hdma, DMA_CHANNEL_NPRIV) != HAL_OK) {
|
||||
LOG_ERR("HAL_DMA_ConfigChannelAttributes: <Failed>");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2s_stm32_sai_initialize(const struct device *dev)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = dev->data;
|
||||
const struct i2s_stm32_sai_cfg *cfg = dev->config;
|
||||
int ret = 0;
|
||||
|
||||
/* Enable SAI clock */
|
||||
ret = stm32_sai_enable_clock(dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Clock enabling failed.");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Configure DT provided pins */
|
||||
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("I2S pinctrl setup: <FAILED>");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!device_is_ready(dev_data->stream.dma_dev)) {
|
||||
LOG_ERR("%s device not ready", dev_data->stream.dma_dev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = k_msgq_alloc_init(&dev_data->stream.queue, sizeof(struct queue_item),
|
||||
CONFIG_I2S_STM32_SAI_BLOCK_COUNT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("k_msgq_alloc_init(): <FAILED>");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Initialize DMA */
|
||||
ret = i2s_stm32_sai_dma_init(dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("i2s_stm32_sai_dma_init(): <FAILED>");
|
||||
return ret;
|
||||
}
|
||||
|
||||
LOG_INF("%s inited", dev->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dma_callback(const struct device *dma_dev, void *arg, uint32_t channel, int status)
|
||||
{
|
||||
DMA_HandleTypeDef *hdma = arg;
|
||||
|
||||
ARG_UNUSED(dma_dev);
|
||||
|
||||
if (status < 0) {
|
||||
LOG_ERR("DMA callback error with channel %d.", channel);
|
||||
}
|
||||
HAL_DMA_IRQHandler(hdma);
|
||||
}
|
||||
|
||||
static int i2s_stm32_sai_configure(const struct device *dev, enum i2s_dir dir,
|
||||
const struct i2s_config *i2s_cfg)
|
||||
{
|
||||
const struct i2s_stm32_sai_cfg *const cfg = dev->config;
|
||||
struct i2s_stm32_sai_data *const dev_data = dev->data;
|
||||
struct stream *stream = &dev_data->stream;
|
||||
SAI_HandleTypeDef *hsai = &dev_data->hsai;
|
||||
uint8_t protocol;
|
||||
uint8_t word_size;
|
||||
|
||||
memcpy(&stream->i2s_cfg, i2s_cfg, sizeof(struct i2s_config));
|
||||
|
||||
stream->master = true;
|
||||
if (i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE ||
|
||||
i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE) {
|
||||
stream->master = false;
|
||||
}
|
||||
|
||||
hsai->Init.Synchro = SAI_ASYNCHRONOUS;
|
||||
|
||||
if (dir == I2S_DIR_RX) {
|
||||
hsai->Init.AudioMode = SAI_MODEMASTER_RX;
|
||||
|
||||
if (stream->master == false) {
|
||||
hsai->Init.AudioMode = SAI_MODESLAVE_RX;
|
||||
if (cfg->synchronous) {
|
||||
hsai->Init.Synchro = SAI_SYNCHRONOUS;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (dir == I2S_DIR_TX) {
|
||||
hsai->Init.AudioMode = SAI_MODEMASTER_TX;
|
||||
|
||||
if (stream->master == false) {
|
||||
hsai->Init.AudioMode = SAI_MODESLAVE_TX;
|
||||
if (cfg->synchronous) {
|
||||
hsai->Init.Synchro = SAI_SYNCHRONOUS;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_ERR("Either RX or TX direction must be selected");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->state != I2S_STATE_NOT_READY && stream->state != I2S_STATE_READY) {
|
||||
LOG_ERR("Invalid state: %d", (int)stream->state);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cfg->mclk_enable && stream->master) {
|
||||
hsai->Init.MckOutput = SAI_MCK_OUTPUT_ENABLE;
|
||||
} else {
|
||||
hsai->Init.MckOutput = SAI_MCK_OUTPUT_DISABLE;
|
||||
}
|
||||
|
||||
if (cfg->mclk_div_enable) {
|
||||
hsai->Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
|
||||
} else {
|
||||
hsai->Init.NoDivider = SAI_MASTERDIVIDER_DISABLED;
|
||||
}
|
||||
|
||||
/* AudioFrequency */
|
||||
switch (stream->i2s_cfg.frame_clk_freq) {
|
||||
case 192000U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_192K;
|
||||
break;
|
||||
case 96000U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_96K;
|
||||
break;
|
||||
case 48000U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_48K;
|
||||
break;
|
||||
case 44100U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_44K;
|
||||
break;
|
||||
case 32000U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_32K;
|
||||
break;
|
||||
case 22050U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_22K;
|
||||
break;
|
||||
case 16000U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_16K;
|
||||
break;
|
||||
case 11025U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_11K;
|
||||
break;
|
||||
case 8000U:
|
||||
hsai->Init.AudioFrequency = SAI_AUDIO_FREQUENCY_8K;
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Invalid frame_clk_freq %u", stream->i2s_cfg.frame_clk_freq);
|
||||
stream->state = I2S_STATE_NOT_READY;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* WordSize */
|
||||
switch (stream->i2s_cfg.word_size) {
|
||||
case 16:
|
||||
word_size = SAI_PROTOCOL_DATASIZE_16BIT;
|
||||
stream->dma_src_size = 2;
|
||||
break;
|
||||
case 24:
|
||||
word_size = SAI_PROTOCOL_DATASIZE_24BIT;
|
||||
stream->dma_src_size = 4;
|
||||
break;
|
||||
case 32:
|
||||
word_size = SAI_PROTOCOL_DATASIZE_32BIT;
|
||||
stream->dma_src_size = 4;
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Invalid wordsize %u", stream->i2s_cfg.word_size);
|
||||
stream->state = I2S_STATE_NOT_READY;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* MonoStereoMode */
|
||||
switch (stream->i2s_cfg.channels) {
|
||||
case 1:
|
||||
hsai->Init.MonoStereoMode = SAI_MONOMODE;
|
||||
LOG_DBG("SAI_MONOMODE");
|
||||
break;
|
||||
case 2:
|
||||
hsai->Init.MonoStereoMode = SAI_STEREOMODE;
|
||||
LOG_DBG("SAI_STEREOMODE");
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("NOT VALID CHANNEL NUMBER %u", stream->i2s_cfg.channels);
|
||||
stream->state = I2S_STATE_NOT_READY;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->i2s_cfg.options & I2S_OPT_PINGPONG) {
|
||||
LOG_ERR("Ping-pong mode not supported");
|
||||
stream->state = I2S_STATE_NOT_READY;
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if ((stream->i2s_cfg.format & I2S_FMT_DATA_ORDER_LSB) ||
|
||||
(stream->i2s_cfg.format & I2S_FMT_BIT_CLK_INV) ||
|
||||
(stream->i2s_cfg.format & I2S_FMT_FRAME_CLK_INV)) {
|
||||
LOG_ERR("Unsupported stream format");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (stream->i2s_cfg.format & I2S_FMT_DATA_FORMAT_MASK) {
|
||||
case I2S_FMT_DATA_FORMAT_I2S:
|
||||
protocol = SAI_I2S_STANDARD;
|
||||
break;
|
||||
case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED:
|
||||
protocol = SAI_I2S_MSBJUSTIFIED;
|
||||
break;
|
||||
case I2S_FMT_DATA_FORMAT_PCM_SHORT:
|
||||
protocol = SAI_PCM_SHORT;
|
||||
break;
|
||||
case I2S_FMT_DATA_FORMAT_PCM_LONG:
|
||||
protocol = SAI_PCM_LONG;
|
||||
break;
|
||||
case I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED:
|
||||
protocol = SAI_I2S_LSBJUSTIFIED;
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unsupported I2S data format");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Initialize SAI peripheral */
|
||||
if (HAL_SAI_InitProtocol(hsai, protocol, word_size, 2) != HAL_OK) {
|
||||
LOG_ERR("HAL_SAI_InitProtocol: <FAILED>");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
stream->state = I2S_STATE_READY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2s_stm32_sai_write(const struct device *dev, void *mem_block, size_t size)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = dev->data;
|
||||
struct stream *stream = &dev_data->stream;
|
||||
int ret;
|
||||
|
||||
if (stream->state != I2S_STATE_RUNNING && stream->state != I2S_STATE_READY) {
|
||||
LOG_ERR("TX Invalid state: %d", (int)stream->state);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (size > stream->i2s_cfg.block_size) {
|
||||
LOG_ERR("Max write size is: %u", (unsigned int)stream->i2s_cfg.block_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
struct queue_item item = {.buffer = mem_block, .size = size};
|
||||
|
||||
ret = k_msgq_put(&stream->queue, &item, K_MSEC(stream->i2s_cfg.timeout));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("TX queue full");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2s_stm32_sai_read(const struct device *dev, void **mem_block, size_t *size)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = dev->data;
|
||||
struct queue_item item;
|
||||
int ret;
|
||||
|
||||
if (dev_data->stream.state == I2S_STATE_NOT_READY ||
|
||||
dev_data->stream.state == I2S_STATE_ERROR) {
|
||||
LOG_ERR("RX invalid state: %d", (int)dev_data->stream.state);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = k_msgq_get(&dev_data->stream.queue, &item, K_MSEC(dev_data->stream.i2s_cfg.timeout));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("RX queue: %d", k_msgq_num_used_get(&dev_data->stream.queue));
|
||||
return ret;
|
||||
}
|
||||
|
||||
*mem_block = item.buffer;
|
||||
*size = item.size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stream_start(const struct device *dev, enum i2s_dir dir)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = dev->data;
|
||||
struct stream *stream = &dev_data->stream;
|
||||
SAI_HandleTypeDef *hsai = &dev_data->hsai;
|
||||
struct queue_item item;
|
||||
int ret;
|
||||
|
||||
if (dir == I2S_DIR_TX) {
|
||||
ret = k_msgq_get(&stream->queue, &item, K_NO_WAIT);
|
||||
if (ret < 0) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
stream->mem_block = item.buffer;
|
||||
stream->mem_block_len = item.size;
|
||||
|
||||
sys_cache_data_flush_range(stream->mem_block, stream->mem_block_len);
|
||||
|
||||
if (HAL_SAI_Transmit_DMA(hsai, stream->mem_block,
|
||||
stream->mem_block_len / stream->dma_src_size) != HAL_OK) {
|
||||
LOG_ERR("HAL_SAI_Transmit_DMA: <FAILED>");
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
|
||||
ret = k_mem_slab_alloc(stream->i2s_cfg.mem_slab, &stream->mem_block, K_NO_WAIT);
|
||||
if (ret < 0) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
stream->mem_block_len = stream->i2s_cfg.block_size;
|
||||
|
||||
if (HAL_SAI_Receive_DMA(hsai, stream->mem_block,
|
||||
stream->mem_block_len / stream->dma_src_size) != HAL_OK) {
|
||||
LOG_ERR("HAL_SAI_Receive_DMA: <FAILED>");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void queue_drop(const struct device *dev)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = dev->data;
|
||||
struct stream *stream = &dev_data->stream;
|
||||
struct queue_item item;
|
||||
|
||||
if (stream->mem_block != NULL) {
|
||||
stream->mem_block = NULL;
|
||||
stream->mem_block_len = 0;
|
||||
}
|
||||
|
||||
while (k_msgq_get(&stream->queue, &item, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Dropping item from queue");
|
||||
k_mem_slab_free(stream->i2s_cfg.mem_slab, item.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static int i2s_stm32_sai_trigger(const struct device *dev, enum i2s_dir dir,
|
||||
enum i2s_trigger_cmd cmd)
|
||||
{
|
||||
struct i2s_stm32_sai_data *dev_data = dev->data;
|
||||
struct stream *stream = &dev_data->stream;
|
||||
unsigned int key;
|
||||
int ret;
|
||||
|
||||
if (dir == I2S_DIR_BOTH) {
|
||||
LOG_ERR("Unsupported direction: %d", (int)dir);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case I2S_TRIGGER_START:
|
||||
LOG_DBG("I2S_TRIGGER_START");
|
||||
|
||||
if (stream->state != I2S_STATE_READY) {
|
||||
LOG_ERR("START trigger: invalid state %d", stream->state);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = stream->stream_start(dev, dir);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("START trigger failed %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
stream->state = I2S_STATE_RUNNING;
|
||||
stream->last_block = false;
|
||||
|
||||
break;
|
||||
case I2S_TRIGGER_STOP:
|
||||
key = irq_lock();
|
||||
LOG_DBG("I2S_TRIGGER_STOP");
|
||||
|
||||
if (stream->state != I2S_STATE_RUNNING) {
|
||||
LOG_ERR("STOP - Invalid state: %d", (int)stream->state);
|
||||
irq_unlock(key);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
stream->last_block = true;
|
||||
stream->state = I2S_STATE_STOPPING;
|
||||
|
||||
irq_unlock(key);
|
||||
break;
|
||||
case I2S_TRIGGER_DRAIN:
|
||||
key = irq_lock();
|
||||
LOG_DBG("I2S_TRIGGER_DRAIN");
|
||||
|
||||
if (stream->state != I2S_STATE_RUNNING) {
|
||||
LOG_ERR("DRAIN - Invalid state: %d", (int)stream->state);
|
||||
irq_unlock(key);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
stream->state = I2S_STATE_STOPPING;
|
||||
|
||||
irq_unlock(key);
|
||||
break;
|
||||
case I2S_TRIGGER_DROP:
|
||||
key = irq_lock();
|
||||
LOG_DBG("I2S_TRIGGER_DROP");
|
||||
|
||||
if (stream->state == I2S_STATE_NOT_READY) {
|
||||
LOG_ERR("DROP - invalid state: %d", (int)stream->state);
|
||||
irq_unlock(key);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
stream->queue_drop(dev);
|
||||
stream->state = I2S_STATE_READY;
|
||||
|
||||
irq_unlock(key);
|
||||
break;
|
||||
case I2S_TRIGGER_PREPARE:
|
||||
key = irq_lock();
|
||||
LOG_DBG("I2S_TRIGGER_PREPARE");
|
||||
|
||||
if (stream->state != I2S_STATE_ERROR) {
|
||||
LOG_ERR("PREPARE - invalid state: %d", (int)stream->state);
|
||||
irq_unlock(key);
|
||||
return -EIO;
|
||||
}
|
||||
stream->queue_drop(dev);
|
||||
stream->state = I2S_STATE_READY;
|
||||
|
||||
irq_unlock(key);
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unsupported trigger command");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEVICE_API(i2s, i2s_stm32_driver_api) = {
|
||||
.configure = i2s_stm32_sai_configure,
|
||||
.trigger = i2s_stm32_sai_trigger,
|
||||
.write = i2s_stm32_sai_write,
|
||||
.read = i2s_stm32_sai_read,
|
||||
|
||||
};
|
||||
|
||||
#define SAI_DMA_CHANNEL_INIT(index, dir, src_dev, dest_dev) \
|
||||
.stream = { \
|
||||
.dma_dev = DEVICE_DT_GET(STM32_DMA_CTLR(index, dir)), \
|
||||
.dma_channel = DT_INST_DMAS_CELL_BY_NAME(index, dir, channel), \
|
||||
.reg = (DMA_TypeDef *)DT_REG_ADDR( \
|
||||
DT_PHANDLE_BY_NAME(DT_DRV_INST(index), dmas, dir)), \
|
||||
.dma_cfg = \
|
||||
{ \
|
||||
.dma_slot = STM32_DMA_SLOT(index, dir, slot), \
|
||||
.channel_direction = src_dev##_TO_##dest_dev, \
|
||||
.dma_callback = dma_callback, \
|
||||
}, \
|
||||
.stream_start = stream_start, \
|
||||
.queue_drop = queue_drop, \
|
||||
}
|
||||
|
||||
#define I2S_STM32_SAI_INIT(index) \
|
||||
\
|
||||
PINCTRL_DT_INST_DEFINE(index); \
|
||||
\
|
||||
static const struct stm32_pclken clk_##index[] = STM32_DT_INST_CLOCKS(index); \
|
||||
\
|
||||
struct i2s_stm32_sai_data sai_data_##index = { \
|
||||
.hsai = \
|
||||
{ \
|
||||
.Instance = (SAI_Block_TypeDef *)DT_INST_REG_ADDR(index), \
|
||||
.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE, \
|
||||
.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_FULL, \
|
||||
.Init.SynchroExt = SAI_SYNCEXT_DISABLE, \
|
||||
.Init.CompandingMode = SAI_NOCOMPANDING, \
|
||||
.Init.TriState = SAI_OUTPUT_NOTRELEASED, \
|
||||
}, \
|
||||
COND_CODE_1(DT_INST_DMAS_HAS_NAME(index, tx), \
|
||||
(SAI_DMA_CHANNEL_INIT(index, tx, MEMORY, PERIPHERAL)), \
|
||||
(SAI_DMA_CHANNEL_INIT(index, rx, PERIPHERAL, MEMORY)))}; \
|
||||
\
|
||||
struct i2s_stm32_sai_cfg sai_config_##index = { \
|
||||
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \
|
||||
.pclken = clk_##index, \
|
||||
.pclk_len = DT_INST_NUM_CLOCKS(index), \
|
||||
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \
|
||||
.mclk_enable = DT_INST_PROP(index, mclk_enable), \
|
||||
.mclk_div_enable = DT_INST_PROP(index, mclk_div_enable), \
|
||||
.synchronous = DT_INST_PROP(index, synchronous), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(index, &i2s_stm32_sai_initialize, NULL, &sai_data_##index, \
|
||||
&sai_config_##index, POST_KERNEL, CONFIG_I2S_INIT_PRIORITY, \
|
||||
&i2s_stm32_driver_api); \
|
||||
\
|
||||
K_MSGQ_DEFINE(queue_##index, sizeof(struct queue_item), CONFIG_I2S_STM32_SAI_BLOCK_COUNT, \
|
||||
4);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(I2S_STM32_SAI_INIT)
|
44
dts/bindings/i2s/st,stm32-sai.yaml
Normal file
44
dts/bindings/i2s/st,stm32-sai.yaml
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Copyright (c) 2024 ZAL Zentrum für Angewandte Luftfahrtforschung GmbH
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: STM32 SAI controller
|
||||
|
||||
compatible: "st,stm32-sai"
|
||||
|
||||
include: [i2s-controller.yaml, pinctrl-device.yaml]
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
dmas:
|
||||
required: true
|
||||
|
||||
dma-names:
|
||||
required: true
|
||||
description: |
|
||||
DMA channel name: "tx" or "rx", depending of expected device behavior.
|
||||
|
||||
pinctrl-0:
|
||||
required: true
|
||||
|
||||
pinctrl-names:
|
||||
required: true
|
||||
|
||||
mclk-div-enable:
|
||||
type: boolean
|
||||
description: |
|
||||
SAI NODIV property.
|
||||
When property is not present, oversampling is enabled @ 256.
|
||||
|
||||
mclk-enable:
|
||||
type: boolean
|
||||
description: |
|
||||
Master Clock Output function.
|
||||
An mck pin must be listed within pinctrl-0 when enabling this property.
|
||||
|
||||
synchronous:
|
||||
type: boolean
|
||||
description: |
|
||||
Synchronous mode.
|
||||
When present, the SAI controller is configured to work in synchronous mode.
|
Loading…
Add table
Add a link
Reference in a new issue