From 702d271aa780f1253062ecc4e78b8ec62848e6a1 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Thu, 12 Jun 2025 17:36:54 +1000 Subject: [PATCH 1/2] disk: sdmmc: support L4 series with shared DMA channel Update the driver to support DMA operations on L4 series devices, with a shared DMA channel. Split channels do not work on these chips, since there is no dedicated TX and RX channels on the DMA, so configuring two channels with SDMMC as the peripheral results in a non-functional configuration. Fixes #91216. Signed-off-by: Jordan Yates --- drivers/disk/sdmmc_stm32.c | 97 +++++++++++++++++++++++++--- dts/bindings/mmc/st,stm32-sdmmc.yaml | 7 +- 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/drivers/disk/sdmmc_stm32.c b/drivers/disk/sdmmc_stm32.c index b0f3503b6c71..09ebe84a9d67 100644 --- a/drivers/disk/sdmmc_stm32.c +++ b/drivers/disk/sdmmc_stm32.c @@ -26,6 +26,8 @@ LOG_MODULE_REGISTER(stm32_sdmmc, CONFIG_SDMMC_LOG_LEVEL); #include #include #include +/* Uses a shared DMA channel for read & write */ +#define STM32_SDMMC_USE_DMA_SHARED DT_INST_DMAS_HAS_NAME(0, txrx) #endif #ifndef MMC_TypeDef @@ -87,11 +89,16 @@ struct stm32_sdmmc_priv { const struct reset_dt_spec reset; #if STM32_SDMMC_USE_DMA +#if STM32_SDMMC_USE_DMA_SHARED + struct sdmmc_dma_stream dma_txrx; + DMA_HandleTypeDef dma_txrx_handle; +#else struct sdmmc_dma_stream dma_rx; struct sdmmc_dma_stream dma_tx; DMA_HandleTypeDef dma_tx_handle; DMA_HandleTypeDef dma_rx_handle; #endif +#endif /* STM32_SDMMC_USE_DMA */ }; #ifdef CONFIG_SDMMC_STM32_HWFC @@ -208,12 +215,16 @@ static int stm32_sdmmc_configure_dma(DMA_HandleTypeDef *handle, struct sdmmc_dma dma->cfg.user_data = handle; + /* Reserve the channel in the DMA subsystem, even though we use the HAL API. + * See the usage of STM32_DMA_HAL_OVERRIDE. + */ ret = dma_config(dma->dev, dma->channel, &dma->cfg); if (ret != 0) { LOG_ERR("Failed to conig"); return ret; } +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_dma_v1) handle->Instance = __LL_DMA_GET_STREAM_INSTANCE(dma->reg, dma->channel_nb); handle->Init.Channel = dma->cfg.dma_slot * DMA_CHANNEL_1; handle->Init.PeriphInc = DMA_PINC_DISABLE; @@ -221,11 +232,28 @@ static int stm32_sdmmc_configure_dma(DMA_HandleTypeDef *handle, struct sdmmc_dma handle->Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; handle->Init.MemDataAlignment = DMA_MDATAALIGN_WORD; handle->Init.Mode = DMA_PFCTRL; - handle->Init.Priority = table_priority[dma->cfg.channel_priority], + handle->Init.Priority = table_priority[dma->cfg.channel_priority]; handle->Init.FIFOMode = DMA_FIFOMODE_ENABLE; handle->Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; handle->Init.MemBurst = DMA_MBURST_INC4; handle->Init.PeriphBurst = DMA_PBURST_INC4; +#else + uint32_t channel_id = dma->channel_nb - STM32_DMA_STREAM_OFFSET; + + BUILD_ASSERT(STM32_SDMMC_USE_DMA_SHARED == 1, "Only txrx is supported on this family"); + /* handle->Init.Direction is not initialised here on purpose. + * Since the channel is reused for both directions, the direction is + * configured before each read/write call. + */ + handle->Instance = __LL_DMA_GET_CHANNEL_INSTANCE(dma->reg, channel_id); + handle->Init.Request = dma->cfg.dma_slot; + handle->Init.PeriphInc = DMA_PINC_DISABLE; + handle->Init.MemInc = DMA_MINC_ENABLE; + handle->Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; + handle->Init.MemDataAlignment = DMA_MDATAALIGN_WORD; + handle->Init.Mode = DMA_NORMAL; + handle->Init.Priority = table_priority[dma->cfg.channel_priority]; +#endif return ret; } @@ -236,6 +264,15 @@ static int stm32_sdmmc_dma_init(struct stm32_sdmmc_priv *priv) LOG_DBG("using dma"); +#if STM32_SDMMC_USE_DMA_SHARED + err = stm32_sdmmc_configure_dma(&priv->dma_txrx_handle, &priv->dma_txrx); + if (err) { + LOG_ERR("failed to init shared DMA"); + return err; + } + __HAL_LINKDMA(&priv->hsd, hdmatx, priv->dma_txrx_handle); + __HAL_LINKDMA(&priv->hsd, hdmarx, priv->dma_txrx_handle); +#else err = stm32_sdmmc_configure_dma(&priv->dma_tx_handle, &priv->dma_tx); if (err) { LOG_ERR("failed to init tx dma"); @@ -251,6 +288,7 @@ static int stm32_sdmmc_dma_init(struct stm32_sdmmc_priv *priv) } __HAL_LINKDMA(&priv->hsd, hdmarx, priv->dma_rx_handle); HAL_DMA_Init(&priv->dma_rx_handle); +#endif /* STM32_SDMMC_USE_DMA_SHARED */ return err; } @@ -258,22 +296,31 @@ static int stm32_sdmmc_dma_init(struct stm32_sdmmc_priv *priv) static int stm32_sdmmc_dma_deinit(struct stm32_sdmmc_priv *priv) { int ret; + + /* Since we use STM32_DMA_HAL_OVERRIDE, the only purpose of dma_stop + * is to notify the DMA subsystem that the channel is no longer in use. + * Calling this before or after HAL_DMA_DeInit makes no difference. + * There is no possibility of runtime failures apart from providing an + * invalid channel ID, which is already validated by the setup. + */ +#if STM32_SDMMC_USE_DMA_SHARED + struct sdmmc_dma_stream *dma_txrx = &priv->dma_txrx; + + ret = dma_stop(dma_txrx->dev, dma_txrx->channel); + __ASSERT(ret == 0, "Shared DMA channel index corrupted"); +#else struct sdmmc_dma_stream *dma_tx = &priv->dma_tx; struct sdmmc_dma_stream *dma_rx = &priv->dma_rx; ret = dma_stop(dma_tx->dev, dma_tx->channel); + __ASSERT(ret == 0, "TX DMA channel index corrupted"); HAL_DMA_DeInit(&priv->dma_tx_handle); - if (ret != 0) { - LOG_ERR("Failed to stop tx DMA transmission"); - return ret; - } + ret = dma_stop(dma_rx->dev, dma_rx->channel); + __ASSERT(ret == 0, "RX DMA channel index corrupted"); HAL_DMA_DeInit(&priv->dma_rx_handle); - if (ret != 0) { - LOG_ERR("Failed to stop rx DMA transmission"); - return ret; - } - return ret; +#endif + return 0; } #endif @@ -401,6 +448,15 @@ static int stm32_sdmmc_access_read(struct disk_info *disk, uint8_t *data_buf, k_sem_take(&priv->thread_lock, K_FOREVER); +#if STM32_SDMMC_USE_DMA_SHARED + /* Initialise the shared DMA channel for the current direction */ + priv->dma_txrx_handle.Init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + if (HAL_DMA_Init(&priv->dma_txrx_handle) != HAL_OK) { + err = -EIO; + goto end; + } +#endif + err = stm32_sdmmc_read_blocks(&priv->hsd, data_buf, start_sector, num_sector); if (err != HAL_OK) { LOG_ERR("sd read block failed %d", err); @@ -410,6 +466,10 @@ static int stm32_sdmmc_access_read(struct disk_info *disk, uint8_t *data_buf, k_sem_take(&priv->sync, K_FOREVER); +#if STM32_SDMMC_USE_DMA_SHARED + HAL_DMA_DeInit(&priv->dma_txrx_handle); +#endif + if (priv->status != DISK_STATUS_OK) { LOG_ERR("sd read error %d", priv->status); err = -EIO; @@ -457,6 +517,15 @@ static int stm32_sdmmc_access_write(struct disk_info *disk, k_sem_take(&priv->thread_lock, K_FOREVER); +#if STM32_SDMMC_USE_DMA_SHARED + /* Initialise the shared DMA channel for the current direction */ + priv->dma_txrx_handle.Init.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + if (HAL_DMA_Init(&priv->dma_txrx_handle) != HAL_OK) { + err = -EIO; + goto end; + } +#endif + err = stm32_sdmmc_write_blocks(&priv->hsd, (uint8_t *)data_buf, start_sector, num_sector); if (err != HAL_OK) { LOG_ERR("sd write block failed %d", err); @@ -466,6 +535,10 @@ static int stm32_sdmmc_access_write(struct disk_info *disk, k_sem_take(&priv->sync, K_FOREVER); +#if STM32_SDMMC_USE_DMA_SHARED + HAL_DMA_DeInit(&priv->dma_txrx_handle); +#endif + if (priv->status != DISK_STATUS_OK) { LOG_ERR("sd write error %d", priv->status); err = -EIO; @@ -813,8 +886,12 @@ static struct stm32_sdmmc_priv stm32_sdmmc_priv_1 = { .pclken = pclken_sdmmc, .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), .reset = RESET_DT_SPEC_INST_GET(0), +#if STM32_SDMMC_USE_DMA_SHARED + SDMMC_DMA_CHANNEL(txrx, TXRX) +#else SDMMC_DMA_CHANNEL(rx, RX) SDMMC_DMA_CHANNEL(tx, TX) +#endif }; DEVICE_DT_INST_DEFINE(0, disk_stm32_sdmmc_init, NULL, diff --git a/dts/bindings/mmc/st,stm32-sdmmc.yaml b/dts/bindings/mmc/st,stm32-sdmmc.yaml index 552e8d06682e..47c428f1d721 100644 --- a/dts/bindings/mmc/st,stm32-sdmmc.yaml +++ b/dts/bindings/mmc/st,stm32-sdmmc.yaml @@ -70,10 +70,15 @@ properties: For example dmas for TX/RX on SDMMC dmas = <&dma2 4 6 0x30000 0x00>, <&dma2 4 3 0x30000 0x00>; + Alternatively, for a shared TX/RX DMA channels on a STM32L4 + dmas = <&dma2 5 7 STM32_DMA_PRIORITY_HIGH>; dma-names: description: | - DMA channel name. If DMA should be used, expected value is "tx", "rx". + DMA channel name. If DMA should be used, expected value is either "tx", "rx" + or a single "txrx" for the shared channel configuration For example dma-names = "tx", "rx"; + or + dma-names = "txrx"; From 52f5de4d7dafc69905ac2abd9846b726e50a256f Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Thu, 12 Jun 2025 17:50:57 +1000 Subject: [PATCH 2/2] tests: disk: disk_access: test STM32L4 DMA Ensure that the shared DMA variant of the STM SDMMC driver is compiled in CI. Signed-off-by: Jordan Yates --- .../disk_access/boards/stm32l496g_disco_stm32l496xx.conf | 1 + .../boards/stm32l496g_disco_stm32l496xx.overlay | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.conf create mode 100644 tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.overlay diff --git a/tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.conf b/tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.conf new file mode 100644 index 000000000000..73aff8957e7a --- /dev/null +++ b/tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.conf @@ -0,0 +1 @@ +CONFIG_DMA=y diff --git a/tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.overlay b/tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.overlay new file mode 100644 index 000000000000..be6891d4b047 --- /dev/null +++ b/tests/drivers/disk/disk_access/boards/stm32l496g_disco_stm32l496xx.overlay @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +&sdmmc1 { + /* Channel 5 request 7 on DMA2 */ + dmas = <&dma2 5 7 STM32_DMA_PRIORITY_HIGH>; + dma-names = "txrx"; +};