From ecb131ab4f28388924934c312ea263ec94bd2cb5 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 15:06:32 +0200 Subject: [PATCH 01/11] bindings/dma/st,stm32-dma-v3.yaml: add compatible Add compatible yaml file for stm32 dma v3 controllers. This is compatible with the HPDMA and LPDMA controllers in the STM32MP2 series Socs. Signed-off-by: Youssef Zini --- dts/bindings/dma/st,stm32-dma-v3.yaml | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 dts/bindings/dma/st,stm32-dma-v3.yaml diff --git a/dts/bindings/dma/st,stm32-dma-v3.yaml b/dts/bindings/dma/st,stm32-dma-v3.yaml new file mode 100644 index 0000000000000..cc151725835b6 --- /dev/null +++ b/dts/bindings/dma/st,stm32-dma-v3.yaml @@ -0,0 +1,76 @@ +# Copyright (C) 2025 Savoir-faire Linux, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: | + STM32 DMA controller V3. + + This DMA controller is what is used in the STM32MP2 series SoCs in the form + of HPDMA, high-performance DMA. It supports 16 independent DMA channels + and 256 DMA requests from peripherals. + DMA clients connected to the STM32 HPDMA controller must use a three-cell + specifier for each channel. + + It is a phandle to the STM32 DMA3 controller plus the following three integer + cells: + 1. channel: The DMA channel number (0 to 15) + 2. request: The DMA request line number (0 to 255) + 3. channel-config: A 32-bit mask specifying the DMA channel configuration + which is device dependent (see 'dma_stm32.h'): + - bit 5: Cyclic Mode + 0x0: STM32_DMA_MODE_NORMAL + 0x1: STM32_DMA_MODE_CYCLIC + - bits 6-7: Direction + 0x0: STM32_DMA_MEMORY_TO_MEMORY: MEM to MEM + 0x1: STM32_DMA_MEMORY_TO_PERIPH: MEM to PERIPH + 0x2: STM32_DMA_PERIPH_TO_MEMORY: PERIPH to MEM + 0x3: reserved for PERIPH to PERIPH + - bit 9: Peripheral Address Increment + 0x0: STM32_DMA_PERIPH_NO_INC: no address increment between transfers + 0x1: STM32_DMA_PERIPH_INC: increment address between transfers + - bit 10: Memory Address Increment + 0x0: STM32_DMA_MEM_NO_INC: no address increment between transfers + 0x1: STM32_DMA_MEM_INC: increment address between transfers + - bits 11-12: Peripheral Data Size + 0x0: STM32_DMA_PERIPH_8BITS: Byte (8 bits) + 0x1: STM32_DMA_PERIPH_16BITS: Half-word (16 bits) + 0x2: STM32_DMA_PERIPH_32BITS: Word (32 bits) + 0x3: STM32_DMA_PERIPH_64BITS: Double-word (64 bits) + - bits 13-14: Memory Data Size + 0x0: STM32_DMA_MEM_8BITS: Byte (8 bits) + 0x1: STM32_DMA_MEM_16BITS: Half-word (16 bits) + 0x2: STM32_DMA_MEM_32BITS: Word (32 bits) + 0x3: STM32_DMA_MEM_64BITS: Double-word (64 bits) + - bit 15: Reserved + - bits 16-17: Priority Level + 0x0: STM32_DMA_PRIORITY_LOW: Low + 0x1: STM32_DMA_PRIORITY_MEDIUM: Medium + 0x2: STM32_DMA_PRIORITY_HIGH: High + 0x3: STM32_DMA_PRIORITY_VERY_HIGH: Very High + + For the client part, example for stm32mp257f-ev1 on the CM33 using the HPDMA3 + instance: + rx using channel 0 with request 17 for uart5_rx + tx using channel 1 with request 18 for uart5_tx + uart5 { + dmas = <&hpdma3 0 18 (STM32_DMA_PERIPH_TX | STM32_DMA_PRIORITY_HIGH)>, + <&hpdma3 1 17 (STM32_DMA_PERIPH_RX | STM32_DMA_PRIORITY_HIGH)>; + dma-names = "tx", "rx"; + }; + +compatible: "st,stm32-dma-v3" + +include: + - name: st,stm32-dma.yaml + +properties: + "#dma-cells": + const: 3 + +# Parameter syntax of stm32 follows the dma client dts syntax +# in the Linux kernel declared in +# https://git.kernel.org/pub/scm/linux/kernel/git/devicetree/devicetree-rebasing.git/plain/Bindings/dma/stm32/st,stm32-dma3.yaml + +dma-cells: + - channel + - slot + - channel-config From b36c582e33c63037d158c9744bf18e0c8dda7ad1 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 17:26:05 +0200 Subject: [PATCH 02/11] drivers/dma/dma_stm32_v3.c: add dma v3 driver Since the STM32MP2 series supports a new DMA peripheral (HPDMA and LPDMA), this commit introduces a new driver for the STM32 DMA v3. So far, it includes basic functionality for the HPDMA channels, enabling the use of async APIs (tested using uart on the STM32MP257F-EV1 board), as well as support circular dma and loop transfers (reload mode and suspend/resume). The driver is designed to be compatible with the existing Zephyr DMA API, while being inspired heavily by the linux stm32 dma3 driver[1]. The classes and functions used are thought to allow for easy linked list operations in the future, as well as support scatter gather, cyclic transfers and channel linking. [1]: https://github.com/STMicroelectronics/linux/blob/v6.6-stm32mp/drivers/dma/stm32/stm32-dma3.c Signed-off-by: Youssef Zini --- drivers/dma/dma_stm32_v3.c | 795 +++++++++++++++++++++++++++++++++++++ 1 file changed, 795 insertions(+) create mode 100644 drivers/dma/dma_stm32_v3.c diff --git a/drivers/dma/dma_stm32_v3.c b/drivers/dma/dma_stm32_v3.c new file mode 100644 index 0000000000000..d4dd03b6b31f0 --- /dev/null +++ b/drivers/dma/dma_stm32_v3.c @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2025 Savoir-faire Linux, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +LOG_MODULE_REGISTER(dma_stm32_v3, CONFIG_DMA_LOG_LEVEL); + +#define DT_DRV_COMPAT st_stm32_dma_v3 +#define DMA_STM32_MAX_DATA_ITEMS 0xffff + +/* Since at this point we only support cyclic mode , we only need 3 descriptors + * at most to update the source and destination addresses and the update + * registers. TODO: Raise this number for larger linked lists. + */ +#define DMA_STM32_NUM_DESCRIPTORS_PER_CHANNEL 24 +#define POLLING_TIMEOUT_US (10 * USEC_PER_MSEC) + +struct dma_stm32_descriptor { + uint32_t channel_tr1; + uint32_t channel_tr2; + uint32_t channel_br1; + uint32_t channel_sar; + uint32_t channel_dar; + uint32_t channel_llr; +}; + +struct dma_stm32_channel { + uint32_t direction; + bool hal_override; + bool busy; + uint32_t state; + uint32_t src_size; + uint32_t dst_size; + void *user_data; + dma_callback_t dma_callback; + bool cyclic; + int block_count; +}; + +struct dma_stm32_data { + struct dma_context dma_ctx; +}; + +struct dma_stm32_config { + struct stm32_pclken pclken; + void (*config_irq)(const struct device *dev); + DMA_TypeDef *base; + uint32_t max_channels; + struct dma_stm32_channel *channels; + uint32_t *linked_list_buffer; +}; + +static int dma_stm32_id_to_channel(uint32_t id, uint32_t *channel) +{ + static const uint32_t channel_nr[] = { + LL_DMA_CHANNEL_0, LL_DMA_CHANNEL_1, LL_DMA_CHANNEL_2, LL_DMA_CHANNEL_3, + LL_DMA_CHANNEL_4, LL_DMA_CHANNEL_5, LL_DMA_CHANNEL_6, LL_DMA_CHANNEL_7, + LL_DMA_CHANNEL_8, LL_DMA_CHANNEL_9, LL_DMA_CHANNEL_10, LL_DMA_CHANNEL_11, + LL_DMA_CHANNEL_12, LL_DMA_CHANNEL_13, LL_DMA_CHANNEL_14, LL_DMA_CHANNEL_15, + }; + + if (id >= ARRAY_SIZE(channel_nr)) { + LOG_ERR("Invalid channel ID %d", id); + return -EINVAL; + } + + *channel = channel_nr[id]; + return 0; +} + +static DMA_Channel_TypeDef *dma_stm32_get_channel_addr(DMA_TypeDef *dma, uint32_t channel) +{ + return (DMA_Channel_TypeDef *)((uintptr_t)dma + LL_DMA_CH_OFFSET_TAB[channel]); +} + +static int dma_stm32_get_src_inc_mode(enum dma_addr_adj increment, uint32_t *ll_increment) +{ + switch (increment) { + case DMA_ADDR_ADJ_INCREMENT: + *ll_increment = LL_DMA_SRC_INCREMENT; + break; + case DMA_ADDR_ADJ_NO_CHANGE: + *ll_increment = LL_DMA_SRC_FIXED; + break; + case DMA_ADDR_ADJ_DECREMENT: + LOG_ERR("Decrement mode not supported for source address adjustment"); + return -ENOTSUP; + default: + LOG_ERR("Invalid source increment mode: %d", increment); + return -EINVAL; + } + + return 0; +} + +static int dma_stm32_get_dest_inc_mode(enum dma_addr_adj increment, uint32_t *ll_increment) +{ + switch (increment) { + case DMA_ADDR_ADJ_INCREMENT: + *ll_increment = LL_DMA_DEST_INCREMENT; + break; + case DMA_ADDR_ADJ_NO_CHANGE: + *ll_increment = LL_DMA_DEST_FIXED; + break; + case DMA_ADDR_ADJ_DECREMENT: + LOG_ERR("Decrement mode not supported for destination address adjustment"); + return -ENOTSUP; + default: + LOG_ERR("Invalid destination increment mode: %d", increment); + return -EINVAL; + } + + return 0; +} + +static void dma_stm32_get_src_data_width(uint32_t size, uint32_t *ll_src_data_width) +{ + static const uint32_t table_src_size[] = { + LL_DMA_SRC_DATAWIDTH_BYTE, + LL_DMA_SRC_DATAWIDTH_HALFWORD, + LL_DMA_SRC_DATAWIDTH_WORD, + LL_DMA_SRC_DATAWIDTH_DOUBLEWORD, + }; + + *ll_src_data_width = table_src_size[LOG2(size)]; +} + +static void dma_stm32_get_dest_data_width(uint32_t size, uint32_t *ll_dest_data_width) +{ + static const uint32_t table_dst_size[] = { + LL_DMA_DEST_DATAWIDTH_BYTE, + LL_DMA_DEST_DATAWIDTH_HALFWORD, + LL_DMA_DEST_DATAWIDTH_WORD, + LL_DMA_DEST_DATAWIDTH_DOUBLEWORD, + }; + + *ll_dest_data_width = table_dst_size[LOG2(size)]; +} + +static int dma_stm32_get_ll_direction(enum dma_channel_direction direction, uint32_t *ll_direction) +{ + switch (direction) { + case MEMORY_TO_MEMORY: + *ll_direction = LL_DMA_DIRECTION_MEMORY_TO_MEMORY; + break; + case MEMORY_TO_PERIPHERAL: + *ll_direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + break; + case PERIPHERAL_TO_MEMORY: + *ll_direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + break; + default: + LOG_ERR("Direction error. %d", direction); + return -EINVAL; + } + + return 0; +} + +static int dma_stm32_get_priority(uint8_t priority, uint32_t *ll_priority) +{ + static const uint32_t table_priority[] = { + LL_DMA_LOW_PRIORITY_LOW_WEIGHT, + LL_DMA_LOW_PRIORITY_MID_WEIGHT, + LL_DMA_LOW_PRIORITY_HIGH_WEIGHT, + LL_DMA_HIGH_PRIORITY, + }; + + if (priority >= ARRAY_SIZE(table_priority)) { + LOG_ERR("Priority error."); + return -EINVAL; + } + + *ll_priority = table_priority[priority]; + return 0; +} + +static bool dma_stm32_is_tc_irq_active(DMA_TypeDef *dma, uint32_t channel) +{ + return LL_DMA_IsEnabledIT_TC(dma, channel) && LL_DMA_IsActiveFlag_TC(dma, channel); +} + +static bool dma_stm32_is_ht_irq_active(DMA_TypeDef *dma, uint32_t channel) +{ + return LL_DMA_IsEnabledIT_HT(dma, channel) && LL_DMA_IsActiveFlag_HT(dma, channel); +} + +static void dma_stm32_disable_it(DMA_TypeDef *dma, uint32_t channel) +{ + mem_addr_t dma_channel_ccr_reg = (mem_addr_t)&dma_stm32_get_channel_addr(dma, channel)->CCR; + + sys_clear_bits(dma_channel_ccr_reg, DMA_CCR_TCIE | DMA_CCR_HTIE | DMA_CCR_USEIE | + DMA_CCR_ULEIE | DMA_CCR_DTEIE | DMA_CCR_SUSPIE); +} + +static void dma_stm32_enable_it(DMA_TypeDef *dma, uint32_t channel) +{ + mem_addr_t dma_channel_ccr_reg = (mem_addr_t)&dma_stm32_get_channel_addr(dma, channel)->CCR; + + sys_set_bits(dma_channel_ccr_reg, + DMA_CCR_TCIE | DMA_CCR_USEIE | DMA_CCR_ULEIE | DMA_CCR_DTEIE); +} + +static void dma_stm32_dump_channel_irq(DMA_TypeDef *dma, uint32_t channel) +{ + LOG_INF("tc: %d, ht: %d, dte: %d, ule: %d, use: %d", LL_DMA_IsActiveFlag_TC(dma, channel), + LL_DMA_IsActiveFlag_HT(dma, channel), LL_DMA_IsActiveFlag_DTE(dma, channel), + LL_DMA_IsActiveFlag_ULE(dma, channel), LL_DMA_IsActiveFlag_USE(dma, channel)); +} + +static void dma_stm32_clear_channel_irq(DMA_TypeDef *dma, uint32_t channel) +{ + mem_addr_t dma_channel_cfcr_reg = + (mem_addr_t)&dma_stm32_get_channel_addr(dma, channel)->CFCR; + + sys_set_bits(dma_channel_cfcr_reg, DMA_CFCR_TCF | DMA_CFCR_HTF | DMA_CFCR_DTEF | + DMA_CFCR_ULEF | DMA_CFCR_USEF | DMA_CFCR_TOF | + DMA_CFCR_SUSPF); +} + +static int dma_stm32_disable_channel(DMA_TypeDef *dma, uint32_t channel) +{ + LL_DMA_DisableChannel(dma, channel); + if (WAIT_FOR(!LL_DMA_IsEnabledChannel(dma, channel), POLLING_TIMEOUT_US, k_msleep(1))) { + return 0; + } + + LOG_ERR("Timeout waiting for channel %d to disable", channel); + return -ETIMEDOUT; +} + +static int dma_stm32_validate_transfer_sizes(struct dma_config *config) +{ + if (config->head_block->block_size > DMA_STM32_MAX_DATA_ITEMS) { + LOG_ERR("Data size exceeds the maximum limit: %d>%d", config->head_block->block_size, + DMA_STM32_MAX_DATA_ITEMS); + return -EINVAL; + } + + if (config->source_data_size != 8U && config->source_data_size != 4U && + config->source_data_size != 2U && config->source_data_size != 1U) { + LOG_ERR("Invalid source data size: %u, only 1, 2, 4, 8 bytes supported", + config->source_data_size); + return -EINVAL; + } + LOG_DBG("Source data size: %u", config->source_data_size); + + if (config->dest_data_size != 8U && config->dest_data_size != 4U && + config->dest_data_size != 2U && config->dest_data_size != 1U) { + LOG_ERR("Invalid destination data size: %u, only 1, 2, 4, 8 bytes supported", + config->dest_data_size); + return -EINVAL; + } + LOG_DBG("Destination data size: %u", config->dest_data_size); + + /* TODO: support different data sizes */ + if (config->source_data_size != config->dest_data_size) { + LOG_ERR("Source and destination data sizes do not match: (%u != %u) - not " + "supported yet", + config->source_data_size, config->dest_data_size); + return -ENOTSUP; + } + + return 0; +} + +int dma_stm32_configure(const struct device *dev, uint32_t id, struct dma_config *config) +{ + const struct dma_stm32_config *dev_config; + struct dma_stm32_channel *channel_config; + struct dma_block_config *block_config; + struct dma_stm32_descriptor hwdesc; + uint32_t channel; + DMA_TypeDef *dma; + uint32_t src_inc_mode; + uint32_t dest_inc_mode; + uint32_t src_data_width_size; + uint32_t dest_data_width_size; + uint32_t ll_priority; + uint32_t ll_direction; + int ret = 0; + uint32_t ccr = 0; + + if (dev == NULL || config == NULL) { + LOG_ERR("Invalid arguments: dev=%p, config=%p", dev, config); + return -EINVAL; + } + + dev_config = dev->config; + if (id >= dev_config->max_channels) { + LOG_ERR("Invalid channel ID %d, max channels %d", id, dev_config->max_channels); + return -EINVAL; + } + + channel_config = &dev_config->channels[id]; + if (channel_config->busy) { + LOG_ERR("Channel %d is busy", id); + return -EBUSY; + } + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return ret; + } + + dma = dev_config->base; + + ret = dma_stm32_disable_channel(dma, channel); + if (ret < 0) { + LOG_ERR("Failed to disable DMA channel %d", id); + return ret; + } + + dma_stm32_clear_channel_irq(dma, channel); + + ret = dma_stm32_validate_transfer_sizes(config); + if (ret < 0) { + return ret; + } + + block_config = config->head_block; + + ret = dma_stm32_get_src_inc_mode(block_config->source_addr_adj, &src_inc_mode); + if (ret < 0) { + return ret; + } + LOG_DBG("Source address increment: %d", src_inc_mode); + + ret = dma_stm32_get_dest_inc_mode(block_config->dest_addr_adj, &dest_inc_mode); + if (ret < 0) { + return ret; + } + LOG_DBG("Destination address increment: %d", dest_inc_mode); + + dma_stm32_get_src_data_width(config->source_data_size, &src_data_width_size); + dma_stm32_get_dest_data_width(config->dest_data_size, &dest_data_width_size); + + ret = dma_stm32_get_priority(config->channel_priority, &ll_priority); + if (ret < 0) { + return ret; + } + + ret = dma_stm32_get_ll_direction(config->channel_direction, &ll_direction); + if (ret < 0) { + return ret; + } + + channel_config->busy = true; + channel_config->dma_callback = config->dma_callback; + channel_config->direction = config->channel_direction; + channel_config->user_data = config->user_data; + channel_config->src_size = config->source_data_size; + channel_config->dst_size = config->dest_data_size; + channel_config->cyclic = config->cyclic; + + dma_stm32_disable_it(dma, channel); + + /* Reset any previous linked list configuration */ + LL_DMA_SetLinkedListBaseAddr(dma, channel, 0); + + int linked_list_needed = (config->block_count > 1) || config->cyclic; + + if (!linked_list_needed) { + ccr |= LL_DMA_LSM_1LINK_EXECUTION; + } else { + LOG_ERR("Only single block transfers are supported for now"); + return -ENOTSUP; + } + + /* TODO: support port specifier from configuration */ + ccr |= ll_priority; + ccr |= LL_DMA_LINK_ALLOCATED_PORT0; + + LL_DMA_ConfigControl(dma, channel, ccr); + + hwdesc.channel_tr1 = + dest_inc_mode | dest_data_width_size | src_inc_mode | src_data_width_size; + + LL_DMA_ConfigTransfer(dma, channel, hwdesc.channel_tr1); + + LL_DMA_ConfigBurstLength(dma, channel, config->source_burst_length, + config->dest_burst_length); + + hwdesc.channel_tr2 = ll_direction; + hwdesc.channel_tr2 |= LL_DMA_TCEM_BLK_TRANSFER; + + LL_DMA_ConfigChannelTransfer(dma, channel, hwdesc.channel_tr2); + + if (ll_direction != LL_DMA_DIRECTION_MEMORY_TO_MEMORY) { + LL_DMA_SetPeriphRequest(dma, channel, config->dma_slot); + } + + hwdesc.channel_br1 = config->head_block->block_size; + + LL_DMA_SetBlkDataLength(dma, channel, hwdesc.channel_br1); + + hwdesc.channel_sar = config->head_block->source_address; + hwdesc.channel_dar = config->head_block->dest_address; + + LL_DMA_ConfigAddresses(dma, channel, hwdesc.channel_sar, hwdesc.channel_dar); + + dma_stm32_enable_it(dma, channel); + + return 0; +} + +static int dma_stm32_reload(const struct device *dev, uint32_t id, uint32_t src, uint32_t dst, + size_t size) +{ + const struct dma_stm32_config *dev_config; + struct dma_stm32_channel *channel_config; + uint32_t channel; + DMA_TypeDef *dma; + int ret = 0; + + if (dev == NULL) { + LOG_ERR("Invalid device pointer"); + return -EINVAL; + } + + dev_config = dev->config; + + if (id >= dev_config->max_channels) { + LOG_ERR("Invalid channel ID %d, max channels %d", id, dev_config->max_channels); + return -EINVAL; + } + + channel_config = &dev_config->channels[id]; + dma = dev_config->base; + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return ret; + } + + ret = dma_stm32_disable_channel(dma, channel); + if (ret < 0) { + return ret; + } + + LL_DMA_ConfigAddresses(dma, channel, src, dst); + + LL_DMA_SetBlkDataLength(dma, channel, size); + + channel_config->busy = true; + + LL_DMA_EnableChannel(dma, channel); + + return 0; +} + +static int dma_stm32_start(const struct device *dev, uint32_t id) +{ + const struct dma_stm32_config *dev_config; + struct dma_stm32_channel *channel_config; + uint32_t channel; + DMA_TypeDef *dma; + int ret = 0; + + if (dev == NULL) { + LOG_ERR("Invalid device pointer"); + return -EINVAL; + } + + dev_config = dev->config; + + if (id >= dev_config->max_channels) { + LOG_ERR("Invalid channel ID %d, max channels %d", id, dev_config->max_channels); + return -EINVAL; + } + + channel_config = &dev_config->channels[id]; + dma = dev_config->base; + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return ret; + } + + if (LL_DMA_IsEnabledChannel(dma, channel)) { + LOG_INF("Channel %d is already enabled", id); + return 0; + } + + /* When starting the dma, the stream is busy before enabling */ + channel_config->busy = true; + + dma_stm32_clear_channel_irq(dma, channel); + + LL_DMA_EnableChannel(dma, channel); + + return 0; +} + +static int dma_stm32_stop(const struct device *dev, uint32_t id) +{ + const struct dma_stm32_config *dev_config; + struct dma_stm32_channel *channel_config; + uint32_t channel; + DMA_TypeDef *dma; + int ret = 0; + + if (dev == NULL) { + LOG_ERR("Invalid device pointer"); + return -EINVAL; + } + + dev_config = dev->config; + + if (id >= dev_config->max_channels) { + LOG_ERR("Invalid channel ID %d, max channels %d", id, dev_config->max_channels); + return -EINVAL; + } + + channel_config = &dev_config->channels[id]; + dma = dev_config->base; + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return ret; + } + + if (channel_config->hal_override) { + channel_config->busy = false; + return 0; + } + + if (!LL_DMA_IsEnabledChannel(dma, channel)) { + return 0; + } + + dma_stm32_clear_channel_irq(dma, channel); + + dma_stm32_disable_it(dma, channel); + + if (dma_stm32_disable_channel(dma, channel)) { + LOG_ERR("Failed to disable DMA channel %d", id); + return -EBUSY; + } + + channel_config->busy = false; + + return 0; +} + +static int dma_stm32_get_status(const struct device *dev, uint32_t id, struct dma_status *status) +{ + const struct dma_stm32_config *dev_config; + struct dma_stm32_channel *channel_config; + uint32_t channel; + DMA_TypeDef *dma; + int ret = 0; + + if (dev == NULL) { + LOG_ERR("Invalid device pointer"); + return -EINVAL; + } + + dev_config = dev->config; + + if (id >= dev_config->max_channels) { + LOG_ERR("Invalid channel ID %d, max channels %d", id, dev_config->max_channels); + return -EINVAL; + } + + channel_config = &dev_config->channels[id]; + dma = dev_config->base; + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return ret; + } + + status->pending_length = LL_DMA_GetBlkDataLength(dma, channel); + status->dir = channel_config->direction; + status->busy = channel_config->busy; + + return 0; +} + +static int dma_stm32_suspend(const struct device *dev, uint32_t id) +{ + const struct dma_stm32_config *dev_config; + uint32_t channel; + DMA_TypeDef *dma; + int ret = 0; + + if (dev == NULL) { + LOG_ERR("Invalid device pointer"); + return -EINVAL; + } + + dev_config = dev->config; + + if (id >= dev_config->max_channels) { + LOG_ERR("Invalid channel ID %d, max channels %d", id, dev_config->max_channels); + return -EINVAL; + } + + dma = dev_config->base; + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return ret; + } + + LL_DMA_SuspendChannel(dma, channel); + if (WAIT_FOR(LL_DMA_IsActiveFlag_SUSP(dma, channel), POLLING_TIMEOUT_US, k_msleep(1))) { + return 0; + } + + LOG_ERR("Timeout waiting for channel %d to suspend", channel); + return -ETIMEDOUT; +} + +static int dma_stm32_resume(const struct device *dev, uint32_t id) +{ + const struct dma_stm32_config *dev_config; + uint32_t channel; + DMA_TypeDef *dma; + int ret = 0; + + if (dev == NULL) { + LOG_ERR("Invalid device pointer"); + return -EINVAL; + } + + dev_config = dev->config; + + if (id >= dev_config->max_channels) { + LOG_ERR("Invalid channel ID %d, max channels %d", id, dev_config->max_channels); + return -EINVAL; + } + + dma = dev_config->base; + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return ret; + } + + dma = dev_config->base; + + LL_DMA_ResumeChannel(dma, channel); + + return 0; +} + +static void dma_stm32_irq_handler(const struct device *dev, uint32_t id) +{ + const struct dma_stm32_config *dev_config = dev->config; + struct dma_stm32_channel *channel_config; + uint32_t channel; + DMA_TypeDef *dma; + uint32_t callback_arg; + int ret; + + channel_config = &dev_config->channels[id]; + + ret = dma_stm32_id_to_channel(id, &channel); + if (ret < 0) { + return; + } + + dma = dev_config->base; + + /* The busy channel is pertinent if not overridden by the HAL */ + if (!channel_config->hal_override && !channel_config->busy) { + /* + * When DMA channel is not overridden by HAL, + * ignore irq if the channel is not busy anymore + */ + dma_stm32_clear_channel_irq(dma, channel); + return; + } + callback_arg = id; + + if (dma_stm32_is_ht_irq_active(dma, channel)) { + if (!channel_config->hal_override) { + LL_DMA_ClearFlag_HT(dma, channel); + } + + channel_config->dma_callback(dev, channel_config->user_data, callback_arg, + DMA_STATUS_BLOCK); + } else if (dma_stm32_is_tc_irq_active(dma, channel)) { + if (!channel_config->cyclic) { + channel_config->busy = false; + } + + if (!channel_config->hal_override) { + LL_DMA_ClearFlag_TC(dma, channel); + } + + channel_config->dma_callback(dev, channel_config->user_data, callback_arg, + DMA_STATUS_COMPLETE); + } else { + LOG_ERR("Transfer Error."); + channel_config->busy = false; + dma_stm32_dump_channel_irq(dma, channel); + dma_stm32_clear_channel_irq(dma, channel); + channel_config->dma_callback(dev, channel_config->user_data, callback_arg, -EIO); + } +} + +static int dma_stm32_init(const struct device *dev) +{ + const struct dma_stm32_config *dev_config = dev->config; + struct dma_stm32_data *dev_data = dev->data; + + for (uint32_t i = 0; i < dev_config->max_channels; i++) { + dev_config->channels[i].busy = false; + } + + memset(dev_data, 0, sizeof(*dev_data)); + dev_config->config_irq(dev); + + return 0; +} + +static DEVICE_API(dma, dma_funcs) = { + .config = dma_stm32_configure, + .reload = dma_stm32_reload, + .start = dma_stm32_start, + .stop = dma_stm32_stop, + .get_status = dma_stm32_get_status, + .suspend = dma_stm32_suspend, + .resume = dma_stm32_resume, +}; + +#define DMA_STM32_IRQ_CONNECT_CHANNEL(chan, dma) \ + do { \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(dma, chan, irq), \ + DT_INST_IRQ_BY_IDX(dma, chan, priority), dma_stm32_irq_##dma##_##chan, \ + DEVICE_DT_INST_GET(dma), 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(dma, chan, irq)); \ + } while (0) + +#define DMA_STM32_IRQ_CONNECT(index) \ + static void dma_stm32_config_irq_##index(const struct device *dev) \ + { \ + ARG_UNUSED(dev); \ + \ + LISTIFY(DT_INST_PROP(index, dma_channels), \ + DMA_STM32_IRQ_CONNECT_CHANNEL, (;), index); \ + } + +#define DMA_STM32_DEFINE_IRQ_HANDLER(chan, dma) \ + static void dma_stm32_irq_##dma##_##chan(const struct device *dev) \ + { \ + dma_stm32_irq_handler(dev, chan); \ + } + +#define DMA_STM32_INIT_DEV(index) \ + BUILD_ASSERT(DT_INST_PROP(index, dma_channels) == DT_NUM_IRQS(DT_DRV_INST(index)), \ + "Nb of Channels and IRQ mismatch"); \ + \ + LISTIFY(DT_INST_PROP(index, dma_channels), \ + DMA_STM32_DEFINE_IRQ_HANDLER, (;), index); \ + \ + DMA_STM32_IRQ_CONNECT(index); \ + \ + static struct dma_stm32_channel dma_stm32_channels_##index[DT_INST_PROP_OR( \ + index, dma_channels, DT_NUM_IRQS(DT_DRV_INST(index)))]; \ + \ + static uint32_t dma_stm32_linked_list_buffer##index \ + [DMA_STM32_NUM_DESCRIPTORS_PER_CHANNEL * \ + DT_INST_PROP_OR(index, dma_channels, DT_NUM_IRQS(DT_DRV_INST(index)))] __nocache; \ + \ + const struct dma_stm32_config dma_stm32_config_##index = { \ + COND_CODE_1(DT_NODE_HAS_PROP(__node, clocks), \ + (.pclken = { .bus = __bus, .enr = __cenr },), \ + (/* Nothing if clocks not present */)) \ + .config_irq = dma_stm32_config_irq_##index, \ + .base = (DMA_TypeDef *)DT_INST_REG_ADDR(index), \ + .max_channels = \ + DT_INST_PROP_OR(index, dma_channels, DT_NUM_IRQS(DT_DRV_INST(index))), \ + .channels = dma_stm32_channels_##index, \ + .linked_list_buffer = dma_stm32_linked_list_buffer##index \ + }; \ + \ + static struct dma_stm32_data dma_stm32_data_##index = {}; \ + \ + DEVICE_DT_INST_DEFINE(index, dma_stm32_init, NULL, &dma_stm32_data_##index, \ + &dma_stm32_config_##index, PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, \ + &dma_funcs); + +DT_INST_FOREACH_STATUS_OKAY(DMA_STM32_INIT_DEV) From d213a0729b4166b3240fa3436f4be7a9e410b65f Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 15:37:59 +0200 Subject: [PATCH 03/11] drivers/serial/Kconfig.stm32: add dma v3 support Add support for the new DMA v3 driver in the STM32 series uart async API. Signed-off-by: Youssef Zini --- drivers/serial/Kconfig.stm32 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/serial/Kconfig.stm32 b/drivers/serial/Kconfig.stm32 index 83ca475c1180e..2ade613849a21 100644 --- a/drivers/serial/Kconfig.stm32 +++ b/drivers/serial/Kconfig.stm32 @@ -14,7 +14,8 @@ config UART_STM32 if DT_HAS_ST_STM32_DMA_V1_ENABLED || \ DT_HAS_ST_STM32_DMA_V2_ENABLED || \ DT_HAS_ST_STM32_DMA_V2BIS_ENABLED || \ - DT_HAS_ST_STM32U5_DMA_ENABLED + DT_HAS_ST_STM32U5_DMA_ENABLED || \ + DT_HAS_ST_STM32_DMA_V3_ENABLED select DMA if UART_ASYNC_API select RESET select PINCTRL From 814228dd77fb3a0484ebe57f9bb14aaf69d6cfb2 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 15:44:42 +0200 Subject: [PATCH 04/11] drivers/dma/Kconfig.stm32: add dma v3 support Add dma v3 symbols to Kconfig.stm32 to enable support when dma v3 is used in the device tree. Also add the new driver reference in the CMakeLists.txt file. Signed-off-by: Youssef Zini --- drivers/dma/CMakeLists.txt | 1 + drivers/dma/Kconfig.stm32 | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/drivers/dma/CMakeLists.txt b/drivers/dma/CMakeLists.txt index 65cba01a5f2ef..d8b8c3863a232 100644 --- a/drivers/dma/CMakeLists.txt +++ b/drivers/dma/CMakeLists.txt @@ -8,6 +8,7 @@ zephyr_library_sources_ifdef(CONFIG_DMA_SAM_XDMAC dma_sam_xdmac.c) zephyr_library_sources_ifdef(CONFIG_DMA_STM32U5 dma_stm32u5.c) zephyr_library_sources_ifdef(CONFIG_DMA_STM32_V1 dma_stm32.c dma_stm32_v1.c) zephyr_library_sources_ifdef(CONFIG_DMA_STM32_V2 dma_stm32.c dma_stm32_v2.c) +zephyr_library_sources_ifdef(CONFIG_DMA_STM32_V3 dma_stm32_v3.c) zephyr_library_sources_ifdef(CONFIG_DMA_STM32_BDMA dma_stm32_bdma.c) zephyr_library_sources_ifdef(CONFIG_DMAMUX_STM32 dmamux_stm32.c) zephyr_library_sources_ifdef(CONFIG_DMA_DW dma_dw.c dma_dw_common.c) diff --git a/drivers/dma/Kconfig.stm32 b/drivers/dma/Kconfig.stm32 index 944c068c42442..1d44e48d557d6 100644 --- a/drivers/dma/Kconfig.stm32 +++ b/drivers/dma/Kconfig.stm32 @@ -3,6 +3,7 @@ # Copyright (c) 2016 Intel Corporation # Copyright (c) 2019 Song Qiang # Copyright (c) 2023 Jeroen van Dooren, Nobleo Technology +# Copyright (C) 2025 Savoir-faire Linux, Inc. # SPDX-License-Identifier: Apache-2.0 config DMA_STM32 @@ -12,6 +13,7 @@ config DMA_STM32 depends on DT_HAS_ST_STM32_DMA_V1_ENABLED \ || DT_HAS_ST_STM32_DMA_V2_ENABLED \ || DT_HAS_ST_STM32_DMA_V2BIS_ENABLED \ + || DT_HAS_ST_STM32_DMA_V3_ENABLED \ || DT_HAS_ST_STM32_BDMA_ENABLED help Driver for STM32 DMA V1, V2, V2bis and BDMA types. @@ -44,6 +46,16 @@ config DMA_STM32_V2 With the versions V2 bis of DMA, the peripheral request (slot) is not a parameter of the dma-cell. +config DMA_STM32_V3 + bool "STM32 DMA V3 driver" + default y + depends on DT_HAS_ST_STM32_DMA_V3_ENABLED + help + Driver for STM32 HPDMA (High Performance DMA) type. + It is used in STM32MP2 series SoCs. + It differs from the DMA driver due to advanced features + it supports such as scatter-gather and burst transfers. + config DMAMUX_STM32 bool default y From 8c108e96d1fb24cf38fc6a3db85e39dde74abd61 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Wed, 3 Sep 2025 15:19:46 +0200 Subject: [PATCH 05/11] include/zephyr: dma stm32: add macro parentheses Add parentheses to macros in drivers and dt-bindings stm32 dma files to avoid potential issues with operator precedence when these macros are used in expressions. Signed-off-by: Youssef Zini --- include/zephyr/drivers/dma/dma_stm32.h | 18 +++++++++--------- include/zephyr/dt-bindings/dma/stm32_dma.h | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/zephyr/drivers/dma/dma_stm32.h b/include/zephyr/drivers/dma/dma_stm32.h index d369ab2ae2ce5..7aeb875198956 100644 --- a/include/zephyr/drivers/dma/dma_stm32.h +++ b/include/zephyr/drivers/dma/dma_stm32.h @@ -57,30 +57,30 @@ /* macros for channel-config */ /* enable circular buffer */ -#define STM32_DMA_CONFIG_CYCLIC(config) ((config >> 5) & 0x1) +#define STM32_DMA_CONFIG_CYCLIC(config) (((config) >> 5) & 0x1) /* direction defined on bits 6-7 */ /* 0 -> MEM_TO_MEM, 1 -> MEM_TO_PERIPH, 2 -> PERIPH_TO_MEM */ -#define STM32_DMA_CONFIG_DIRECTION(config) ((config >> 6) & 0x3) +#define STM32_DMA_CONFIG_DIRECTION(config) (((config) >> 6) & 0x3) /* periph increment defined on bit 9 as true/false */ -#define STM32_DMA_CONFIG_PERIPHERAL_ADDR_INC(config) ((config >> 9) & 0x1) +#define STM32_DMA_CONFIG_PERIPHERAL_ADDR_INC(config) (((config) >> 9) & 0x1) /* mem increment defined on bit 10 as true/false */ -#define STM32_DMA_CONFIG_MEMORY_ADDR_INC(config) ((config >> 10) & 0x1) +#define STM32_DMA_CONFIG_MEMORY_ADDR_INC(config) (((config) >> 10) & 0x1) /* periph data size defined on bits 11-12 */ /* 0 -> 1 byte, 1 -> 2 bytes, 2 -> 4 bytes */ #define STM32_DMA_CONFIG_PERIPHERAL_DATA_SIZE(config) \ - (1 << ((config >> 11) & 0x3)) + (1 << (((config) >> 11) & 0x3)) /* memory data size defined on bits 13, 14 */ /* 0 -> 1 byte, 1 -> 2 bytes, 2 -> 4 bytes */ #define STM32_DMA_CONFIG_MEMORY_DATA_SIZE(config) \ - (1 << ((config >> 13) & 0x3)) + (1 << (((config) >> 13) & 0x3)) /* priority increment offset defined on bit 15 */ -#define STM32_DMA_CONFIG_PERIPHERAL_INC_FIXED(config) ((config >> 15) & 0x1) +#define STM32_DMA_CONFIG_PERIPHERAL_INC_FIXED(config) (((config) >> 15) & 0x1) /* priority defined on bits 16-17 as 0, 1, 2, 3 */ -#define STM32_DMA_CONFIG_PRIORITY(config) ((config >> 16) & 0x3) +#define STM32_DMA_CONFIG_PRIORITY(config) (((config) >> 16) & 0x3) /* macro for features (only for dma-v1) */ #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_dma_v1) -#define STM32_DMA_FEATURES_FIFO_THRESHOLD(features) (features & 0x3) +#define STM32_DMA_FEATURES_FIFO_THRESHOLD(features) ((features) & 0x3) #else #define STM32_DMA_FEATURES_FIFO_THRESHOLD(features) 0 #endif diff --git a/include/zephyr/dt-bindings/dma/stm32_dma.h b/include/zephyr/dt-bindings/dma/stm32_dma.h index 6eb1e5be139f0..345eb9d882fee 100644 --- a/include/zephyr/dt-bindings/dma/stm32_dma.h +++ b/include/zephyr/dt-bindings/dma/stm32_dma.h @@ -11,46 +11,46 @@ * @{ */ /** DMA cyclic mode config on bit 5*/ -#define STM32_DMA_CH_CFG_MODE(val) ((val & 0x1) << 5) +#define STM32_DMA_CH_CFG_MODE(val) (((val) & 0x1) << 5) #define STM32_DMA_MODE_NORMAL STM32_DMA_CH_CFG_MODE(0) #define STM32_DMA_MODE_CYCLIC STM32_DMA_CH_CFG_MODE(1) /** DMA transfer direction config on bits 6-7 */ -#define STM32_DMA_CH_CFG_DIRECTION(val) ((val & 0x3) << 6) +#define STM32_DMA_CH_CFG_DIRECTION(val) (((val) & 0x3) << 6) #define STM32_DMA_MEMORY_TO_MEMORY STM32_DMA_CH_CFG_DIRECTION(0) #define STM32_DMA_MEMORY_TO_PERIPH STM32_DMA_CH_CFG_DIRECTION(1) #define STM32_DMA_PERIPH_TO_MEMORY STM32_DMA_CH_CFG_DIRECTION(2) #define STM32_DMA_PERIPH_TO_PERIPH STM32_DMA_CH_CFG_DIRECTION(3) /** DMA Peripheral increment Address config on bit 9 */ -#define STM32_DMA_CH_CFG_PERIPH_ADDR_INC(val) ((val & 0x1) << 9) +#define STM32_DMA_CH_CFG_PERIPH_ADDR_INC(val) (((val) & 0x1) << 9) #define STM32_DMA_PERIPH_NO_INC STM32_DMA_CH_CFG_PERIPH_ADDR_INC(0) #define STM32_DMA_PERIPH_INC STM32_DMA_CH_CFG_PERIPH_ADDR_INC(1) /** DMA Memory increment Address config on bit 10 */ -#define STM32_DMA_CH_CFG_MEM_ADDR_INC(val) ((val & 0x1) << 10) +#define STM32_DMA_CH_CFG_MEM_ADDR_INC(val) (((val) & 0x1) << 10) #define STM32_DMA_MEM_NO_INC STM32_DMA_CH_CFG_MEM_ADDR_INC(0) #define STM32_DMA_MEM_INC STM32_DMA_CH_CFG_MEM_ADDR_INC(1) /** DMA Peripheral data size config on bits 11, 12 */ -#define STM32_DMA_CH_CFG_PERIPH_WIDTH(val) ((val & 0x3) << 11) +#define STM32_DMA_CH_CFG_PERIPH_WIDTH(val) (((val) & 0x3) << 11) #define STM32_DMA_PERIPH_8BITS STM32_DMA_CH_CFG_PERIPH_WIDTH(0) #define STM32_DMA_PERIPH_16BITS STM32_DMA_CH_CFG_PERIPH_WIDTH(1) #define STM32_DMA_PERIPH_32BITS STM32_DMA_CH_CFG_PERIPH_WIDTH(2) /** DMA Memory data size config on bits 13, 14 */ -#define STM32_DMA_CH_CFG_MEM_WIDTH(val) ((val & 0x3) << 13) +#define STM32_DMA_CH_CFG_MEM_WIDTH(val) (((val) & 0x3) << 13) #define STM32_DMA_MEM_8BITS STM32_DMA_CH_CFG_MEM_WIDTH(0) #define STM32_DMA_MEM_16BITS STM32_DMA_CH_CFG_MEM_WIDTH(1) #define STM32_DMA_MEM_32BITS STM32_DMA_CH_CFG_MEM_WIDTH(2) /** DMA Peripheral increment offset config on bit 15 */ -#define STM32_DMA_CH_CFG_PERIPH_INC_FIXED(val) ((val & 0x1) << 15) +#define STM32_DMA_CH_CFG_PERIPH_INC_FIXED(val) (((val) & 0x1) << 15) #define STM32_DMA_OFFSET_LINKED_BUS STM32_DMA_CH_CFG_PERIPH_INC_FIXED(0) #define STM32_DMA_OFFSET_FIXED_4 STM32_DMA_CH_CFG_PERIPH_INC_FIXED(1) /** DMA Priority config on bits 16, 17*/ -#define STM32_DMA_CH_CFG_PRIORITY(val) ((val & 0x3) << 16) +#define STM32_DMA_CH_CFG_PRIORITY(val) (((val) & 0x3) << 16) #define STM32_DMA_PRIORITY_LOW STM32_DMA_CH_CFG_PRIORITY(0) #define STM32_DMA_PRIORITY_MEDIUM STM32_DMA_CH_CFG_PRIORITY(1) #define STM32_DMA_PRIORITY_HIGH STM32_DMA_CH_CFG_PRIORITY(2) From 8142991c9117fe890fa0b97cf702447e655e0361 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 15:00:51 +0200 Subject: [PATCH 06/11] include/zephyr/dt-bindings: add stm32mp2 dma clock Add the clock bindings for the stm32mp2 dmas, HPDMA(1-3) and LPDMA1. Signed-off-by: Youssef Zini --- include/zephyr/dt-bindings/clock/stm32mp2_clock.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/zephyr/dt-bindings/clock/stm32mp2_clock.h b/include/zephyr/dt-bindings/clock/stm32mp2_clock.h index 5a11ed64d4049..4fc6ffaa249b5 100644 --- a/include/zephyr/dt-bindings/clock/stm32mp2_clock.h +++ b/include/zephyr/dt-bindings/clock/stm32mp2_clock.h @@ -39,6 +39,12 @@ #define STM32_CLOCK_PERIPH_GPIOK 0x554 #define STM32_CLOCK_PERIPH_GPIOZ 0x558 +/* HPDMA/LPDMA */ +#define STM32_CLOCK_PERIPH_HPDMA1 0x55C +#define STM32_CLOCK_PERIPH_HPDMA2 0x560 +#define STM32_CLOCK_PERIPH_HPDMA3 0x564 +#define STM32_CLOCK_PERIPH_LPDMA1 0x568 + /* SPI Peripheral */ #define STM32_CLOCK_PERIPH_SPI1 0x758 #define STM32_CLOCK_PERIPH_SPI2 0x75C From e318d74321117c97860a9bad6420f1db438ef773 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 15:48:51 +0200 Subject: [PATCH 07/11] dts/arm/st/mp2/stm32mp2_m33.dtsi: add hpdma3 node Add a new HPDMA3 node to the STM32MP2 M33 device tree. With the current configuration, the Cortex-M33 can only use the HPDMA3 channels 0 to 13. Signed-off-by: Youssef Zini --- dts/arm/st/mp2/stm32mp2_m33.dtsi | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dts/arm/st/mp2/stm32mp2_m33.dtsi b/dts/arm/st/mp2/stm32mp2_m33.dtsi index 1d52adb6c97ea..7bdde23d96f90 100644 --- a/dts/arm/st/mp2/stm32mp2_m33.dtsi +++ b/dts/arm/st/mp2/stm32mp2_m33.dtsi @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -430,6 +431,20 @@ interrupts = <8 0>; status = "disabled"; }; + + hpdma3: dma@40420000 { + compatible = "st,stm32-dma-v3"; + reg = <0x40420000 DT_SIZE_K(4)>; + #dma-cells = <3>; + interrupts = <65 0>, <66 0>, <67 0>, <68 0>, + <69 0>, <70 0>, <71 0>, <72 0>, + <73 0>, <74 0>, <75 0>, <76 0>, + <77 0>, <78 0>; + dma-channels = <14>; + dma-requests = <183>; + dma-offset = <0>; + status = "disabled"; + }; }; clocks { From c317ef17eac6332110f1fef15edc676ef91b4e66 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Mon, 28 Jul 2025 15:24:27 +0200 Subject: [PATCH 08/11] boards/st/stm32mp257f_ev1: add dma to board yaml Add dma as a supported driver in the STM32MP257F-EV1 board YAML file. Signed-off-by: Youssef Zini --- boards/st/stm32mp257f_ev1/stm32mp257f_ev1_stm32mp257fxx_m33.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/boards/st/stm32mp257f_ev1/stm32mp257f_ev1_stm32mp257fxx_m33.yaml b/boards/st/stm32mp257f_ev1/stm32mp257f_ev1_stm32mp257fxx_m33.yaml index f319715640e5a..40952967eb87e 100644 --- a/boards/st/stm32mp257f_ev1/stm32mp257f_ev1_stm32mp257fxx_m33.yaml +++ b/boards/st/stm32mp257f_ev1/stm32mp257f_ev1_stm32mp257fxx_m33.yaml @@ -11,6 +11,7 @@ supported: - shell - i2c - spi + - dma testing: ignore_tags: - cmsis_rtos_v2 From 7254eb3454aef0dc875d792d6d3846d5c05b7b3a Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 14:29:20 +0200 Subject: [PATCH 09/11] samples/drivers/uart: add stm32mp257f_ev1 overlay Add stm32mp257f_ev1 overlay for uart async API and circular_dma samples using uart5 with HPDMA3 channels 0 and 1 for TX and RX respectively. Signed-off-by: Youssef Zini --- .../stm32mp257f_ev1_stm32mp257fxx_m33.overlay | 17 +++++++++++++++++ .../stm32mp257f_ev1_stm32mp257fxx_m33.overlay | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 samples/boards/st/uart/circular_dma/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay create mode 100644 samples/drivers/uart/async_api/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay diff --git a/samples/boards/st/uart/circular_dma/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay b/samples/boards/st/uart/circular_dma/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay new file mode 100644 index 0000000000000..aebe5bbb2a05b --- /dev/null +++ b/samples/boards/st/uart/circular_dma/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2025 Savoir-faire Linux, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&uart5 { + dmas = <&hpdma3 0 18 STM32_DMA_PERIPH_TX>, + <&hpdma3 1 17 (STM32_DMA_PERIPH_RX | STM32_DMA_MEM_8BITS | + STM32_DMA_MODE_CYCLIC)>; + dma-names = "tx", "rx"; + fifo-enable; +}; + +&hpdma3 { + status = "okay"; +}; diff --git a/samples/drivers/uart/async_api/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay b/samples/drivers/uart/async_api/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay new file mode 100644 index 0000000000000..cfd1d156a687f --- /dev/null +++ b/samples/drivers/uart/async_api/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2025 Savoir-faire Linux, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&uart5 { + dmas = <&hpdma3 1 18 (STM32_DMA_PERIPH_TX)>, + <&hpdma3 0 17 (STM32_DMA_PERIPH_RX)>; + dma-names = "tx", "rx"; + fifo-enable; +}; + +&hpdma3 { + status = "okay"; +}; From 05a84944f5ad16aadb126d30a6950faf98d71659 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Fri, 11 Jul 2025 14:47:42 +0200 Subject: [PATCH 10/11] tests/drivers/dma: add stm32mp257f_ev1 support Add stm32mp257f_ev1 overlays and related configuration files for the DMA tests. This includes support for: * chan_blen_transfer * cyclic * loop_transfer * scatter_gather Add stm32mp257f_ev1/stm32mp257fxx/m33 to the platform_allow list. Signed-off-by: Youssef Zini --- .../boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf | 5 +++++ .../stm32mp257f_ev1_stm32mp257fxx_m33.overlay | 9 +++++++++ .../stm32mp257f_ev1_stm32mp257fxx_m33.overlay | 15 +++++++++++++++ tests/drivers/dma/cyclic/testcase.yaml | 1 + .../boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf | 4 ++++ .../stm32mp257f_ev1_stm32mp257fxx_m33.overlay | 9 +++++++++ .../boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf | 4 ++++ .../stm32mp257f_ev1_stm32mp257fxx_m33.overlay | 15 +++++++++++++++ tests/drivers/dma/scatter_gather/testcase.yaml | 1 + 9 files changed, 63 insertions(+) create mode 100644 tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf create mode 100644 tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay create mode 100644 tests/drivers/dma/cyclic/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay create mode 100644 tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf create mode 100644 tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay create mode 100644 tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf create mode 100644 tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay diff --git a/tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf b/tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf new file mode 100644 index 0000000000000..0f291d964a897 --- /dev/null +++ b/tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf @@ -0,0 +1,5 @@ +# Copyright (C) 2025 Savoir-faire Linux, Inc. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_DMA_TRANSFER_CHANNEL_NR_0=0 +CONFIG_DMA_TRANSFER_CHANNEL_NR_1=13 diff --git a/tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay b/tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay new file mode 100644 index 0000000000000..ba0692a68950f --- /dev/null +++ b/tests/drivers/dma/chan_blen_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2025 Savoir-faire Linux, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +tst_dma0: &hpdma3 { + status = "okay"; +}; diff --git a/tests/drivers/dma/cyclic/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay b/tests/drivers/dma/cyclic/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay new file mode 100644 index 0000000000000..10ee0cedddb37 --- /dev/null +++ b/tests/drivers/dma/cyclic/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2025 Savoir-faire Linux, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + dma0 = &hpdma3; + }; +}; + +&hpdma3 { + status = "okay"; +}; diff --git a/tests/drivers/dma/cyclic/testcase.yaml b/tests/drivers/dma/cyclic/testcase.yaml index ab5959ca0ebd8..98d03e37cd44f 100644 --- a/tests/drivers/dma/cyclic/testcase.yaml +++ b/tests/drivers/dma/cyclic/testcase.yaml @@ -7,4 +7,5 @@ tests: platform_allow: - xmc47_relax_kit - xmc45_relax_kit + - stm32mp257f_ev1/stm32mp257fxx/m33 filter: dt_alias_exists("dma0") diff --git a/tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf b/tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf new file mode 100644 index 0000000000000..6f8f2b132e2c6 --- /dev/null +++ b/tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf @@ -0,0 +1,4 @@ +# Copyright (C) 2025 Savoir-faire Linux, Inc. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_DMA_LOOP_TRANSFER_CHANNEL_NR=0 diff --git a/tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay b/tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay new file mode 100644 index 0000000000000..ba0692a68950f --- /dev/null +++ b/tests/drivers/dma/loop_transfer/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2025 Savoir-faire Linux, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +tst_dma0: &hpdma3 { + status = "okay"; +}; diff --git a/tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf b/tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf new file mode 100644 index 0000000000000..dc1f88384740d --- /dev/null +++ b/tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.conf @@ -0,0 +1,4 @@ +# Copyright (C) 2025 Savoir-faire Linux, Inc. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_DMA_SG_CHANNEL_NR=0 diff --git a/tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay b/tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay new file mode 100644 index 0000000000000..10ee0cedddb37 --- /dev/null +++ b/tests/drivers/dma/scatter_gather/boards/stm32mp257f_ev1_stm32mp257fxx_m33.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2025 Savoir-faire Linux, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + dma0 = &hpdma3; + }; +}; + +&hpdma3 { + status = "okay"; +}; diff --git a/tests/drivers/dma/scatter_gather/testcase.yaml b/tests/drivers/dma/scatter_gather/testcase.yaml index 0e08abae59da4..a4073ec786c94 100644 --- a/tests/drivers/dma/scatter_gather/testcase.yaml +++ b/tests/drivers/dma/scatter_gather/testcase.yaml @@ -21,6 +21,7 @@ tests: - adp_xc7k/ae350 - adp_xc7k/ae350/clic - bg29_rb4420a + - stm32mp257f_ev1/stm32mp257fxx/m33 filter: dt_alias_exists("dma0") integration_platforms: - intel_adsp/cavs25 From c89e0896f43545134aa4c5041c7e41d04f1f6fc2 Mon Sep 17 00:00:00 2001 From: Youssef Zini Date: Mon, 28 Jul 2025 16:59:30 +0200 Subject: [PATCH 11/11] drivers/dma/dma_stm32_v3.c: add LLI support Add support for linked list items (LLI) in the STM32 DMA v3 driver. This allows for more flexible DMA configurations, such as scatter-gather and cyclic transfers. This allows to pass both the dma_m2m_cyclic and the dma_m2m_sg tests on the stm32mp257f_ev1 board. Signed-off-by: Youssef Zini --- drivers/dma/dma_stm32_v3.c | 111 ++++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 19 deletions(-) diff --git a/drivers/dma/dma_stm32_v3.c b/drivers/dma/dma_stm32_v3.c index d4dd03b6b31f0..16b656d308630 100644 --- a/drivers/dma/dma_stm32_v3.c +++ b/drivers/dma/dma_stm32_v3.c @@ -16,9 +16,8 @@ LOG_MODULE_REGISTER(dma_stm32_v3, CONFIG_DMA_LOG_LEVEL); #define DT_DRV_COMPAT st_stm32_dma_v3 #define DMA_STM32_MAX_DATA_ITEMS 0xffff -/* Since at this point we only support cyclic mode , we only need 3 descriptors - * at most to update the source and destination addresses and the update - * registers. TODO: Raise this number for larger linked lists. +/* Since the descriptors pool is allocated statically, we define the number of + * descriptors per channel to be used for linked list trasnfers. */ #define DMA_STM32_NUM_DESCRIPTORS_PER_CHANNEL 24 #define POLLING_TIMEOUT_US (10 * USEC_PER_MSEC) @@ -40,6 +39,7 @@ struct dma_stm32_channel { uint32_t src_size; uint32_t dst_size; void *user_data; + uint32_t complete_callback_en; dma_callback_t dma_callback; bool cyclic; int block_count; @@ -240,8 +240,8 @@ static int dma_stm32_disable_channel(DMA_TypeDef *dma, uint32_t channel) static int dma_stm32_validate_transfer_sizes(struct dma_config *config) { if (config->head_block->block_size > DMA_STM32_MAX_DATA_ITEMS) { - LOG_ERR("Data size exceeds the maximum limit: %d>%d", config->head_block->block_size, - DMA_STM32_MAX_DATA_ITEMS); + LOG_ERR("Data size exceeds the maximum limit: %d>%d", + config->head_block->block_size, DMA_STM32_MAX_DATA_ITEMS); return -EINVAL; } @@ -272,11 +272,65 @@ static int dma_stm32_validate_transfer_sizes(struct dma_config *config) return 0; } +static void dma_stm32_configure_linked_list(uint32_t channel, struct dma_config *config, + uint32_t *linked_list_node, DMA_TypeDef *dma) +{ + uint32_t next_desc = 1; + struct dma_block_config *block_config; + uint32_t registers_update = 0; + uint32_t addr_offset = 0; + uint32_t descriptor_index = 0; + uint32_t base_addr = 0; + uint32_t next_desc_addr = 0; + + block_config = config->head_block; + base_addr = (uint32_t)&linked_list_node[descriptor_index]; + + LL_DMA_SetLinkedListBaseAddr(dma, channel, base_addr); + + for (uint32_t i = 0; i < config->block_count; i++) { + registers_update = 0; + LOG_DBG("Configuring block descriptor %d for channel %d", i, channel); + + linked_list_node[descriptor_index] = block_config->source_address; + descriptor_index++; + linked_list_node[descriptor_index] = block_config->dest_address; + descriptor_index++; + + if (i < config->block_count - 1) { + registers_update |= + LL_DMA_UPDATE_CSAR | LL_DMA_UPDATE_CDAR | LL_DMA_UPDATE_CLLR; + block_config = block_config->next_block; + next_desc_addr = (uint32_t)&linked_list_node[descriptor_index + 1]; + } else if (config->cyclic) { + LOG_DBG("Last descriptor %d for channel %d, linking to first", i, channel); + registers_update |= + LL_DMA_UPDATE_CSAR | LL_DMA_UPDATE_CDAR | LL_DMA_UPDATE_CLLR; + next_desc_addr = base_addr; + } else { + LOG_DBG("Last descriptor %d for channel %d, no link", i, channel); + registers_update = 0; + next_desc = 0; + } + + if (next_desc != 0) { + addr_offset = next_desc_addr & GENMASK(15, 2); + registers_update |= addr_offset; + } + + linked_list_node[descriptor_index] = registers_update; + descriptor_index++; + + if (i == 0) { + LL_DMA_ConfigLinkUpdate(dma, channel, registers_update, addr_offset); + } + } +} + int dma_stm32_configure(const struct device *dev, uint32_t id, struct dma_config *config) { const struct dma_stm32_config *dev_config; struct dma_stm32_channel *channel_config; - struct dma_block_config *block_config; struct dma_stm32_descriptor hwdesc; uint32_t channel; DMA_TypeDef *dma; @@ -326,15 +380,13 @@ int dma_stm32_configure(const struct device *dev, uint32_t id, struct dma_config return ret; } - block_config = config->head_block; - - ret = dma_stm32_get_src_inc_mode(block_config->source_addr_adj, &src_inc_mode); + ret = dma_stm32_get_src_inc_mode(config->head_block->source_addr_adj, &src_inc_mode); if (ret < 0) { return ret; } LOG_DBG("Source address increment: %d", src_inc_mode); - ret = dma_stm32_get_dest_inc_mode(block_config->dest_addr_adj, &dest_inc_mode); + ret = dma_stm32_get_dest_inc_mode(config->head_block->dest_addr_adj, &dest_inc_mode); if (ret < 0) { return ret; } @@ -360,6 +412,7 @@ int dma_stm32_configure(const struct device *dev, uint32_t id, struct dma_config channel_config->src_size = config->source_data_size; channel_config->dst_size = config->dest_data_size; channel_config->cyclic = config->cyclic; + channel_config->complete_callback_en = config->complete_callback_en; dma_stm32_disable_it(dma, channel); @@ -371,8 +424,12 @@ int dma_stm32_configure(const struct device *dev, uint32_t id, struct dma_config if (!linked_list_needed) { ccr |= LL_DMA_LSM_1LINK_EXECUTION; } else { - LOG_ERR("Only single block transfers are supported for now"); - return -ENOTSUP; + ccr |= LL_DMA_LSM_FULL_EXECUTION; + + dma_stm32_configure_linked_list(channel, config, + dev_config->linked_list_buffer + + id * DMA_STM32_NUM_DESCRIPTORS_PER_CHANNEL, + dma); } /* TODO: support port specifier from configuration */ @@ -390,7 +447,19 @@ int dma_stm32_configure(const struct device *dev, uint32_t id, struct dma_config config->dest_burst_length); hwdesc.channel_tr2 = ll_direction; - hwdesc.channel_tr2 |= LL_DMA_TCEM_BLK_TRANSFER; + + if (linked_list_needed) { + if (channel_config->complete_callback_en == 1) { + hwdesc.channel_tr2 |= LL_DMA_TCEM_EACH_LLITEM_TRANSFER; + LOG_DBG("Enabling TC callback at the end of each linked list item"); + } else { + hwdesc.channel_tr2 |= LL_DMA_TCEM_LAST_LLITEM_TRANSFER; + LOG_DBG("Enabling TC callback at the end of last linked list item"); + } + } else { + hwdesc.channel_tr2 |= LL_DMA_TCEM_BLK_TRANSFER; + LOG_DBG("Enabling TC callback at the end of the block"); + } LL_DMA_ConfigChannelTransfer(dma, channel, hwdesc.channel_tr2); @@ -691,16 +760,20 @@ static void dma_stm32_irq_handler(const struct device *dev, uint32_t id) channel_config->dma_callback(dev, channel_config->user_data, callback_arg, DMA_STATUS_BLOCK); } else if (dma_stm32_is_tc_irq_active(dma, channel)) { - if (!channel_config->cyclic) { - channel_config->busy = false; - } - if (!channel_config->hal_override) { LL_DMA_ClearFlag_TC(dma, channel); } - channel_config->dma_callback(dev, channel_config->user_data, callback_arg, - DMA_STATUS_COMPLETE); + if (channel_config->complete_callback_en == 1) { + channel_config->dma_callback(dev, channel_config->user_data, callback_arg, + DMA_STATUS_BLOCK); + } else { + if (!channel_config->cyclic) { + channel_config->busy = false; + } + channel_config->dma_callback(dev, channel_config->user_data, callback_arg, + DMA_STATUS_COMPLETE); + } } else { LOG_ERR("Transfer Error."); channel_config->busy = false;