diff --git a/drivers/serial/Kconfig.stm32 b/drivers/serial/Kconfig.stm32 index c2a6f96c3b5..b8aaa9d764f 100644 --- a/drivers/serial/Kconfig.stm32 +++ b/drivers/serial/Kconfig.stm32 @@ -25,15 +25,34 @@ config UART_STM32 if UART_STM32 -config UART_STM32U5_ERRATA_DMAT - bool - default y - depends on SOC_STM32U575XX || SOC_STM32U585XX || \ +if SOC_STM32U575XX || SOC_STM32U585XX || \ SOC_STM32H562XX || SOC_STM32H563XX || SOC_STM32H573XX + +choice UART_STM32U5_ERRATA_DMAT + prompt "Workaround for DMAT errata on selected devices" + default UART_STM32U5_ERRATA_DMAT_LOWPOWER if PM + default UART_STM32U5_ERRATA_DMAT_NOCLEAR if !PM help Handles erratum "USART does not generate DMA requests after setting/clearing DMAT bit". Seen in Errata Sheet 0499 § 2.19.2 and §2.20.1 for stm32u57x/u58x, Errata Sheet 0565 § 2.14.1 and §2.15.1 for stm32h56x/h57x -endif +config UART_STM32U5_ERRATA_DMAT_LOWPOWER + bool "Send first byte by polling" + help + This option sends first byte via software polling and + rest of the buffer is sent via DMA. This option is suitable + for STOP low-power modes. + +config UART_STM32U5_ERRATA_DMAT_NOCLEAR + bool "Do not clear DMAT" + help + This option keeps DMAT bit set. This may cause additional power + consumption in STOP low-power modes. + +endchoice + +endif # U575 || U585 || H562 || H563 || H573 + +endif # UART_STM32 diff --git a/drivers/serial/uart_stm32.c b/drivers/serial/uart_stm32.c index 6728a7aa4dd..296085b8c8f 100644 --- a/drivers/serial/uart_stm32.c +++ b/drivers/serial/uart_stm32.c @@ -1434,7 +1434,7 @@ static inline void uart_stm32_dma_tx_enable(const struct device *dev) static inline void uart_stm32_dma_tx_disable(const struct device *dev) { -#ifdef CONFIG_UART_STM32U5_ERRATA_DMAT +#ifdef CONFIG_UART_STM32U5_ERRATA_DMAT_NOCLEAR ARG_UNUSED(dev); /* @@ -1615,8 +1615,16 @@ static int uart_stm32_async_tx(const struct device *dev, const struct uart_stm32_config *config = dev->config; USART_TypeDef *usart = config->usart; struct uart_stm32_data *data = dev->data; + __maybe_unused unsigned int key; int ret; + /* Check size of singl character (1 or 2 bytes) */ + const int char_size = (IS_ENABLED(CONFIG_UART_WIDE_DATA) && + (LL_USART_GetDataWidth(usart) == LL_USART_DATAWIDTH_9B) && + (LL_USART_GetParity(usart) == LL_USART_PARITY_NONE)) + ? 2 + : 1; + if (data->dma_tx.dma_dev == NULL) { return -ENODEV; } @@ -1648,34 +1656,74 @@ static int uart_stm32_async_tx(const struct device *dev, /* Enable TC interrupt so we can signal correct TX done */ LL_USART_EnableIT_TC(usart); - /* set source address */ - data->dma_tx.blk_cfg.source_address = (uint32_t)data->dma_tx.buffer; - data->dma_tx.blk_cfg.block_size = data->dma_tx.buffer_length; + /** + * Setup DMA descriptor for TX. + * If DMAT low-power errata workaround is enabled, + * we send the first character using polling and the rest + * using DMA; as such, single-character transfers use only + * polling and don't need to prepare a DMA descriptor. + * In other configurations, the DMA is always used. + */ + if (!IS_ENABLED(CONFIG_UART_STM32U5_ERRATA_DMAT_LOWPOWER) || + data->dma_tx.buffer_length > char_size) { + if (IS_ENABLED(CONFIG_UART_STM32U5_ERRATA_DMAT_LOWPOWER)) { + /* set source address */ + data->dma_tx.blk_cfg.source_address = + ((uint32_t)data->dma_tx.buffer) + char_size; + data->dma_tx.blk_cfg.block_size = data->dma_tx.buffer_length - char_size; + } else { + /* set source address */ + data->dma_tx.blk_cfg.source_address = ((uint32_t)data->dma_tx.buffer); + data->dma_tx.blk_cfg.block_size = data->dma_tx.buffer_length; + } - ret = dma_config(data->dma_tx.dma_dev, data->dma_tx.dma_channel, - &data->dma_tx.dma_cfg); + ret = dma_config(data->dma_tx.dma_dev, data->dma_tx.dma_channel, + &data->dma_tx.dma_cfg); - if (ret != 0) { - LOG_ERR("dma tx config error!"); - return -EINVAL; + if (ret != 0) { + LOG_ERR("dma tx config error!"); + return -EINVAL; + } + + if (dma_start(data->dma_tx.dma_dev, data->dma_tx.dma_channel)) { + LOG_ERR("UART err: TX DMA start failed!"); + return -EFAULT; + } + + /* Start TX timer */ + async_timer_start(&data->dma_tx.timeout_work, data->dma_tx.timeout); } - if (dma_start(data->dma_tx.dma_dev, data->dma_tx.dma_channel)) { - LOG_ERR("UART err: TX DMA start failed!"); - return -EFAULT; - } - - /* Start TX timer */ - async_timer_start(&data->dma_tx.timeout_work, data->dma_tx.timeout); - #ifdef CONFIG_PM /* Do not allow system to suspend until transmission has completed */ uart_stm32_pm_policy_state_lock_get_unconditional(); #endif - /* Enable TX DMA requests */ - uart_stm32_dma_tx_enable(dev); + if (IS_ENABLED(CONFIG_UART_STM32U5_ERRATA_DMAT_LOWPOWER)) { + /** + * Send first character using polling. + * The DMA TX needs to be enabled before the UART transmits + * the character and triggers transfer complete event. + */ + key = irq_lock(); + + if (char_size > 1) { + LL_USART_TransmitData9(usart, *(const uint16_t *)tx_data); + } else { + LL_USART_TransmitData8(usart, *tx_data); + } + + if (data->dma_tx.buffer_length > char_size) { + /* Enable TX DMA requests */ + uart_stm32_dma_tx_enable(dev); + } + + irq_unlock(key); + } else { + /* Enable TX DMA requests */ + uart_stm32_dma_tx_enable(dev); + } return 0; }