diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/st/uart/stm32_uart.c | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/drivers/st/uart/stm32_uart.c b/drivers/st/uart/stm32_uart.c new file mode 100644 index 0000000..63970c7 --- /dev/null +++ b/drivers/st/uart/stm32_uart.c @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2021-2022, STMicroelectronics - All Rights Reserved + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include <common/bl_common.h> +#include <drivers/clk.h> +#include <drivers/delay_timer.h> +#include <drivers/st/stm32_gpio.h> +#include <drivers/st/stm32_uart.h> +#include <drivers/st/stm32_uart_regs.h> +#include <drivers/st/stm32mp_clkfunc.h> +#include <lib/mmio.h> + +#include <platform_def.h> + +/* UART time-out value */ +#define STM32_UART_TIMEOUT_US 20000U + +/* Mask to clear ALL the configuration registers */ + +#define STM32_UART_CR1_FIELDS \ + (USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | \ + USART_CR1_RE | USART_CR1_OVER8 | USART_CR1_FIFOEN) + +#define STM32_UART_CR2_FIELDS \ + (USART_CR2_SLVEN | USART_CR2_DIS_NSS | USART_CR2_ADDM7 | \ + USART_CR2_LBDL | USART_CR2_LBDIE | USART_CR2_LBCL | \ + USART_CR2_CPHA | USART_CR2_CPOL | USART_CR2_CLKEN | \ + USART_CR2_STOP | USART_CR2_LINEN | USART_CR2_SWAP | \ + USART_CR2_RXINV | USART_CR2_TXINV | USART_CR2_DATAINV | \ + USART_CR2_MSBFIRST | USART_CR2_ABREN | USART_CR2_ABRMODE | \ + USART_CR2_RTOEN | USART_CR2_ADD) + +#define STM32_UART_CR3_FIELDS \ + (USART_CR3_EIE | USART_CR3_IREN | USART_CR3_IRLP | \ + USART_CR3_HDSEL | USART_CR3_NACK | USART_CR3_SCEN | \ + USART_CR3_DMAR | USART_CR3_DMAT | USART_CR3_RTSE | \ + USART_CR3_CTSE | USART_CR3_CTSIE | USART_CR3_ONEBIT | \ + USART_CR3_OVRDIS | USART_CR3_DDRE | USART_CR3_DEM | \ + USART_CR3_DEP | USART_CR3_SCARCNT | USART_CR3_WUS | \ + USART_CR3_WUFIE | USART_CR3_TXFTIE | USART_CR3_TCBGTIE | \ + USART_CR3_RXFTCFG | USART_CR3_RXFTIE | USART_CR3_TXFTCFG) + +#define STM32_UART_ISR_ERRORS \ + (USART_ISR_ORE | USART_ISR_NE | USART_ISR_FE | USART_ISR_PE) + +static const uint16_t presc_table[STM32_UART_PRESCALER_NB] = { + 1U, 2U, 4U, 6U, 8U, 10U, 12U, 16U, 32U, 64U, 128U, 256U +}; + +/* @brief BRR division operation to set BRR register in 8-bit oversampling + * mode. + * @param clockfreq: UART clock. + * @param baud_rate: Baud rate set by the user. + * @param prescaler: UART prescaler value. + * @retval Division result. + */ +static uint32_t uart_div_sampling8(unsigned long clockfreq, + uint32_t baud_rate, + uint32_t prescaler) +{ + uint32_t scaled_freq = clockfreq / presc_table[prescaler]; + + return ((scaled_freq * 2) + (baud_rate / 2)) / baud_rate; + +} + +/* @brief BRR division operation to set BRR register in 16-bit oversampling + * mode. + * @param clockfreq: UART clock. + * @param baud_rate: Baud rate set by the user. + * @param prescaler: UART prescaler value. + * @retval Division result. + */ +static uint32_t uart_div_sampling16(unsigned long clockfreq, + uint32_t baud_rate, + uint32_t prescaler) +{ + uint32_t scaled_freq = clockfreq / presc_table[prescaler]; + + return (scaled_freq + (baud_rate / 2)) / baud_rate; + +} + +/* + * @brief Return the UART clock frequency. + * @param huart: UART handle. + * @retval Frequency value in Hz. + */ +static unsigned long uart_get_clock_freq(struct stm32_uart_handle_s *huart) +{ + return fdt_get_uart_clock_freq((uintptr_t)huart->base); +} + +/* + * @brief Configure the UART peripheral. + * @param huart: UART handle. + * @retval UART status. + */ +static int uart_set_config(struct stm32_uart_handle_s *huart, + const struct stm32_uart_init_s *init) +{ + uint32_t tmpreg; + unsigned long clockfreq; + unsigned long int_div; + uint32_t brrtemp; + uint32_t over_sampling; + + /*---------------------- USART BRR configuration --------------------*/ + clockfreq = uart_get_clock_freq(huart); + if (clockfreq == 0UL) { + return -ENODEV; + } + + int_div = clockfreq / init->baud_rate; + if (int_div < 16U) { + uint32_t usartdiv = uart_div_sampling8(clockfreq, + init->baud_rate, + init->prescaler); + + brrtemp = (usartdiv & USART_BRR_DIV_MANTISSA) | + ((usartdiv & USART_BRR_DIV_FRACTION) >> 1); + over_sampling = USART_CR1_OVER8; + } else { + brrtemp = uart_div_sampling16(clockfreq, + init->baud_rate, + init->prescaler) & + (USART_BRR_DIV_FRACTION | USART_BRR_DIV_MANTISSA); + over_sampling = 0x0U; + } + mmio_write_32(huart->base + USART_BRR, brrtemp); + + /* + * ---------------------- USART CR1 Configuration -------------------- + * Clear M, PCE, PS, TE, RE and OVER8 bits and configure + * the UART word length, parity, mode and oversampling: + * - set the M bits according to init->word_length value, + * - set PCE and PS bits according to init->parity value, + * - set TE and RE bits according to init->mode value, + * - set OVER8 bit according baudrate and clock. + */ + tmpreg = init->word_length | + init->parity | + init->mode | + over_sampling | + init->fifo_mode; + mmio_clrsetbits_32(huart->base + USART_CR1, STM32_UART_CR1_FIELDS, tmpreg); + + /* + * --------------------- USART CR2 Configuration --------------------- + * Configure the UART Stop Bits: Set STOP[13:12] bits according + * to init->stop_bits value. + */ + mmio_clrsetbits_32(huart->base + USART_CR2, STM32_UART_CR2_FIELDS, + init->stop_bits); + + /* + * --------------------- USART CR3 Configuration --------------------- + * Configure: + * - UART HardWare Flow Control: set CTSE and RTSE bits according + * to init->hw_flow_control value, + * - one-bit sampling method versus three samples' majority rule + * according to init->one_bit_sampling (not applicable to + * LPUART), + * - set TXFTCFG bit according to init->tx_fifo_threshold value, + * - set RXFTCFG bit according to init->rx_fifo_threshold value. + */ + tmpreg = init->hw_flow_control | init->one_bit_sampling; + + if (init->fifo_mode == USART_CR1_FIFOEN) { + tmpreg |= init->tx_fifo_threshold | + init->rx_fifo_threshold; + } + + mmio_clrsetbits_32(huart->base + USART_CR3, STM32_UART_CR3_FIELDS, tmpreg); + + /* + * --------------------- USART PRESC Configuration ------------------- + * Configure UART Clock Prescaler : set PRESCALER according to + * init->prescaler value. + */ + assert(init->prescaler < STM32_UART_PRESCALER_NB); + mmio_clrsetbits_32(huart->base + USART_PRESC, USART_PRESC_PRESCALER, + init->prescaler); + + return 0; +} + +/* + * @brief Handle UART communication timeout. + * @param huart: UART handle. + * @param flag: Specifies the UART flag to check. + * @retval UART status. + */ +static int stm32_uart_wait_flag(struct stm32_uart_handle_s *huart, uint32_t flag) +{ + uint64_t timeout_ref = timeout_init_us(STM32_UART_TIMEOUT_US); + + while ((mmio_read_32(huart->base + USART_ISR) & flag) == 0U) { + if (timeout_elapsed(timeout_ref)) { + return -ETIMEDOUT; + } + } + + return 0; +} + +/* + * @brief Check the UART idle State. + * @param huart: UART handle. + * @retval UART status. + */ +static int stm32_uart_check_idle(struct stm32_uart_handle_s *huart) +{ + int ret; + + /* Check if the transmitter is enabled */ + if ((mmio_read_32(huart->base + USART_CR1) & USART_CR1_TE) == USART_CR1_TE) { + ret = stm32_uart_wait_flag(huart, USART_ISR_TEACK); + if (ret != 0) { + return ret; + } + } + + /* Check if the receiver is enabled */ + if ((mmio_read_32(huart->base + USART_CR1) & USART_CR1_RE) == USART_CR1_RE) { + ret = stm32_uart_wait_flag(huart, USART_ISR_REACK); + if (ret != 0) { + return ret; + } + } + + return 0; +} + +/* + * @brief Compute RDR register mask depending on word length. + * @param huart: UART handle. + * @retval Mask value. + */ +static unsigned int stm32_uart_rdr_mask(const struct stm32_uart_init_s *init) +{ + unsigned int mask = 0U; + + switch (init->word_length) { + case STM32_UART_WORDLENGTH_9B: + mask = GENMASK(8, 0); + break; + case STM32_UART_WORDLENGTH_8B: + mask = GENMASK(7, 0); + break; + case STM32_UART_WORDLENGTH_7B: + mask = GENMASK(6, 0); + break; + default: + break; /* not reached */ + } + + if (init->parity != STM32_UART_PARITY_NONE) { + mask >>= 1; + } + + return mask; +} + +/* + * @brief Check interrupt and status errors. + * @retval True if error detected, false otherwise. + */ +static bool stm32_uart_error_detected(struct stm32_uart_handle_s *huart) +{ + return (mmio_read_32(huart->base + USART_ISR) & STM32_UART_ISR_ERRORS) != 0U; +} + +/* + * @brief Clear status errors. + */ +static void stm32_uart_error_clear(struct stm32_uart_handle_s *huart) +{ + mmio_write_32(huart->base + USART_ICR, STM32_UART_ISR_ERRORS); +} + +/* + * @brief Stop the UART. + * @param base: UART base address. + */ +void stm32_uart_stop(uintptr_t base) +{ + mmio_clrbits_32(base + USART_CR1, USART_CR1_UE); +} + +/* + * @brief Initialize UART. + * @param huart: UART handle. + * @param base_addr: base address of UART. + * @param init: UART initialization parameter. + * @retval UART status. + */ +int stm32_uart_init(struct stm32_uart_handle_s *huart, + uintptr_t base_addr, + const struct stm32_uart_init_s *init) +{ + int ret; + int uart_node; + int clk; + void *fdt = NULL; + + if (huart == NULL || init == NULL || base_addr == 0U) { + return -EINVAL; + } + + huart->base = base_addr; + + /* Search UART instance in DT */ + if (fdt_get_address(&fdt) == 0) { + return -FDT_ERR_NOTFOUND; + } + + if (fdt == NULL) { + return -FDT_ERR_NOTFOUND; + } + + uart_node = dt_match_instance_by_compatible(DT_UART_COMPAT, base_addr); + if (uart_node == -FDT_ERR_NOTFOUND) { + return -FDT_ERR_NOTFOUND; + } + + /* Pinctrl initialization */ + if (dt_set_pinctrl_config(uart_node) != 0) { + return -FDT_ERR_BADVALUE; + } + + /* Clock initialization */ + clk = fdt_get_clock_id(uart_node); + if (clk < 0) { + return -FDT_ERR_NOTFOUND; + } + clk_enable(clk); + + /* Disable the peripheral */ + stm32_uart_stop(huart->base); + + /* Computation of UART mask to apply to RDR register */ + huart->rdr_mask = stm32_uart_rdr_mask(init); + + /* Init the peripheral */ + ret = uart_set_config(huart, init); + if (ret != 0) { + return ret; + } + + /* Enable the peripheral */ + mmio_setbits_32(huart->base + USART_CR1, USART_CR1_UE); + + /* TEACK and/or REACK to check */ + return stm32_uart_check_idle(huart); +} + +/* + * @brief Transmit one data in no blocking mode. + * @param huart: UART handle. + * @param c: data to sent. + * @retval UART status. + */ +int stm32_uart_putc(struct stm32_uart_handle_s *huart, int c) +{ + int ret; + + if (huart == NULL) { + return -EINVAL; + } + + ret = stm32_uart_wait_flag(huart, USART_ISR_TXE); + if (ret != 0) { + return ret; + } + + mmio_write_32(huart->base + USART_TDR, c); + if (stm32_uart_error_detected(huart)) { + stm32_uart_error_clear(huart); + return -EFAULT; + } + + return 0; +} + +/* + * @brief Flush TX Transmit fifo + * @param huart: UART handle. + * @retval UART status. + */ +int stm32_uart_flush(struct stm32_uart_handle_s *huart) +{ + int ret; + + if (huart == NULL) { + return -EINVAL; + } + + ret = stm32_uart_wait_flag(huart, USART_ISR_TXE); + if (ret != 0) { + return ret; + } + + return stm32_uart_wait_flag(huart, USART_ISR_TC); +} + +/* + * @brief Receive a data in no blocking mode. + * @retval value if >0 or UART status. + */ +int stm32_uart_getc(struct stm32_uart_handle_s *huart) +{ + uint32_t data; + + if (huart == NULL) { + return -EINVAL; + } + + /* Check if data is available */ + if ((mmio_read_32(huart->base + USART_ISR) & USART_ISR_RXNE) == 0U) { + return -EAGAIN; + } + + data = mmio_read_32(huart->base + USART_RDR) & huart->rdr_mask; + + if (stm32_uart_error_detected(huart)) { + stm32_uart_error_clear(huart); + return -EFAULT; + } + + return (int)data; +} |