Browse Source

drivers: i2s: add sai support for stm32u5xx

This PR adds initial sai support for STM32u5xx

Signed-off-by: Mario Paja <mario.paja@zal.aero>
pull/89972/merge
Mario Paja 11 months ago committed by Benjamin Cabé
parent
commit
041f942ba8
  1. 1
      drivers/i2s/CMakeLists.txt
  2. 20
      drivers/i2s/Kconfig.stm32
  3. 820
      drivers/i2s/i2s_stm32_sai.c
  4. 44
      dts/bindings/i2s/st,stm32-sai.yaml

1
drivers/i2s/CMakeLists.txt

@ -16,3 +16,4 @@ zephyr_library_sources_ifdef(CONFIG_I2S_MCUX_SAI i2s_mcux_sai.c) @@ -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)

20
drivers/i2s/Kconfig.stm32

@ -25,3 +25,23 @@ config I2S_STM32_TX_BLOCK_COUNT @@ -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

@ -0,0 +1,820 @@ @@ -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

@ -0,0 +1,44 @@ @@ -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…
Cancel
Save