drivers: spi: Enable dma transfer for SPI on stm32
Enable dma operations with or w/o a dmamux on STM32 for SPI periph/memory operations. Use the pi dma client with dma macros Signed-off-by: Francois Ramu <francois.ramu@st.com>
This commit is contained in:
parent
bea0a95578
commit
ce093dc35e
|
@ -17,10 +17,18 @@ config SPI_STM32_INTERRUPT
|
|||
help
|
||||
Enable Interrupt support for the SPI Driver of STM32 family.
|
||||
|
||||
config SPI_STM32_DMA
|
||||
bool "STM32 MCU SPI DMA Support"
|
||||
select DMA
|
||||
help
|
||||
Enable the SPI DMA mode for SPI instances
|
||||
that enable dma channels in their device tree node.
|
||||
|
||||
config SPI_STM32_USE_HW_SS
|
||||
bool "STM32 Hardware Slave Select support"
|
||||
default y
|
||||
help
|
||||
Use Slave Select pin instead of software Slave Select.
|
||||
|
||||
|
||||
endif # SPI_STM32
|
||||
|
|
|
@ -16,7 +16,10 @@ LOG_MODULE_REGISTER(spi_ll_stm32);
|
|||
#include <errno.h>
|
||||
#include <drivers/spi.h>
|
||||
#include <toolchain.h>
|
||||
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
#include <dt-bindings/dma/stm32_dma.h>
|
||||
#include <drivers/dma.h>
|
||||
#endif
|
||||
#include <drivers/clock_control/stm32_clock_control.h>
|
||||
#include <drivers/clock_control.h>
|
||||
|
||||
|
@ -48,6 +51,166 @@ LOG_MODULE_REGISTER(spi_ll_stm32);
|
|||
#endif
|
||||
#endif /* CONFIG_SOC_SERIES_STM32MP1X */
|
||||
|
||||
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
/* dummy value used for transferring NOP when tx buf is null */
|
||||
u32_t nop_tx;
|
||||
|
||||
/* This function is executed in the interrupt context */
|
||||
static void dma_callback(void *arg, u32_t channel, int status)
|
||||
{
|
||||
/* TODO: callback function where arg directly holds the client data */
|
||||
}
|
||||
|
||||
static int spi_stm32_dma_tx_load(struct device *dev, const u8_t *buf,
|
||||
size_t len)
|
||||
{
|
||||
const struct spi_stm32_config *cfg = DEV_CFG(dev);
|
||||
struct spi_stm32_data *data = DEV_DATA(dev);
|
||||
struct dma_block_config blk_cfg;
|
||||
int ret;
|
||||
|
||||
/* remember active TX DMA channel (used in callback) */
|
||||
struct stream *stream = &data->dma_tx;
|
||||
|
||||
/* prepare the block for this TX DMA channel */
|
||||
memset(&blk_cfg, 0, sizeof(blk_cfg));
|
||||
blk_cfg.block_size = len;
|
||||
|
||||
/* tx direction has memory as source and periph as dest. */
|
||||
if (buf == NULL) {
|
||||
nop_tx = 0;
|
||||
/* if tx buff is null, then sends NOP on the line. */
|
||||
blk_cfg.source_address = (u32_t)&nop_tx;
|
||||
blk_cfg.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
||||
} else {
|
||||
blk_cfg.source_address = (u32_t)buf;
|
||||
if (data->dma_tx.src_addr_increment) {
|
||||
blk_cfg.source_addr_adj = DMA_ADDR_ADJ_INCREMENT;
|
||||
} else {
|
||||
blk_cfg.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
||||
}
|
||||
}
|
||||
|
||||
blk_cfg.dest_address = (u32_t)LL_SPI_DMA_GetRegAddr(cfg->spi);
|
||||
/* fifo mode NOT USED there */
|
||||
if (data->dma_tx.dst_addr_increment) {
|
||||
blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_INCREMENT;
|
||||
} else {
|
||||
blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
||||
}
|
||||
|
||||
/* give the fifo mode from the DT */
|
||||
blk_cfg.fifo_mode_control = data->dma_tx.fifo_threshold;
|
||||
|
||||
/* direction is given by the DT */
|
||||
stream->dma_cfg.head_block = &blk_cfg;
|
||||
/* give the client data as arg, as the callback comes from the dma */
|
||||
stream->dma_cfg.callback_arg = data;
|
||||
/* pass our client origin to the dma: data->dma_tx.dma_channel */
|
||||
ret = dma_config(data->dev_dma_tx, data->dma_tx.channel,
|
||||
&stream->dma_cfg);
|
||||
/* the channel is the actual stream from 0 */
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* starting this dma transfer */
|
||||
data->dma_tx.transfer_complete = false;
|
||||
|
||||
/* gives the request ID to the dma mux */
|
||||
return dma_start(data->dev_dma_tx, data->dma_tx.channel);
|
||||
}
|
||||
|
||||
static int spi_stm32_dma_rx_load(struct device *dev, u8_t *buf, size_t len)
|
||||
{
|
||||
const struct spi_stm32_config *cfg = DEV_CFG(dev);
|
||||
struct spi_stm32_data *data = DEV_DATA(dev);
|
||||
struct dma_block_config blk_cfg;
|
||||
int ret;
|
||||
|
||||
/* retrieve active RX DMA channel (used in callback) */
|
||||
struct stream *stream = &data->dma_rx;
|
||||
|
||||
/* prepare the block for this RX DMA channel */
|
||||
memset(&blk_cfg, 0, sizeof(blk_cfg));
|
||||
blk_cfg.block_size = len;
|
||||
|
||||
/* rx direction has periph as source and mem as dest. */
|
||||
blk_cfg.dest_address = (buf != NULL) ? (u32_t)buf : (u32_t)NULL;
|
||||
blk_cfg.source_address = (u32_t)LL_SPI_DMA_GetRegAddr(cfg->spi);
|
||||
if (data->dma_rx.src_addr_increment) {
|
||||
blk_cfg.source_addr_adj = DMA_ADDR_ADJ_INCREMENT;
|
||||
} else {
|
||||
blk_cfg.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
||||
}
|
||||
if (data->dma_rx.dst_addr_increment) {
|
||||
blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_INCREMENT;
|
||||
} else {
|
||||
blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
||||
}
|
||||
|
||||
/* give the fifo mode from the DT */
|
||||
blk_cfg.fifo_mode_control = data->dma_rx.fifo_threshold;
|
||||
|
||||
/* direction is given by the DT */
|
||||
stream->dma_cfg.head_block = &blk_cfg;
|
||||
stream->dma_cfg.callback_arg = data;
|
||||
|
||||
|
||||
/* pass our client origin to the dma: data->dma_rx.channel */
|
||||
ret = dma_config(data->dev_dma_rx, data->dma_rx.channel,
|
||||
&stream->dma_cfg);
|
||||
/* the channel is the actual stream from 0 */
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* starting this dma transfer */
|
||||
data->dma_rx.transfer_complete = false;
|
||||
|
||||
/* gives the request ID to the dma mux */
|
||||
return dma_start(data->dev_dma_rx, data->dma_rx.channel);
|
||||
}
|
||||
|
||||
static int spi_dma_move_buffers(struct device *dev)
|
||||
{
|
||||
struct spi_stm32_data *data = DEV_DATA(dev);
|
||||
int ret;
|
||||
|
||||
/* the length to transmit depends on the source data size (1,2 4) */
|
||||
data->dma_segment_len = data->ctx.tx_len
|
||||
/ data->dma_tx.dma_cfg.source_data_size;
|
||||
|
||||
/* Load receive first, so it can accept transmit data */
|
||||
if (data->ctx.rx_len) {
|
||||
ret = spi_stm32_dma_rx_load(dev, data->ctx.rx_buf,
|
||||
data->dma_segment_len);
|
||||
} else {
|
||||
ret = spi_stm32_dma_rx_load(dev, NULL, data->dma_segment_len);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (data->ctx.tx_len) {
|
||||
ret = spi_stm32_dma_tx_load(dev, data->ctx.tx_buf,
|
||||
data->dma_segment_len);
|
||||
} else {
|
||||
ret = spi_stm32_dma_tx_load(dev, NULL, data->dma_segment_len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool spi_stm32_dma_transfer_ongoing(struct spi_stm32_data *data)
|
||||
{
|
||||
return ((data->dma_tx.transfer_complete != true)
|
||||
&& (data->dma_rx.transfer_complete != true));
|
||||
}
|
||||
#endif /* CONFIG_SPI_STM32_DMA */
|
||||
|
||||
/* Value to shift out when no application data needs transmitting. */
|
||||
#define SPI_STM32_TX_NOP 0x00
|
||||
|
||||
|
@ -348,6 +511,16 @@ static int spi_stm32_configure(struct device *dev,
|
|||
ll_func_set_fifo_threshold_8bit(spi);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
/* with LL_SPI_FULL_DUPLEX mode, both tx and Rx DMA are on */
|
||||
if (data->dev_dma_tx) {
|
||||
LL_SPI_EnableDMAReq_TX(spi);
|
||||
}
|
||||
if (data->dev_dma_rx) {
|
||||
LL_SPI_EnableDMAReq_RX(spi);
|
||||
}
|
||||
#endif /* CONFIG_SPI_STM32_DMA */
|
||||
|
||||
#ifndef CONFIG_SOC_SERIES_STM32F1X
|
||||
LL_SPI_SetStandard(spi, LL_SPI_PROTOCOL_MOTOROLA);
|
||||
#endif
|
||||
|
@ -451,11 +624,72 @@ static int transceive(struct device *dev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
static int transceive_dma(struct device *dev,
|
||||
const struct spi_config *config,
|
||||
const struct spi_buf_set *tx_bufs,
|
||||
const struct spi_buf_set *rx_bufs,
|
||||
bool asynchronous, struct k_poll_signal *signal)
|
||||
{
|
||||
const struct spi_stm32_config *cfg = DEV_CFG(dev);
|
||||
struct spi_stm32_data *data = DEV_DATA(dev);
|
||||
SPI_TypeDef *spi = cfg->spi;
|
||||
int ret;
|
||||
|
||||
if (!tx_bufs && !rx_bufs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (asynchronous) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
spi_context_lock(&data->ctx, asynchronous, signal);
|
||||
|
||||
data->dma_tx.transfer_complete = false;
|
||||
data->dma_rx.transfer_complete = false;
|
||||
|
||||
ret = spi_stm32_configure(dev, config);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set buffers info */
|
||||
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
|
||||
|
||||
ret = spi_dma_move_buffers(dev);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
LL_SPI_Enable(spi);
|
||||
|
||||
do {
|
||||
} while (spi_stm32_dma_transfer_ongoing(data));
|
||||
|
||||
/* This is turned off in spi_stm32_complete(). */
|
||||
spi_context_cs_control(&data->ctx, true);
|
||||
|
||||
spi_context_release(&data->ctx, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_SPI_STM32_DMA */
|
||||
|
||||
static int spi_stm32_transceive(struct device *dev,
|
||||
const struct spi_config *config,
|
||||
const struct spi_buf_set *tx_bufs,
|
||||
const struct spi_buf_set *rx_bufs)
|
||||
{
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
struct spi_stm32_data *data = DEV_DATA(dev);
|
||||
|
||||
if ((data->dma_tx.dma_name != NULL)
|
||||
&& (data->dma_rx.dma_name != NULL)) {
|
||||
return transceive_dma(dev, config, tx_bufs, rx_bufs,
|
||||
false, NULL);
|
||||
}
|
||||
#endif /* CONFIG_SPI_STM32_DMA */
|
||||
return transceive(dev, config, tx_bufs, rx_bufs, false, NULL);
|
||||
}
|
||||
|
||||
|
@ -495,6 +729,24 @@ static int spi_stm32_init(struct device *dev)
|
|||
cfg->irq_config(dev);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
if (data->dma_tx.dma_name != NULL) {
|
||||
/* Get the binding to the DMA device */
|
||||
data->dev_dma_tx = device_get_binding(data->dma_tx.dma_name);
|
||||
if (!data->dev_dma_tx) {
|
||||
LOG_ERR("%s device not found", data->dma_tx.dma_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->dma_rx.dma_name != NULL) {
|
||||
data->dev_dma_rx = device_get_binding(data->dma_rx.dma_name);
|
||||
if (!data->dev_dma_rx) {
|
||||
LOG_ERR("%s device not found", data->dma_rx.dma_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_SPI_STM32_DMA */
|
||||
spi_context_unlock_unconditionally(&data->ctx);
|
||||
|
||||
return 0;
|
||||
|
@ -519,14 +771,49 @@ static void spi_stm32_irq_config_func_##id(struct device *dev) \
|
|||
#define STM32_SPI_IRQ_HANDLER(id)
|
||||
#endif
|
||||
|
||||
#define DMA_CHANNEL_CONFIG(id, dir) \
|
||||
DT_INST_DMAS_CELL_BY_NAME(id, dir, channel_config)
|
||||
#define DMA_FEATURES(id, dir) \
|
||||
DT_INST_DMAS_CELL_BY_NAME(id, dir, features)
|
||||
|
||||
#define SPI_DMA_CHANNEL_INIT(index, dir, dir_cap, src_dev, dest_dev) \
|
||||
.dma_##dir = { \
|
||||
.dma_name = DT_INST_DMAS_LABEL_BY_NAME(index, dir), \
|
||||
.channel = \
|
||||
DT_INST_DMAS_CELL_BY_NAME(index, dir, channel), \
|
||||
.dma_cfg = { \
|
||||
.dma_slot = \
|
||||
DT_INST_DMAS_CELL_BY_NAME(index, dir, slot), \
|
||||
.channel_direction = STM32_DMA_CONFIG_DIRECTION( \
|
||||
DMA_CHANNEL_CONFIG(index, dir)),\
|
||||
.source_data_size = STM32_DMA_CONFIG_##src_dev##_DATA_SIZE(\
|
||||
DMA_CHANNEL_CONFIG(index, dir)),\
|
||||
.dest_data_size = STM32_DMA_CONFIG_##dest_dev##_DATA_SIZE(\
|
||||
DMA_CHANNEL_CONFIG(index, dir)),\
|
||||
.source_burst_length = 1, /* SINGLE transfer */ \
|
||||
.dest_burst_length = 1, /* SINGLE transfer */ \
|
||||
.channel_priority = STM32_DMA_CONFIG_PRIORITY( \
|
||||
DMA_CHANNEL_CONFIG(index, dir)),\
|
||||
.dma_callback = dma_callback, \
|
||||
.block_count = 2, \
|
||||
}, \
|
||||
.src_addr_increment = STM32_DMA_CONFIG_##src_dev##_ADDR_INC( \
|
||||
DMA_CHANNEL_CONFIG(index, dir)),\
|
||||
.dst_addr_increment = STM32_DMA_CONFIG_##dest_dev##_ADDR_INC( \
|
||||
DMA_CHANNEL_CONFIG(index, dir)),\
|
||||
.transfer_complete = false, \
|
||||
.fifo_threshold = STM32_DMA_FEATURES_FIFO_THRESHOLD( \
|
||||
DMA_FEATURES(index, dir)) \
|
||||
}
|
||||
|
||||
#define STM32_SPI_INIT(id) \
|
||||
STM32_SPI_IRQ_HANDLER_DECL(id); \
|
||||
\
|
||||
static const struct spi_stm32_config spi_stm32_cfg_##id = { \
|
||||
.spi = (SPI_TypeDef *) DT_INST_REG_ADDR(id),\
|
||||
.pclken = { \
|
||||
.enr = DT_INST_CLOCKS_CELL(id, bits), \
|
||||
.bus = DT_INST_CLOCKS_CELL(id, bus) \
|
||||
.enr = DT_INST_CLOCKS_CELL(id, bits), \
|
||||
.bus = DT_INST_CLOCKS_CELL(id, bus) \
|
||||
}, \
|
||||
STM32_SPI_IRQ_HANDLER_FUNC(id) \
|
||||
}; \
|
||||
|
@ -534,6 +821,10 @@ static const struct spi_stm32_config spi_stm32_cfg_##id = { \
|
|||
static struct spi_stm32_data spi_stm32_dev_data_##id = { \
|
||||
SPI_CONTEXT_INIT_LOCK(spi_stm32_dev_data_##id, ctx), \
|
||||
SPI_CONTEXT_INIT_SYNC(spi_stm32_dev_data_##id, ctx), \
|
||||
UTIL_AND(DT_INST_DMAS_HAS_NAME(id, rx), \
|
||||
SPI_DMA_CHANNEL_INIT(id, rx, RX, PERIPHERAL, MEMORY)), \
|
||||
UTIL_AND(DT_INST_DMAS_HAS_NAME(id, tx), \
|
||||
SPI_DMA_CHANNEL_INIT(id, tx, TX, MEMORY, PERIPHERAL)), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_AND_API_INIT(spi_stm32_##id, DT_INST_LABEL(id), \
|
||||
|
|
|
@ -19,8 +19,28 @@ struct spi_stm32_config {
|
|||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
struct stream {
|
||||
const char *dma_name;
|
||||
u32_t channel; /* stores the channel for dma or mux */
|
||||
struct dma_config dma_cfg;
|
||||
u8_t priority;
|
||||
bool src_addr_increment;
|
||||
bool dst_addr_increment;
|
||||
bool transfer_complete;
|
||||
int fifo_threshold;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct spi_stm32_data {
|
||||
struct spi_context ctx;
|
||||
#ifdef CONFIG_SPI_STM32_DMA
|
||||
struct device *dev_dma_tx;
|
||||
struct device *dev_dma_rx;
|
||||
struct stream dma_rx;
|
||||
struct stream dma_tx;
|
||||
size_t dma_segment_len;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline u32_t ll_func_tx_is_empty(SPI_TypeDef *spi)
|
||||
|
|
Loading…
Reference in a new issue