You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
979 lines
25 KiB
979 lines
25 KiB
/* |
|
* Copyright (c) 2018 STMicroelectronics |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT st_stm32_i2s |
|
|
|
#include <string.h> |
|
#include <zephyr/drivers/dma.h> |
|
#include <zephyr/drivers/i2s.h> |
|
#include <zephyr/drivers/dma/dma_stm32.h> |
|
#include <soc.h> |
|
#include <stm32_ll_rcc.h> |
|
#include <stm32_ll_spi.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 "i2s_ll_stm32.h" |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/irq.h> |
|
LOG_MODULE_REGISTER(i2s_ll_stm32); |
|
|
|
static unsigned int div_round_closest(uint32_t dividend, uint32_t divisor) |
|
{ |
|
return (dividend + (divisor / 2U)) / divisor; |
|
} |
|
|
|
static bool queue_is_empty(struct k_msgq *q) |
|
{ |
|
return (k_msgq_num_used_get(q) == 0) ? true : false; |
|
} |
|
|
|
/* |
|
* Get data from the queue |
|
*/ |
|
static int queue_get(struct k_msgq *q, void **mem_block, size_t *size, int32_t timeout) |
|
{ |
|
struct queue_item item; |
|
int result = k_msgq_get(q, &item, SYS_TIMEOUT_MS(timeout)); |
|
|
|
if (result == 0) { |
|
*mem_block = item.mem_block; |
|
*size = item.size; |
|
} |
|
return result; |
|
} |
|
|
|
/* |
|
* Put data in the queue |
|
*/ |
|
static int queue_put(struct k_msgq *q, void *mem_block, size_t size, int32_t timeout) |
|
{ |
|
struct queue_item item = {.mem_block = mem_block, .size = size}; |
|
|
|
return k_msgq_put(q, &item, SYS_TIMEOUT_MS(timeout)); |
|
} |
|
|
|
static void stream_queue_drop(struct stream *s) |
|
{ |
|
size_t size; |
|
void *mem_block; |
|
|
|
while (queue_get(s->msgq, &mem_block, &size, 0) == 0) { |
|
k_mem_slab_free(s->cfg.mem_slab, mem_block); |
|
} |
|
} |
|
|
|
static int i2s_stm32_enable_clock(const struct device *dev) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
const struct device *clk; |
|
int ret; |
|
|
|
clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); |
|
|
|
if (!device_is_ready(clk)) { |
|
LOG_ERR("clock control device not ready"); |
|
return -ENODEV; |
|
} |
|
|
|
ret = clock_control_on(clk, (clock_control_subsys_t)&cfg->pclken[0]); |
|
if (ret != 0) { |
|
LOG_ERR("Could not enable I2S clock"); |
|
return -EIO; |
|
} |
|
|
|
if (cfg->pclk_len > 1) { |
|
/* Enable I2S clock source */ |
|
ret = clock_control_configure(clk, |
|
(clock_control_subsys_t)&cfg->pclken[1], |
|
NULL); |
|
if (ret < 0) { |
|
LOG_ERR("Could not configure I2S domain clock"); |
|
return -EIO; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int i2s_stm32_set_clock(const struct device *dev, |
|
uint32_t bit_clk_freq) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
uint32_t freq_in = 0U; |
|
uint8_t i2s_div, i2s_odd; |
|
|
|
if (cfg->pclk_len > 1) { |
|
/* Handle multiple clock sources */ |
|
if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), |
|
(clock_control_subsys_t)&cfg->pclken[1], |
|
&freq_in) < 0) { |
|
LOG_ERR("Failed call clock_control_get_rate(pclken[1])"); |
|
return -EIO; |
|
} |
|
} else { |
|
/* Handle single clock source */ |
|
if (clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), |
|
(clock_control_subsys_t)&cfg->pclken[0], |
|
&freq_in) < 0) { |
|
LOG_ERR("Failed call clock_control_get_rate(pclken[0])"); |
|
return -EIO; |
|
} |
|
} |
|
/* |
|
* The ratio between input clock (I2SxClk) and output |
|
* clock on the pad (I2S_CK) is obtained using the |
|
* following formula: |
|
* (i2s_div * 2) + i2s_odd |
|
*/ |
|
i2s_div = div_round_closest(freq_in, bit_clk_freq); |
|
i2s_odd = (i2s_div & 0x1) ? 1 : 0; |
|
i2s_div >>= 1; |
|
|
|
/* i2s_div == 0 || i2s_div == 1 are forbidden */ |
|
if (i2s_div < 2U) { |
|
LOG_ERR("The linear prescaler value is unsupported"); |
|
return -EINVAL; |
|
} |
|
|
|
LOG_DBG("i2s_div: %d - i2s_odd: %d", i2s_div, i2s_odd); |
|
|
|
LL_I2S_SetPrescalerLinear(cfg->i2s, i2s_div); |
|
LL_I2S_SetPrescalerParity(cfg->i2s, i2s_odd); |
|
|
|
return 0; |
|
} |
|
|
|
static int i2s_stm32_configure(const struct device *dev, enum i2s_dir dir, |
|
const struct i2s_config *i2s_cfg) |
|
{ |
|
const struct i2s_stm32_cfg *const cfg = dev->config; |
|
struct i2s_stm32_data *const dev_data = dev->data; |
|
/* For words greater than 16-bit the channel length is considered 32-bit */ |
|
const uint32_t channel_length = i2s_cfg->word_size > 16U ? 32U : 16U; |
|
/* |
|
* comply with the i2s_config driver remark: |
|
* When I2S data format is selected parameter channels is ignored, |
|
* number of words in a frame is always 2. |
|
*/ |
|
const uint32_t num_channels = |
|
((i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) == I2S_FMT_DATA_FORMAT_I2S) |
|
? 2U |
|
: i2s_cfg->channels; |
|
struct stream *stream; |
|
uint32_t bit_clk_freq; |
|
bool enable_mck; |
|
int ret; |
|
|
|
if (dir == I2S_DIR_RX) { |
|
stream = &dev_data->rx; |
|
} else if (dir == I2S_DIR_TX) { |
|
stream = &dev_data->tx; |
|
} else if (dir == I2S_DIR_BOTH) { |
|
return -ENOSYS; |
|
} 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"); |
|
return -EINVAL; |
|
} |
|
|
|
stream->master = true; |
|
if (i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE || |
|
i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE) { |
|
stream->master = false; |
|
} |
|
|
|
if (i2s_cfg->frame_clk_freq == 0U) { |
|
stream_queue_drop(stream); |
|
memset(&stream->cfg, 0, sizeof(struct i2s_config)); |
|
stream->state = I2S_STATE_NOT_READY; |
|
return 0; |
|
} |
|
|
|
memcpy(&stream->cfg, i2s_cfg, sizeof(struct i2s_config)); |
|
|
|
/* conditions to enable master clock output */ |
|
enable_mck = stream->master && cfg->master_clk_sel; |
|
|
|
/* set I2S bitclock */ |
|
bit_clk_freq = i2s_cfg->frame_clk_freq * |
|
channel_length * num_channels; |
|
|
|
if (enable_mck) { |
|
/* |
|
* Compensate for the master clock dividers. |
|
* MCK = N * CK, where N: |
|
* 8 when the channel frame is 16-bit wide |
|
* 4 when the channel frame is 32-bit wide |
|
*/ |
|
bit_clk_freq *= channel_length == 16U ? 4U * 2U : 4U; |
|
} |
|
|
|
ret = i2s_stm32_set_clock(dev, bit_clk_freq); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
/* set I2S Master Clock output in the MCK pin, enabled in the DT */ |
|
if (enable_mck) { |
|
LL_I2S_EnableMasterClock(cfg->i2s); |
|
} else { |
|
LL_I2S_DisableMasterClock(cfg->i2s); |
|
} |
|
|
|
/* |
|
* set I2S Data Format |
|
* 16-bit data extended on 32-bit channel length excluded |
|
*/ |
|
if (i2s_cfg->word_size == 16U) { |
|
LL_I2S_SetDataFormat(cfg->i2s, LL_I2S_DATAFORMAT_16B); |
|
} else if (i2s_cfg->word_size == 24U) { |
|
LL_I2S_SetDataFormat(cfg->i2s, LL_I2S_DATAFORMAT_24B); |
|
} else if (i2s_cfg->word_size == 32U) { |
|
LL_I2S_SetDataFormat(cfg->i2s, LL_I2S_DATAFORMAT_32B); |
|
} else { |
|
LOG_ERR("invalid word size"); |
|
return -EINVAL; |
|
} |
|
|
|
/* set I2S Standard */ |
|
switch (i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK) { |
|
case I2S_FMT_DATA_FORMAT_I2S: |
|
LL_I2S_SetStandard(cfg->i2s, LL_I2S_STANDARD_PHILIPS); |
|
break; |
|
|
|
case I2S_FMT_DATA_FORMAT_PCM_SHORT: |
|
LL_I2S_SetStandard(cfg->i2s, LL_I2S_STANDARD_PCM_SHORT); |
|
break; |
|
|
|
case I2S_FMT_DATA_FORMAT_PCM_LONG: |
|
LL_I2S_SetStandard(cfg->i2s, LL_I2S_STANDARD_PCM_LONG); |
|
break; |
|
|
|
case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED: |
|
LL_I2S_SetStandard(cfg->i2s, LL_I2S_STANDARD_MSB); |
|
break; |
|
|
|
case I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED: |
|
LL_I2S_SetStandard(cfg->i2s, LL_I2S_STANDARD_LSB); |
|
break; |
|
|
|
default: |
|
LOG_ERR("Unsupported I2S data format"); |
|
return -EINVAL; |
|
} |
|
|
|
/* set I2S clock polarity */ |
|
if ((i2s_cfg->format & I2S_FMT_CLK_FORMAT_MASK) == I2S_FMT_BIT_CLK_INV) { |
|
LL_I2S_SetClockPolarity(cfg->i2s, LL_I2S_POLARITY_HIGH); |
|
} else { |
|
LL_I2S_SetClockPolarity(cfg->i2s, LL_I2S_POLARITY_LOW); |
|
} |
|
|
|
stream->state = I2S_STATE_READY; |
|
return 0; |
|
} |
|
|
|
static int i2s_stm32_trigger(const struct device *dev, enum i2s_dir dir, |
|
enum i2s_trigger_cmd cmd) |
|
{ |
|
struct i2s_stm32_data *const dev_data = dev->data; |
|
const struct i2s_stm32_cfg *const cfg = dev->config; |
|
struct stream *stream; |
|
unsigned int key; |
|
int ret; |
|
|
|
if (dir == I2S_DIR_RX) { |
|
stream = &dev_data->rx; |
|
} else if (dir == I2S_DIR_TX) { |
|
stream = &dev_data->tx; |
|
} else if (dir == I2S_DIR_BOTH) { |
|
return -ENOSYS; |
|
} else { |
|
LOG_ERR("Either RX or TX direction must be selected"); |
|
return -EINVAL; |
|
} |
|
|
|
switch (cmd) { |
|
case I2S_TRIGGER_START: |
|
if (stream->state != I2S_STATE_READY) { |
|
LOG_ERR("START trigger: invalid state %d", |
|
stream->state); |
|
return -EIO; |
|
} |
|
|
|
__ASSERT_NO_MSG(stream->mem_block == NULL); |
|
|
|
ret = stream->stream_start(stream, dev); |
|
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(); |
|
if (stream->state != I2S_STATE_RUNNING) { |
|
irq_unlock(key); |
|
LOG_ERR("STOP trigger: invalid state"); |
|
return -EIO; |
|
} |
|
do_trigger_stop: |
|
if (ll_func_i2s_dma_busy(cfg->i2s)) { |
|
stream->state = I2S_STATE_STOPPING; |
|
/* |
|
* Indicate that the transition to I2S_STATE_STOPPING |
|
* is triggered by STOP command |
|
*/ |
|
stream->tx_stop_for_drain = false; |
|
} else { |
|
stream->stream_disable(stream, dev); |
|
stream->state = I2S_STATE_READY; |
|
stream->last_block = true; |
|
} |
|
irq_unlock(key); |
|
break; |
|
|
|
case I2S_TRIGGER_DRAIN: |
|
key = irq_lock(); |
|
if (stream->state != I2S_STATE_RUNNING) { |
|
irq_unlock(key); |
|
LOG_ERR("DRAIN trigger: invalid state"); |
|
return -EIO; |
|
} |
|
|
|
if (dir == I2S_DIR_TX) { |
|
if ((queue_is_empty(stream->msgq) == false) || |
|
(ll_func_i2s_dma_busy(cfg->i2s))) { |
|
stream->state = I2S_STATE_STOPPING; |
|
/* |
|
* Indicate that the transition to I2S_STATE_STOPPING |
|
* is triggered by DRAIN command |
|
*/ |
|
stream->tx_stop_for_drain = true; |
|
} else { |
|
stream->stream_disable(stream, dev); |
|
stream->state = I2S_STATE_READY; |
|
} |
|
} else if (dir == I2S_DIR_RX) { |
|
goto do_trigger_stop; |
|
} else { |
|
LOG_ERR("Unavailable direction"); |
|
return -EINVAL; |
|
} |
|
irq_unlock(key); |
|
break; |
|
|
|
case I2S_TRIGGER_DROP: |
|
if (stream->state == I2S_STATE_NOT_READY) { |
|
LOG_ERR("DROP trigger: invalid state"); |
|
return -EIO; |
|
} |
|
stream->stream_disable(stream, dev); |
|
stream_queue_drop(stream); |
|
stream->state = I2S_STATE_READY; |
|
break; |
|
|
|
case I2S_TRIGGER_PREPARE: |
|
if (stream->state != I2S_STATE_ERROR) { |
|
LOG_ERR("PREPARE trigger: invalid state"); |
|
return -EIO; |
|
} |
|
stream->state = I2S_STATE_READY; |
|
stream_queue_drop(stream); |
|
break; |
|
|
|
default: |
|
LOG_ERR("Unsupported trigger command"); |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int i2s_stm32_read(const struct device *dev, void **mem_block, |
|
size_t *size) |
|
{ |
|
struct i2s_stm32_data *const dev_data = dev->data; |
|
int ret; |
|
|
|
if (dev_data->rx.state == I2S_STATE_NOT_READY) { |
|
LOG_DBG("invalid state"); |
|
return -EIO; |
|
} |
|
|
|
/* Get data from the beginning of RX queue */ |
|
ret = queue_get(dev_data->rx.msgq, mem_block, size, dev_data->rx.cfg.timeout); |
|
if (ret < 0) { |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int i2s_stm32_write(const struct device *dev, void *mem_block, |
|
size_t size) |
|
{ |
|
struct i2s_stm32_data *const dev_data = dev->data; |
|
|
|
if (dev_data->tx.state != I2S_STATE_RUNNING && |
|
dev_data->tx.state != I2S_STATE_READY) { |
|
LOG_DBG("invalid state"); |
|
return -EIO; |
|
} |
|
|
|
/* Add data to the end of the TX queue */ |
|
return queue_put(dev_data->tx.msgq, mem_block, size, dev_data->tx.cfg.timeout); |
|
} |
|
|
|
static DEVICE_API(i2s, i2s_stm32_driver_api) = { |
|
.configure = i2s_stm32_configure, |
|
.read = i2s_stm32_read, |
|
.write = i2s_stm32_write, |
|
.trigger = i2s_stm32_trigger, |
|
}; |
|
|
|
#define STM32_DMA_NUM_CHANNELS 8 |
|
static const struct device *active_dma_rx_channel[STM32_DMA_NUM_CHANNELS]; |
|
static const struct device *active_dma_tx_channel[STM32_DMA_NUM_CHANNELS]; |
|
|
|
static int reload_dma(const struct device *dev_dma, uint32_t channel, |
|
struct dma_config *dcfg, void *src, void *dst, |
|
uint32_t blk_size) |
|
{ |
|
int ret; |
|
|
|
ret = dma_reload(dev_dma, channel, (uint32_t)src, (uint32_t)dst, blk_size); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
ret = dma_start(dev_dma, channel); |
|
|
|
return ret; |
|
} |
|
|
|
static int start_dma(const struct device *dev_dma, uint32_t channel, |
|
struct dma_config *dcfg, void *src, |
|
bool src_addr_increment, void *dst, |
|
bool dst_addr_increment, uint8_t fifo_threshold, |
|
uint32_t blk_size) |
|
{ |
|
struct dma_block_config blk_cfg; |
|
int ret; |
|
|
|
memset(&blk_cfg, 0, sizeof(blk_cfg)); |
|
blk_cfg.block_size = blk_size; |
|
blk_cfg.source_address = (uint32_t)src; |
|
blk_cfg.dest_address = (uint32_t)dst; |
|
if (src_addr_increment) { |
|
blk_cfg.source_addr_adj = DMA_ADDR_ADJ_INCREMENT; |
|
} else { |
|
blk_cfg.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
} |
|
if (dst_addr_increment) { |
|
blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_INCREMENT; |
|
} else { |
|
blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
} |
|
blk_cfg.fifo_mode_control = fifo_threshold; |
|
|
|
dcfg->head_block = &blk_cfg; |
|
|
|
ret = dma_config(dev_dma, channel, dcfg); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
ret = dma_start(dev_dma, channel); |
|
|
|
return ret; |
|
} |
|
|
|
static const struct device *get_dev_from_rx_dma_channel(uint32_t dma_channel); |
|
static const struct device *get_dev_from_tx_dma_channel(uint32_t dma_channel); |
|
static void rx_stream_disable(struct stream *stream, const struct device *dev); |
|
static void tx_stream_disable(struct stream *stream, const struct device *dev); |
|
|
|
/* This function is executed in the interrupt context */ |
|
static void dma_rx_callback(const struct device *dma_dev, void *arg, |
|
uint32_t channel, int status) |
|
{ |
|
const struct device *dev = get_dev_from_rx_dma_channel(channel); |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
struct i2s_stm32_data *const dev_data = dev->data; |
|
struct stream *stream = &dev_data->rx; |
|
void *mblk_tmp; |
|
int ret; |
|
|
|
if (status < 0) { |
|
ret = -EIO; |
|
stream->state = I2S_STATE_ERROR; |
|
goto rx_disable; |
|
} |
|
|
|
__ASSERT_NO_MSG(stream->mem_block != NULL); |
|
|
|
/* Stop reception if there was an error */ |
|
if (stream->state == I2S_STATE_ERROR) { |
|
goto rx_disable; |
|
} |
|
|
|
mblk_tmp = stream->mem_block; |
|
|
|
/* Prepare to receive the next data block */ |
|
ret = k_mem_slab_alloc(stream->cfg.mem_slab, &stream->mem_block, |
|
K_NO_WAIT); |
|
if (ret < 0) { |
|
stream->state = I2S_STATE_ERROR; |
|
goto rx_disable; |
|
} |
|
|
|
ret = reload_dma(stream->dev_dma, stream->dma_channel, |
|
&stream->dma_cfg, |
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
(void *)LL_SPI_DMA_GetRxRegAddr(cfg->i2s), |
|
#else |
|
(void *)LL_SPI_DMA_GetRegAddr(cfg->i2s), |
|
#endif |
|
stream->mem_block, |
|
stream->cfg.block_size); |
|
if (ret < 0) { |
|
LOG_DBG("Failed to start RX DMA transfer: %d", ret); |
|
goto rx_disable; |
|
} |
|
|
|
/* Assure cache coherency after DMA write operation */ |
|
sys_cache_data_invd_range(mblk_tmp, stream->cfg.block_size); |
|
|
|
/* All block data received */ |
|
ret = queue_put(stream->msgq, mblk_tmp, |
|
stream->cfg.block_size, 0); |
|
if (ret < 0) { |
|
stream->state = I2S_STATE_ERROR; |
|
goto rx_disable; |
|
} |
|
|
|
/* Stop reception if we were requested */ |
|
if (stream->state == I2S_STATE_STOPPING) { |
|
stream->state = I2S_STATE_READY; |
|
goto rx_disable; |
|
} |
|
|
|
return; |
|
|
|
rx_disable: |
|
rx_stream_disable(stream, dev); |
|
} |
|
|
|
static void dma_tx_callback(const struct device *dma_dev, void *arg, |
|
uint32_t channel, int status) |
|
{ |
|
const struct device *dev = get_dev_from_tx_dma_channel(channel); |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
struct i2s_stm32_data *const dev_data = dev->data; |
|
struct stream *stream = &dev_data->tx; |
|
size_t mem_block_size; |
|
int ret; |
|
|
|
if (status < 0) { |
|
ret = -EIO; |
|
stream->state = I2S_STATE_ERROR; |
|
goto tx_disable; |
|
} |
|
|
|
__ASSERT_NO_MSG(stream->mem_block != NULL); |
|
|
|
/* All block data sent */ |
|
k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block); |
|
stream->mem_block = NULL; |
|
|
|
/* Stop transmission if there was an error */ |
|
if (stream->state == I2S_STATE_ERROR) { |
|
LOG_ERR("TX error detected"); |
|
goto tx_disable; |
|
} |
|
|
|
/* Check if we finished transferring one block and stopping is requested */ |
|
if ((stream->state == I2S_STATE_STOPPING) && (status == DMA_STATUS_COMPLETE)) { |
|
/* |
|
* Check if all tx samples have been completely handled |
|
* as stated in zephyr i2s specification, in case of DRAIN command |
|
* send all data in the transmit queue and stop the transmission. |
|
*/ |
|
if (queue_is_empty(stream->msgq) == true) { |
|
stream_queue_drop(stream); |
|
stream->state = I2S_STATE_READY; |
|
goto tx_disable; |
|
} else if (stream->tx_stop_for_drain == false) { |
|
/* |
|
* In case of STOP command, just stop the transmission |
|
* at the current. The transmission can be resumed. |
|
*/ |
|
stream->state = I2S_STATE_READY; |
|
goto tx_disable; |
|
} |
|
/* else: DRAIN trigger -> continue TX normally until queue is empty */ |
|
} |
|
|
|
/* Stop transmission if we were requested */ |
|
if (stream->last_block) { |
|
stream->state = I2S_STATE_READY; |
|
goto tx_disable; |
|
} |
|
|
|
/* Prepare to send the next data block */ |
|
ret = queue_get(stream->msgq, &stream->mem_block, |
|
&mem_block_size, 0); |
|
if (ret < 0) { |
|
if (stream->state == I2S_STATE_STOPPING) { |
|
stream->state = I2S_STATE_READY; |
|
} else { |
|
stream->state = I2S_STATE_ERROR; |
|
} |
|
goto tx_disable; |
|
} |
|
|
|
/* Assure cache coherency before DMA read operation */ |
|
sys_cache_data_flush_range(stream->mem_block, mem_block_size); |
|
|
|
ret = reload_dma(stream->dev_dma, stream->dma_channel, |
|
&stream->dma_cfg, |
|
stream->mem_block, |
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
(void *)LL_SPI_DMA_GetTxRegAddr(cfg->i2s), |
|
#else |
|
(void *)LL_SPI_DMA_GetRegAddr(cfg->i2s), |
|
#endif |
|
mem_block_size); |
|
if (ret < 0) { |
|
LOG_DBG("Failed to start TX DMA transfer: %d", ret); |
|
goto tx_disable; |
|
} |
|
|
|
return; |
|
|
|
tx_disable: |
|
tx_stream_disable(stream, dev); |
|
} |
|
|
|
static uint32_t i2s_stm32_irq_count; |
|
static uint32_t i2s_stm32_irq_ovr_count; |
|
static uint32_t i2s_stm32_irq_udr_count; |
|
|
|
static void i2s_stm32_isr(const struct device *dev) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
|
|
/* OVR error must be explicitly cleared */ |
|
if (LL_I2S_IsActiveFlag_OVR(cfg->i2s)) { |
|
i2s_stm32_irq_ovr_count++; |
|
LL_I2S_ClearFlag_OVR(cfg->i2s); |
|
} |
|
|
|
/* NOTE: UDR error must be explicitly cleared on STM32H7 */ |
|
if (LL_I2S_IsActiveFlag_UDR(cfg->i2s)) { |
|
i2s_stm32_irq_udr_count++; |
|
LL_I2S_ClearFlag_UDR(cfg->i2s); |
|
} |
|
|
|
i2s_stm32_irq_count++; |
|
} |
|
|
|
static int i2s_stm32_initialize(const struct device *dev) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
struct i2s_stm32_data *const dev_data = dev->data; |
|
struct stream *stream = &dev_data->tx; |
|
int ret, i; |
|
|
|
/* Initialize the variable used to handle the TX */ |
|
stream->tx_stop_for_drain = false; |
|
|
|
/* Enable I2S clock propagation */ |
|
ret = i2s_stm32_enable_clock(dev); |
|
if (ret < 0) { |
|
LOG_ERR("%s: clock enabling failed: %d", __func__, ret); |
|
return -EIO; |
|
} |
|
|
|
/* Configure dt provided device signals when available */ |
|
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
|
if (ret < 0) { |
|
LOG_ERR("I2S pinctrl setup failed (%d)", ret); |
|
return ret; |
|
} |
|
|
|
cfg->irq_config(dev); |
|
|
|
for (i = 0; i < STM32_DMA_NUM_CHANNELS; i++) { |
|
active_dma_rx_channel[i] = NULL; |
|
active_dma_tx_channel[i] = NULL; |
|
} |
|
|
|
/* Get the binding to the DMA device */ |
|
if (!device_is_ready(dev_data->tx.dev_dma)) { |
|
LOG_ERR("%s device not ready", dev_data->tx.dev_dma->name); |
|
return -ENODEV; |
|
} |
|
if (!device_is_ready(dev_data->rx.dev_dma)) { |
|
LOG_ERR("%s device not ready", dev_data->rx.dev_dma->name); |
|
return -ENODEV; |
|
} |
|
|
|
LOG_INF("%s inited", dev->name); |
|
|
|
return 0; |
|
} |
|
|
|
static int rx_stream_start(struct stream *stream, const struct device *dev) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
int ret; |
|
|
|
ret = k_mem_slab_alloc(stream->cfg.mem_slab, &stream->mem_block, |
|
K_NO_WAIT); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
if (stream->master) { |
|
LL_I2S_SetTransferMode(cfg->i2s, LL_I2S_MODE_MASTER_RX); |
|
} else { |
|
LL_I2S_SetTransferMode(cfg->i2s, LL_I2S_MODE_SLAVE_RX); |
|
} |
|
|
|
/* remember active RX DMA channel (used in callback) */ |
|
active_dma_rx_channel[stream->dma_channel] = dev; |
|
|
|
ret = start_dma(stream->dev_dma, stream->dma_channel, |
|
&stream->dma_cfg, |
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
(void *)LL_SPI_DMA_GetRxRegAddr(cfg->i2s), |
|
#else |
|
(void *)LL_SPI_DMA_GetRegAddr(cfg->i2s), |
|
#endif |
|
stream->src_addr_increment, stream->mem_block, |
|
stream->dst_addr_increment, stream->fifo_threshold, |
|
stream->cfg.block_size); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to start RX DMA transfer: %d", ret); |
|
return ret; |
|
} |
|
|
|
LL_I2S_EnableDMAReq_RX(cfg->i2s); |
|
|
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
LL_I2S_EnableIT_OVR(cfg->i2s); |
|
LL_I2S_EnableIT_UDR(cfg->i2s); |
|
LL_I2S_EnableIT_FRE(cfg->i2s); |
|
LL_I2S_Enable(cfg->i2s); |
|
LL_SPI_StartMasterTransfer(cfg->i2s); |
|
#else |
|
LL_I2S_EnableIT_ERR(cfg->i2s); |
|
LL_I2S_Enable(cfg->i2s); |
|
#endif |
|
|
|
|
|
return 0; |
|
} |
|
|
|
static int tx_stream_start(struct stream *stream, const struct device *dev) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
size_t mem_block_size; |
|
int ret; |
|
|
|
ret = queue_get(stream->msgq, &stream->mem_block, |
|
&mem_block_size, 0); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
/* Assure cache coherency before DMA read operation */ |
|
sys_cache_data_flush_range(stream->mem_block, mem_block_size); |
|
|
|
if (stream->master) { |
|
LL_I2S_SetTransferMode(cfg->i2s, LL_I2S_MODE_MASTER_TX); |
|
} else { |
|
LL_I2S_SetTransferMode(cfg->i2s, LL_I2S_MODE_SLAVE_TX); |
|
} |
|
|
|
/* remember active TX DMA channel (used in callback) */ |
|
active_dma_tx_channel[stream->dma_channel] = dev; |
|
|
|
ret = start_dma(stream->dev_dma, stream->dma_channel, |
|
&stream->dma_cfg, |
|
stream->mem_block, stream->src_addr_increment, |
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
(void *)LL_SPI_DMA_GetTxRegAddr(cfg->i2s), |
|
#else |
|
(void *)LL_SPI_DMA_GetRegAddr(cfg->i2s), |
|
#endif |
|
stream->dst_addr_increment, stream->fifo_threshold, |
|
stream->cfg.block_size); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to start TX DMA transfer: %d", ret); |
|
return ret; |
|
} |
|
|
|
LL_I2S_EnableDMAReq_TX(cfg->i2s); |
|
|
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
LL_I2S_EnableIT_OVR(cfg->i2s); |
|
LL_I2S_EnableIT_UDR(cfg->i2s); |
|
LL_I2S_EnableIT_FRE(cfg->i2s); |
|
|
|
LL_I2S_Enable(cfg->i2s); |
|
LL_SPI_StartMasterTransfer(cfg->i2s); |
|
#else |
|
LL_I2S_EnableIT_ERR(cfg->i2s); |
|
LL_I2S_Enable(cfg->i2s); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
static void rx_stream_disable(struct stream *stream, const struct device *dev) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
|
|
LL_I2S_DisableDMAReq_RX(cfg->i2s); |
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
LL_I2S_DisableIT_OVR(cfg->i2s); |
|
LL_I2S_DisableIT_UDR(cfg->i2s); |
|
LL_I2S_DisableIT_FRE(cfg->i2s); |
|
#else |
|
LL_I2S_DisableIT_ERR(cfg->i2s); |
|
#endif |
|
|
|
dma_stop(stream->dev_dma, stream->dma_channel); |
|
if (stream->mem_block != NULL) { |
|
k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block); |
|
stream->mem_block = NULL; |
|
} |
|
|
|
LL_I2S_Disable(cfg->i2s); |
|
|
|
active_dma_rx_channel[stream->dma_channel] = NULL; |
|
} |
|
|
|
static void tx_stream_disable(struct stream *stream, const struct device *dev) |
|
{ |
|
const struct i2s_stm32_cfg *cfg = dev->config; |
|
|
|
LL_I2S_DisableDMAReq_TX(cfg->i2s); |
|
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_i2s) |
|
LL_I2S_DisableIT_OVR(cfg->i2s); |
|
LL_I2S_DisableIT_UDR(cfg->i2s); |
|
LL_I2S_DisableIT_FRE(cfg->i2s); |
|
#else |
|
LL_I2S_DisableIT_ERR(cfg->i2s); |
|
#endif |
|
|
|
dma_stop(stream->dev_dma, stream->dma_channel); |
|
if (stream->mem_block != NULL) { |
|
k_mem_slab_free(stream->cfg.mem_slab, stream->mem_block); |
|
stream->mem_block = NULL; |
|
} |
|
|
|
/* Wait for TX queue to drain before disabling */ |
|
k_busy_wait(100); |
|
LL_I2S_Disable(cfg->i2s); |
|
|
|
active_dma_tx_channel[stream->dma_channel] = NULL; |
|
} |
|
|
|
static const struct device *get_dev_from_rx_dma_channel(uint32_t dma_channel) |
|
{ |
|
return active_dma_rx_channel[dma_channel]; |
|
} |
|
|
|
static const struct device *get_dev_from_tx_dma_channel(uint32_t dma_channel) |
|
{ |
|
return active_dma_tx_channel[dma_channel]; |
|
} |
|
|
|
/* src_dev and dest_dev should be 'MEMORY' or 'PERIPHERAL'. */ |
|
#define I2S_DMA_CHANNEL_INIT(index, dir, dir_cap, src_dev, dest_dev) \ |
|
.dir = { \ |
|
.dev_dma = DEVICE_DT_GET(STM32_DMA_CTLR(index, dir)), \ |
|
.dma_channel = DT_INST_DMAS_CELL_BY_NAME(index, dir, channel), \ |
|
.dma_cfg = { \ |
|
.block_count = 2, \ |
|
.dma_slot = STM32_DMA_SLOT(index, dir, slot),\ |
|
.channel_direction = src_dev##_TO_##dest_dev, \ |
|
.source_data_size = 2, /* 16bit default */ \ |
|
.dest_data_size = 2, /* 16bit default */ \ |
|
.source_burst_length = 1, /* SINGLE transfer */ \ |
|
.dest_burst_length = 1, \ |
|
.channel_priority = STM32_DMA_CONFIG_PRIORITY( \ |
|
STM32_DMA_CHANNEL_CONFIG(index, dir)),\ |
|
.dma_callback = dma_##dir##_callback, \ |
|
}, \ |
|
.src_addr_increment = STM32_DMA_CONFIG_##src_dev##_ADDR_INC( \ |
|
STM32_DMA_CHANNEL_CONFIG(index, dir)), \ |
|
.dst_addr_increment = STM32_DMA_CONFIG_##dest_dev##_ADDR_INC( \ |
|
STM32_DMA_CHANNEL_CONFIG(index, dir)), \ |
|
.fifo_threshold = STM32_DMA_FEATURES_FIFO_THRESHOLD( \ |
|
STM32_DMA_FEATURES(index, dir)), \ |
|
.stream_start = dir##_stream_start, \ |
|
.stream_disable = dir##_stream_disable, \ |
|
.msgq = &dir##_##index##_queue, \ |
|
} |
|
|
|
#define I2S_STM32_INIT(index) \ |
|
\ |
|
static void i2s_stm32_irq_config_func_##index(const struct device *dev);\ |
|
\ |
|
PINCTRL_DT_INST_DEFINE(index); \ |
|
\ |
|
static const struct stm32_pclken clk_##index[] = \ |
|
STM32_DT_INST_CLOCKS(index); \ |
|
\ |
|
static const struct i2s_stm32_cfg i2s_stm32_config_##index = { \ |
|
.i2s = (SPI_TypeDef *)DT_INST_REG_ADDR(index), \ |
|
.pclken = clk_##index, \ |
|
.pclk_len = DT_INST_NUM_CLOCKS(index), \ |
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ |
|
.irq_config = i2s_stm32_irq_config_func_##index, \ |
|
.master_clk_sel = DT_INST_PROP(index, mck_enabled) \ |
|
}; \ |
|
\ |
|
K_MSGQ_DEFINE(rx_##index##_queue, sizeof(struct queue_item), CONFIG_I2S_STM32_RX_BLOCK_COUNT, 4);\ |
|
K_MSGQ_DEFINE(tx_##index##_queue, sizeof(struct queue_item), CONFIG_I2S_STM32_TX_BLOCK_COUNT, 4);\ |
|
\ |
|
static struct i2s_stm32_data i2s_stm32_data_##index = { \ |
|
UTIL_AND(DT_INST_DMAS_HAS_NAME(index, rx), \ |
|
I2S_DMA_CHANNEL_INIT(index, rx, RX, PERIPHERAL, MEMORY)),\ |
|
UTIL_AND(DT_INST_DMAS_HAS_NAME(index, tx), \ |
|
I2S_DMA_CHANNEL_INIT(index, tx, TX, MEMORY, PERIPHERAL)),\ |
|
}; \ |
|
DEVICE_DT_INST_DEFINE(index, \ |
|
&i2s_stm32_initialize, NULL, \ |
|
&i2s_stm32_data_##index, \ |
|
&i2s_stm32_config_##index, POST_KERNEL, \ |
|
CONFIG_I2S_INIT_PRIORITY, &i2s_stm32_driver_api); \ |
|
\ |
|
static void i2s_stm32_irq_config_func_##index(const struct device *dev) \ |
|
{ \ |
|
IRQ_CONNECT(DT_INST_IRQN(index), \ |
|
DT_INST_IRQ(index, priority), \ |
|
i2s_stm32_isr, DEVICE_DT_INST_GET(index), 0); \ |
|
irq_enable(DT_INST_IRQN(index)); \ |
|
} |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2S_STM32_INIT)
|
|
|