driver: uart: stm32: Add workaround for DMAT errata in low-power modes

This change adds additional workaround for following errata:
"USART does not generate DMA requests after setting/clearing DMAT bit"
Instead of keepint DMAT bit set, it sends first byte by polling
in firmware. This prevents additional power consumption in STOP mode,
caused by keeping DMAT bit set.

Signed-off-by: Adam Berlinger <adam.berlinger@st.com>
This commit is contained in:
Adam Berlinger 2025-03-28 14:50:10 +01:00 committed by Benjamin Cabé
commit c1a173a884
2 changed files with 91 additions and 24 deletions

View file

@ -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

View file

@ -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;
}