From ff9b19ad7c43ec70a4dacb1f49ce851508ba0a81 Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Fri, 30 Aug 2024 09:29:00 +0200 Subject: [PATCH] dev/serial: Rework Zynq UART baud calculation Calculate the best approximation for the desired baud and return the error. --- bsps/include/dev/serial/zynq-uart-regs.h | 29 ++++- bsps/shared/dev/serial/zynq-uart-polled.c | 129 +++++++++++----------- bsps/shared/dev/serial/zynq-uart.c | 38 ++++--- 3 files changed, 112 insertions(+), 84 deletions(-) diff --git a/bsps/include/dev/serial/zynq-uart-regs.h b/bsps/include/dev/serial/zynq-uart-regs.h index e5810749ab..1c2d3886cf 100644 --- a/bsps/include/dev/serial/zynq-uart-regs.h +++ b/bsps/include/dev/serial/zynq-uart-regs.h @@ -176,11 +176,30 @@ void zynq_uart_write_char_polled(volatile zynq_uart *regs, char c); */ void zynq_uart_reset_tx_flush(volatile zynq_uart *regs); -int zynq_cal_baud_rate( - uint32_t baudrate, - uint32_t* brgr, - uint32_t* bauddiv, - uint32_t modereg +/** + * @brief Calculates the clock and baud divisor of the best approximation of + * the desired baud. + * + * The function tries to yield a sample set around 16 per RX-bit. + * + * @param desired_baud is the desired baud for an Zynq UART device. + * + * @param mode_clks is the value of the CLKS bit of the Zynq UART mode register. + * + * @param cd_ptr[out] is a reference to an uint32_t object. The function + * stores the calculated clock divisor to this object. + * + * @param bdiv_ptr[out] is a reference to an uint32_t object. The function + * stores the calculated baud divisor to this object. + * + * @return Returns the absolute error of the calculated baud to the desired + * baud in Hz. + */ +uint32_t zynq_uart_calculate_baud( + uint32_t desired_baud, + uint32_t mode_clks, + uint32_t *cd_ptr, + uint32_t *bdiv_ptr ); /** @} */ diff --git a/bsps/shared/dev/serial/zynq-uart-polled.c b/bsps/shared/dev/serial/zynq-uart-polled.c index 979f33282a..3fa83e7622 100644 --- a/bsps/shared/dev/serial/zynq-uart-polled.c +++ b/bsps/shared/dev/serial/zynq-uart-polled.c @@ -39,6 +39,7 @@ #include #include +#include /* * Make weak and let the user override. @@ -50,94 +51,94 @@ uint32_t zynq_uart_input_clock(void) return ZYNQ_CLOCK_UART; } -int zynq_cal_baud_rate(uint32_t baudrate, - uint32_t* brgr, - uint32_t* bauddiv, - uint32_t modereg) +static uint32_t zync_uart_baud_error( + uint32_t selected_clock, + uint32_t desired_baud, + uint32_t cd, + uint32_t bdiv_plus_one +) { - uint32_t brgr_value; /* Calculated value for baud rate generator */ - uint32_t calcbaudrate; /* Calculated baud rate */ - uint32_t bauderror; /* Diff between calculated and requested baud rate */ - uint32_t best_error = 0xFFFFFFFF; - uint32_t percenterror; - uint32_t bdiv; - uint32_t inputclk = zynq_uart_input_clock(); + uint32_t actual_baud = selected_clock / ( cd * bdiv_plus_one ); - /* - * Make sure the baud rate is not impossilby large. - * Fastest possible baud rate is Input Clock / 2. - */ - if ((baudrate * 2) > inputclk) { - return -1; - } - /* - * Check whether the input clock is divided by 8 - */ - if(modereg & ZYNQ_UART_MODE_CLKS) { - inputclk = inputclk / 8; + if ( actual_baud > desired_baud ) { + return actual_baud - desired_baud; } - /* - * Determine the Baud divider. It can be 4to 254. - * Loop through all possible combinations - */ - for (bdiv = 4; bdiv < 255; bdiv++) { + return desired_baud - actual_baud; +} - /* - * Calculate the value for BRGR register - */ - brgr_value = inputclk / (baudrate * (bdiv + 1)); +uint32_t zynq_uart_calculate_baud( + uint32_t desired_baud, + uint32_t mode_clks, + uint32_t *cd_ptr, + uint32_t *bdiv_ptr +) +{ + uint32_t best_error = UINT32_MAX; + uint32_t best_cd; + uint32_t best_bdiv_plus_one; + uint32_t bdiv_plus_one; + uint32_t selected_clock; - /* - * Calculate the baud rate from the BRGR value - */ - calcbaudrate = inputclk/ (brgr_value * (bdiv + 1)); + _Assert((mode_clks & ~ZYNQ_UART_MODE_CLKS) == 0); + selected_clock = zynq_uart_input_clock() / (1U << (3 * mode_clks)); - /* - * Avoid unsigned integer underflow - */ - if (baudrate > calcbaudrate) { - bauderror = baudrate - calcbaudrate; - } - else { - bauderror = calcbaudrate - baudrate; + for (bdiv_plus_one = 5; bdiv_plus_one <= 256; ++bdiv_plus_one) { + uint32_t cd = ( selected_clock / bdiv_plus_one ) / desired_baud; + uint32_t error; + + if (cd == 0 ) { + cd = 1; + } else if ( cd > 65535 ) { + cd = 65535; } + error = zync_uart_baud_error( + selected_clock, + desired_baud, + cd, + bdiv_plus_one + ); + /* - * Find the calculated baud rate closest to requested baud rate. + * The procedure to detect a start bit uses three samples in the middle of + * an RX-bit. If the sample set is too small, there may be a sample in + * another bit in case the baud setting is not accurate. Most noise is in + * the form of small peaks, if the sample rate is too high, then noise may + * get detected as a bit. + * + * Prefer an sample set of around 16 per RX-bit. */ - if (best_error > bauderror) { - *brgr = brgr_value; - *bauddiv = bdiv; - best_error = bauderror; + if (error < best_error || (bdiv_plus_one <= 20 && error <= best_error)) { + best_error = error; + best_cd = cd; + best_bdiv_plus_one = bdiv_plus_one; } } - /* - * Make sure the best error is not too large. - */ - percenterror = (best_error * 100) / baudrate; -#define XUARTPS_MAX_BAUD_ERROR_RATE 3 /* max % error allowed */ - if (XUARTPS_MAX_BAUD_ERROR_RATE < percenterror) { - return -1; - } - - return 0; + *cd_ptr = best_cd; + *bdiv_ptr = best_bdiv_plus_one - 1; + return best_error; } void zynq_uart_initialize(volatile zynq_uart *regs) { - uint32_t brgr = 0x3e; - uint32_t bauddiv = 0x6; uint32_t mode_clks = regs->mode & ZYNQ_UART_MODE_CLKS; + uint32_t cd; + uint32_t bdiv; zynq_uart_reset_tx_flush(regs); - zynq_cal_baud_rate(ZYNQ_UART_DEFAULT_BAUD, &brgr, &bauddiv, mode_clks); + (void) zynq_uart_calculate_baud( + ZYNQ_UART_DEFAULT_BAUD, + mode_clks, + &cd, + &bdiv + ); regs->control = 0; regs->control = ZYNQ_UART_CONTROL_RXDIS | ZYNQ_UART_CONTROL_TXDIS; - regs->baud_rate_gen = ZYNQ_UART_BAUD_RATE_GEN_CD(brgr); - regs->baud_rate_div = ZYNQ_UART_BAUD_RATE_DIV_BDIV(bauddiv); + regs->baud_rate_gen = ZYNQ_UART_BAUD_RATE_GEN_CD(cd); + regs->baud_rate_div = ZYNQ_UART_BAUD_RATE_DIV_BDIV(bdiv); /* A Tx/Rx logic reset must be issued after baud rate manipulation */ regs->control = ZYNQ_UART_CONTROL_RXDIS | ZYNQ_UART_CONTROL_TXDIS; regs->control = ZYNQ_UART_CONTROL_RXRES | ZYNQ_UART_CONTROL_TXRES; diff --git a/bsps/shared/dev/serial/zynq-uart.c b/bsps/shared/dev/serial/zynq-uart.c index 0489288271..e2ef6f067d 100644 --- a/bsps/shared/dev/serial/zynq-uart.c +++ b/bsps/shared/dev/serial/zynq-uart.c @@ -157,27 +157,35 @@ static bool zynq_uart_set_attributes( { zynq_uart_context *ctx = (zynq_uart_context *) context; volatile zynq_uart *regs = ctx->regs; - int32_t baud; - uint32_t brgr = 0; - uint32_t bauddiv = 0; - uint32_t mode = 0; - int rc; + uint32_t desired_baud; + uint32_t cd; + uint32_t bdiv; + uint32_t mode; /* - * Determine the baud rate + * Determine the baud */ - baud = rtems_termios_baud_to_number(term->c_ospeed); + desired_baud = rtems_termios_baud_to_number(term->c_ospeed); + mode = regs->mode & ZYNQ_UART_MODE_CLKS; - if (baud > 0) { - rc = zynq_cal_baud_rate(baud, &brgr, &bauddiv, regs->mode); - if (rc != 0) - return rc; + if (desired_baud > 0) { + uint32_t error = zynq_uart_calculate_baud(desired_baud, mode, &cd, &bdiv); + uint32_t margin; + + if ( desired_baud >= 100 ) { + margin = 3 * (desired_baud / 100); + } else { + margin = 1; + } + + if (error > margin) { + return false; + } } /* * Configure the mode register */ - mode = regs->mode & ZYNQ_UART_MODE_CLKS; mode |= ZYNQ_UART_MODE_CHMODE(ZYNQ_UART_MODE_CHMODE_NORMAL); /* @@ -233,9 +241,9 @@ static bool zynq_uart_set_attributes( regs->control = ZYNQ_UART_CONTROL_RXDIS | ZYNQ_UART_CONTROL_TXDIS; /* Ignore baud rate of B0. There are no modem control lines to de-assert */ - if (baud > 0) { - regs->baud_rate_gen = ZYNQ_UART_BAUD_RATE_GEN_CD(brgr); - regs->baud_rate_div = ZYNQ_UART_BAUD_RATE_DIV_BDIV(bauddiv); + if (desired_baud > 0) { + regs->baud_rate_gen = ZYNQ_UART_BAUD_RATE_GEN_CD(cd); + regs->baud_rate_div = ZYNQ_UART_BAUD_RATE_DIV_BDIV(bdiv); } regs->control = ZYNQ_UART_CONTROL_RXRES | ZYNQ_UART_CONTROL_TXRES; regs->mode = mode;