From 18cdd100a99e0e69096f221fe75dcbe7b7064a00 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Thu, 11 Nov 2021 17:03:05 +0100 Subject: [PATCH] cpu/sam0_common: uart: set oversampling based on baud rate In Asynchronous Fractional baud rate mode, the baud rate can not be greater than the source frequency divided by the oversampling (8, 16). Currently we are always using 16x oversampling. This makes it impossible to e.g. set a 2 MHz UART baud rate on the 16 MHz `saml10-xpro`. With this change, the oversampling is automatically reduced to 8x which allows us to set 16 MHz / 8 -> 2 MHz baud rate. --- cpu/sam0_common/periph/uart.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/cpu/sam0_common/periph/uart.c b/cpu/sam0_common/periph/uart.c index dc8fb6a224..8e4906dbb1 100644 --- a/cpu/sam0_common/periph/uart.c +++ b/cpu/sam0_common/periph/uart.c @@ -95,14 +95,16 @@ static inline void _reset(SercomUsart *dev) #endif } -static void _set_baud(uart_t uart, uint32_t baudrate) +static void _set_baud(uart_t uart, uint32_t baudrate, uint32_t f_src) { - const uint32_t f_src = sam0_gclk_freq(uart_config[uart].gclk_src); #if IS_ACTIVE(CONFIG_SAM0_UART_BAUD_FRAC) /* Asynchronous Fractional */ - uint32_t baud = (((f_src * 8) / baudrate) / 16); - dev(uart)->BAUD.FRAC.FP = (baud % 8); - dev(uart)->BAUD.FRAC.BAUD = (baud / 8); + /* BAUD + FP / 8 = f_src / (S * f_baud) */ + /* BAUD * 8 + FP = (8 * f_src) / (S * f_baud) */ + /* S * (BAUD + 8 * FP) = (8 * f_src) / f_baud */ + uint32_t baud = (f_src * 8) / baudrate; + dev(uart)->BAUD.FRAC.FP = (baud >> 4) & 0x7; /* baud / 16 */ + dev(uart)->BAUD.FRAC.BAUD = baud >> 7; /* baud / (8 * 16) */ #else /* Asynchronous Arithmetic */ /* BAUD = 2^16 * (2^0 - 2^4 * f_baud / f_src) */ @@ -180,12 +182,27 @@ int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) /* configure clock generator */ sercom_set_gen(dev(uart), uart_config[uart].gclk_src); + uint32_t f_src = sam0_gclk_freq(uart_config[uart].gclk_src); + +#if IS_ACTIVE(CONFIG_SAM0_UART_BAUD_FRAC) + uint32_t sampr; + /* constraint: f_baud ≤ f_src / S */ + if (baudrate * 16 > f_src) { + /* 8x oversampling */ + sampr = SERCOM_USART_CTRLA_SAMPR(0x3); + f_src <<= 1; + } else { + /* 16x oversampling */ + sampr = SERCOM_USART_CTRLA_SAMPR(0x1); + } +#endif + /* set asynchronous mode w/o parity, LSB first, TX and RX pad as specified * by the board in the periph_conf.h, x16 sampling and use internal clock */ dev(uart)->CTRLA.reg = SERCOM_USART_CTRLA_DORD #if IS_ACTIVE(CONFIG_SAM0_UART_BAUD_FRAC) /* enable Asynchronous Fractional mode */ - | SERCOM_USART_CTRLA_SAMPR(0x1) + | sampr #endif | SERCOM_USART_CTRLA_TXPO(uart_config[uart].tx_pad) | SERCOM_USART_CTRLA_RXPO(uart_config[uart].rx_pad) @@ -197,7 +214,7 @@ int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) } /* calculate and set baudrate */ - _set_baud(uart, baudrate); + _set_baud(uart, baudrate, f_src); /* enable transmitter, and configure 8N1 mode */ if (uart_config[uart].tx_pin != GPIO_UNDEF) {