diff options
Diffstat (limited to 'drivers/tty/serial/stm32-usart.c')
-rw-r--r-- | drivers/tty/serial/stm32-usart.c | 71 |
1 files changed, 50 insertions, 21 deletions
diff --git a/drivers/tty/serial/stm32-usart.c b/drivers/tty/serial/stm32-usart.c index 794b77512740..19a45e1b7102 100644 --- a/drivers/tty/serial/stm32-usart.c +++ b/drivers/tty/serial/stm32-usart.c @@ -9,6 +9,7 @@ * Inspired by st-asc.c from STMicroelectronics (c) */ +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/console.h> #include <linux/delay.h> @@ -50,6 +51,7 @@ static struct stm32_usart_info __maybe_unused stm32f4_info = { .rtor = UNDEF_REG, .rqr = UNDEF_REG, .icr = UNDEF_REG, + .presc = UNDEF_REG, }, .cfg = { .uart_enable_bit = 13, @@ -71,6 +73,7 @@ static struct stm32_usart_info __maybe_unused stm32f7_info = { .icr = 0x20, .rdr = 0x24, .tdr = 0x28, + .presc = UNDEF_REG, }, .cfg = { .uart_enable_bit = 0, @@ -93,6 +96,7 @@ static struct stm32_usart_info __maybe_unused stm32h7_info = { .icr = 0x20, .rdr = 0x24, .tdr = 0x28, + .presc = 0x2c, }, .cfg = { .uart_enable_bit = 0, @@ -1145,6 +1149,8 @@ static void stm32_usart_shutdown(struct uart_port *port) free_irq(port->irq, port); } +static const unsigned int stm32_usart_presc_val[] = {1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256}; + static void stm32_usart_set_termios(struct uart_port *port, struct ktermios *termios, const struct ktermios *old) @@ -1153,17 +1159,19 @@ static void stm32_usart_set_termios(struct uart_port *port, const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; const struct stm32_usart_config *cfg = &stm32_port->info->cfg; struct serial_rs485 *rs485conf = &port->rs485; - unsigned int baud, bits; + unsigned int baud, bits, uart_clk, uart_clk_pres; u32 usartdiv, mantissa, fraction, oversampling; tcflag_t cflag = termios->c_cflag; - u32 cr1, cr2, cr3, isr; + u32 cr1, cr2, cr3, isr, brr, presc; unsigned long flags; int ret; if (!stm32_port->hw_flow_control) cflag &= ~CRTSCTS; - baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 8); + uart_clk = clk_get_rate(stm32_port->clk); + + baud = uart_get_baud_rate(port, termios, old, 0, uart_clk / 8); uart_port_lock_irqsave(port, &flags); @@ -1265,27 +1273,48 @@ static void stm32_usart_set_termios(struct uart_port *port, cr3 |= USART_CR3_CTSE | USART_CR3_RTSE; } - usartdiv = DIV_ROUND_CLOSEST(port->uartclk, baud); + for (presc = 0; presc <= USART_PRESC_MAX; presc++) { + uart_clk_pres = DIV_ROUND_CLOSEST(uart_clk, stm32_usart_presc_val[presc]); + usartdiv = DIV_ROUND_CLOSEST(uart_clk_pres, baud); - /* - * The USART supports 16 or 8 times oversampling. - * By default we prefer 16 times oversampling, so that the receiver - * has a better tolerance to clock deviations. - * 8 times oversampling is only used to achieve higher speeds. - */ - if (usartdiv < 16) { - oversampling = 8; - cr1 |= USART_CR1_OVER8; - stm32_usart_set_bits(port, ofs->cr1, USART_CR1_OVER8); - } else { - oversampling = 16; - cr1 &= ~USART_CR1_OVER8; - stm32_usart_clr_bits(port, ofs->cr1, USART_CR1_OVER8); + /* + * The USART supports 16 or 8 times oversampling. + * By default we prefer 16 times oversampling, so that the receiver + * has a better tolerance to clock deviations. + * 8 times oversampling is only used to achieve higher speeds. + */ + if (usartdiv < 16) { + oversampling = 8; + cr1 |= USART_CR1_OVER8; + stm32_usart_set_bits(port, ofs->cr1, USART_CR1_OVER8); + } else { + oversampling = 16; + cr1 &= ~USART_CR1_OVER8; + stm32_usart_clr_bits(port, ofs->cr1, USART_CR1_OVER8); + } + + mantissa = (usartdiv / oversampling) << USART_BRR_DIV_M_SHIFT; + fraction = usartdiv % oversampling; + brr = mantissa | fraction; + + if (FIELD_FIT(USART_BRR_MASK, brr)) { + if (ofs->presc != UNDEF_REG) { + port->uartclk = uart_clk_pres; + writel_relaxed(presc, port->membase + ofs->presc); + } else if (presc) { + /* We need a prescaler but we don't have it (STM32F4, STM32F7) */ + dev_err(port->dev, + "unable to set baudrate, input clock is too high"); + } + break; + } else if (presc == USART_PRESC_MAX) { + /* Even with prescaler and brr at max value we can't set baudrate */ + dev_err(port->dev, "unable to set baudrate, input clock is too high"); + break; + } } - mantissa = (usartdiv / oversampling) << USART_BRR_DIV_M_SHIFT; - fraction = usartdiv % oversampling; - writel_relaxed(mantissa | fraction, port->membase + ofs->brr); + writel_relaxed(brr, port->membase + ofs->brr); uart_update_timeout(port, cflag, baud); |