From 9bec9ccade797c3cd5ea318eddcfc740a7f8e0be Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Fri, 8 Sep 2023 18:23:10 +0800 Subject: [PATCH] feat(hal): add hal utils for clock divider calculation --- components/hal/CMakeLists.txt | 1 + components/hal/hal_utils.c | 118 +++++++++++++++++++++++++ components/hal/include/hal/hal_utils.h | 65 ++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 components/hal/hal_utils.c create mode 100644 components/hal/include/hal/hal_utils.h diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index e0bab96d2b..7019d22804 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -9,6 +9,7 @@ endif() set(srcs "mpu_hal.c" "efuse_hal.c" + "hal_utils.c" "${target}/efuse_hal.c") diff --git a/components/hal/hal_utils.c b/components/hal/hal_utils.c new file mode 100644 index 0000000000..1d9847c9ae --- /dev/null +++ b/components/hal/hal_utils.c @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "hal/hal_utils.h" + +/** + * @brief helper function, calculate the Greatest Common Divisor + * @note gcd(a, b) = gcd(b, a % b) + * @param a bigger value + * @param b smaller value + * @return result of gcd(a, b) + */ +__attribute__((always_inline)) +static inline uint32_t _gcd(uint32_t a, uint32_t b) +{ + uint32_t c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + return b; +} + +__attribute__((always_inline)) +static inline uint32_t _sub_abs(uint32_t a, uint32_t b) +{ + return a > b ? a - b : b - a; +} + +uint32_t hal_utils_calc_clk_div_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div) +{ + uint32_t div_denom = 1; + uint32_t div_numer = 0; + uint32_t div_integ = clk_info->src_freq_hz / clk_info->exp_freq_hz; + uint32_t freq_error = clk_info->src_freq_hz % clk_info->exp_freq_hz; + + // If the expect frequency is too high or too low to satisfy the integral division range, failed and return 0 + if (div_integ < clk_info->min_integ || div_integ > clk_info->max_integ) { + return 0; + } + + // fractional divider + if (freq_error) { + // Calculate the Greatest Common Divisor, time complexity O(log n) + uint32_t gcd = _gcd(clk_info->exp_freq_hz, freq_error); + // divide by the Greatest Common Divisor to get the accurate fraction before normalization + div_denom = clk_info->exp_freq_hz / gcd; + div_numer = freq_error / gcd; + // normalize div_denom and div_numer + uint32_t d = div_denom / clk_info->max_fract + 1; + // divide by the normalization coefficient to get the denominator and numerator within range of clk_info->max_fract + div_denom /= d; + div_numer /= d; + } + + // Assign result + clk_div->integ = div_integ; + clk_div->denom = div_denom; + clk_div->numer = div_numer; + + // Return the actual frequency + if (div_numer) { + uint32_t temp = div_integ * div_denom + div_numer; + return (uint32_t)(((uint64_t)clk_info->src_freq_hz * div_denom + temp / 2) / temp); + } + return clk_info->src_freq_hz / div_integ; +} + +uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div) +{ + uint32_t div_denom = 1; + uint32_t div_numer = 0; + uint32_t div_integ = clk_info->src_freq_hz / clk_info->exp_freq_hz; + uint32_t freq_error = clk_info->src_freq_hz % clk_info->exp_freq_hz; + + // If the expect frequency is too high to satisfy the minimum integral division, failed and return 0 + if (div_integ < clk_info->min_integ) { + return 0; + } + + if (freq_error) { + // Carry bit if the decimal is greater than 1.0 - 1.0 / (PARLIO_LL_CLK_DIVIDER_MAX * 2) + if (freq_error < clk_info->exp_freq_hz - clk_info->exp_freq_hz / (clk_info->max_fract * 2)) { + // Search the closest fraction, time complexity O(n) + for (uint32_t sub = 0, a = 2, b = 0, min = UINT32_MAX; min && a <= clk_info->max_fract; a++) { + b = (a * freq_error + clk_info->exp_freq_hz / 2) / clk_info->exp_freq_hz; + sub = _sub_abs(clk_info->exp_freq_hz * b, freq_error * a); + if (sub < min) { + div_denom = a; + div_numer = b; + min = sub; + } + } + } else { + div_integ++; + } + } + // If the expect frequency is too low to satisfy the maximum integral division, failed and return 0 + if (div_integ > clk_info->max_integ) { + return 0; + } + + // Assign result + clk_div->integ = div_integ; + clk_div->denom = div_denom; + clk_div->numer = div_numer; + + // Return the actual frequency + if (div_numer) { + uint32_t temp = div_integ * div_denom + div_numer; + return (uint32_t)(((uint64_t)clk_info->src_freq_hz * div_denom + temp / 2) / temp); + } + return clk_info->src_freq_hz / div_integ; +} diff --git a/components/hal/include/hal/hal_utils.h b/components/hal/include/hal/hal_utils.h new file mode 100644 index 0000000000..36ead6fbf4 --- /dev/null +++ b/components/hal/include/hal/hal_utils.h @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Clock infomation + * + */ +typedef struct { + uint32_t src_freq_hz; /*!< Source clock frequency, unit: Hz */ + uint32_t exp_freq_hz; /*!< Expected output clock frequency, unit: Hz */ + uint32_t max_integ; /*!< The max value of the integral part */ + uint32_t min_integ; /*!< The min value of the integral part, integer range: [min_integ, max_integ) */ + uint32_t max_fract; /*!< The max value of the denominator and numerator, numerator range: [0, max_fract), denominator range: [1, max_fract) */ +} hal_utils_clk_info_t; + +/** + * @brief Members of clock division + * + */ +typedef struct { + uint32_t integ; /*!< Integer part of division */ + uint32_t denom; /*!< Denominator part of division */ + uint32_t numer; /*!< Numerator part of division */ +} hal_utils_clk_div_t; + +/** + * @brief Calculate the clock division + * @note Speed first algorithm, Time complexity O(log n). + * About 8~10 times faster than the accurate algorithm + * + * @param[in] clk_info The clock infomation + * @param[out] clk_div The clock division + * @return + * - 0: Failed to get the result because the division is out of range + * - others: The real output clock frequency + */ +uint32_t hal_utils_calc_clk_div_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div); + +/** + * @brief Calculate the clock division + * @note Accuracy first algorithm, Time complexity O(n). + * About 1~hundreds times more accurate than the fast algorithm + * + * @param[in] clk_info The clock infomation + * @param[out] clk_div The clock division + * @return + * - 0: Failed to get the result because the division is out of range + * - others: The real output clock frequency + */ +uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div); + +#ifdef __cplusplus +} +#endif