소스 검색

refactor(hal): use hal utils to calculate clock division

laokaiyao 2 년 전
부모
커밋
dd4072a80c

+ 1 - 1
components/driver/dac/esp32/dac_dma.c

@@ -97,7 +97,7 @@ static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll)
     ESP_LOGD(TAG, "[sclk] %"PRIu32" [mclk] %"PRIu32" [mclk_div] %"PRIu32" [bclk] %"PRIu32" [bclk_div] %"PRIu32, sclk, mclk, mclk_div, bclk, bclk_div);
 
     i2s_ll_tx_clk_set_src(s_ddp->periph_dev, is_apll ? I2S_CLK_SRC_APLL : I2S_CLK_SRC_DEFAULT);
-    i2s_ll_mclk_div_t mclk_div_coeff = {};
+    hal_utils_clk_div_t mclk_div_coeff = {};
     i2s_hal_calc_mclk_precise_division(sclk, mclk, &mclk_div_coeff);
     i2s_ll_tx_set_mclk(s_ddp->periph_dev, &mclk_div_coeff);
     i2s_ll_tx_set_bck_div_num(s_ddp->periph_dev, bclk_div);

+ 13 - 27
components/driver/dac/esp32s2/dac_dma.c

@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD
  *
  * SPDX-License-Identifier: Apache-2.0
  */
@@ -18,6 +18,7 @@
 #include "hal/spi_ll.h"
 #include "hal/dac_ll.h"
 #include "hal/adc_ll.h"
+#include "hal/hal_utils.h"
 #include "soc/lldesc.h"
 #include "soc/soc.h"
 #include "soc/soc_caps.h"
@@ -99,36 +100,21 @@ static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll){
     }
     ESP_RETURN_ON_FALSE(interval * 256 > total_div, ESP_ERR_INVALID_ARG, TAG, "the DAC frequency is too small");
 
-    /* Step 3: Calculate the coefficients of ADC digital controller divider*/
-    uint32_t fsclk = interval * freq_hz; /* The clock frequency that produced by ADC controller divider */
-    uint32_t clk_div = digi_ctrl_freq / fsclk;
-    uint32_t mod = digi_ctrl_freq % fsclk;
-    uint32_t a = 0;
-    uint32_t b = 1;
-    if (mod == 0) {
-        goto finish;
-    }
-    uint32_t min_diff = mod + 1;
-    for (uint32_t tmp_b = 1; tmp_b < 64; tmp_b++) {
-        uint32_t tmp_a = (uint32_t)(((mod * b) / (float)fsclk) + 0.5);
-        uint32_t diff = (uint32_t)abs((int)(mod * tmp_b) - (int)(fsclk * tmp_a));
-        if (diff == 0) {
-            a = tmp_a;
-            b = tmp_b;
-            goto finish;
-        }
-        if (diff < min_diff) {
-            min_diff = diff;
-            a = tmp_a;
-            b = tmp_b;
-        }
-    }
+    /* Step 3: Calculate the coefficients of ADC digital controller divider */
+    hal_utils_clk_info_t adc_clk_info = {
+        .src_freq_hz = digi_ctrl_freq / interval,
+        .exp_freq_hz = freq_hz,
+        .max_integ = 257,
+        .min_integ = 1,
+        .max_fract = 64,
+    };
+    hal_utils_clk_div_t adc_clk_div = {};
+    hal_utils_calc_clk_div_frac_accurate(&adc_clk_info, &adc_clk_div);
 
-finish:
     /* Step 4: Set the clock coefficients */
     dac_ll_digi_clk_inv(true);
     dac_ll_digi_set_trigger_interval(interval); // secondary clock division
-    adc_ll_digi_controller_clk_div(clk_div - 1, b, a);
+    adc_ll_digi_controller_clk_div(adc_clk_div.integer - 1, adc_clk_div.denominator, adc_clk_div.numerator);
     adc_ll_digi_clk_sel(is_apll ? ADC_DIGI_CLK_SRC_APLL : ADC_DIGI_CLK_SRC_DEFAULT);
     return ESP_OK;
 }

+ 1 - 0
components/esp_lcd/linker.lf

@@ -9,3 +9,4 @@ archive: libhal.a
 entries:
     if LCD_RGB_ISR_IRAM_SAFE = y:
         lcd_hal: lcd_hal_cal_pclk_freq (noflash)
+        hal_utils: hal_utils_calc_clk_div_frac_fast (noflash)

+ 7 - 0
components/hal/.build-test-rules.yml

@@ -0,0 +1,7 @@
+components/hal/test_apps/hal_utils:
+  enable:
+    - if: IDF_TARGET == "linux"
+  disable:
+    - if: IDF_TARGET == "linux"
+      temporary: true
+      reason: env not ready

+ 1 - 1
components/hal/adc_hal.c

@@ -180,7 +180,7 @@ static void adc_hal_digi_sample_freq_config(adc_hal_dma_ctx_t *hal, adc_continuo
     uint32_t bclk_div = 16;
     uint32_t bclk = sample_freq_hz * 2;
     uint32_t mclk = bclk * bclk_div;
-    i2s_ll_mclk_div_t mclk_div = {};
+    hal_utils_clk_div_t mclk_div = {};
     i2s_hal_calc_mclk_precise_division(I2S_BASE_CLK, mclk, &mclk_div);
     i2s_ll_rx_set_mclk(hal->dev, &mclk_div);
     i2s_ll_rx_set_bck_div_num(hal->dev, bclk_div);

+ 7 - 15
components/hal/esp32/include/hal/i2s_ll.h

@@ -19,6 +19,8 @@
 #include "soc/i2s_periph.h"
 #include "soc/i2s_struct.h"
 #include "hal/i2s_types.h"
+#include "hal/hal_utils.h"
+
 
 #ifdef __cplusplus
 extern "C" {
@@ -30,8 +32,8 @@ extern "C" {
 #define I2S_LL_AD_BCK_FACTOR           (2)
 #define I2S_LL_PDM_BCK_FACTOR          (64)
 
-#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH  (6)
-#define I2S_LL_MCLK_DIVIDER_MAX        ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1)
+#define I2S_LL_CLK_FRAC_DIV_N_MAX  256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width
+#define I2S_LL_CLK_FRAC_DIV_AB_MAX 64  // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 6 bit-width
 
 #define I2S_LL_BCK_MAX_PRESCALE   (64)
 
@@ -48,16 +50,6 @@ extern "C" {
 #define I2S_LL_PLL_F160M_CLK_FREQ   (160 * 1000000) // PLL_F160M_CLK: 160MHz
 #define I2S_LL_DEFAULT_PLL_CLK_FREQ     I2S_LL_PLL_F160M_CLK_FREQ    // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT
 
-/**
- * @brief I2S clock configuration structure
- * @note Fmclk = Fsclk /(integ+numer/denom)
- */
-typedef struct {
-    uint16_t integ;     // Integer part of I2S module clock divider
-    uint16_t denom;     // Denominator part of I2S module clock divider
-    uint16_t numer;     // Numerator part of I2S module clock divider
-} i2s_ll_mclk_div_t;
-
 /**
  * @brief Enable DMA descriptor owner check
  *
@@ -312,14 +304,14 @@ static inline void i2s_ll_set_raw_mclk_div(i2s_dev_t *hw, uint32_t mclk_div, uin
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
      * otherwise the clock division might be inaccurate.
      * the general idea is to set a value that unlike to calculate from the regular decimal */
     i2s_ll_set_raw_mclk_div(hw, 7, 47, 3);
-    i2s_ll_set_raw_mclk_div(hw, mclk_div->integ, mclk_div->denom, mclk_div->numer);
+    i2s_ll_set_raw_mclk_div(hw, mclk_div->integer, mclk_div->denominator, mclk_div->numerator);
 }
 
 /**
@@ -340,7 +332,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val)
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     // TX and RX channel on ESP32 shares a same mclk
     i2s_ll_tx_set_mclk(hw, mclk_div);

+ 17 - 26
components/hal/esp32c3/include/hal/i2s_ll.h

@@ -18,6 +18,7 @@
 #include "soc/i2s_periph.h"
 #include "soc/i2s_struct.h"
 #include "hal/i2s_types.h"
+#include "hal/hal_utils.h"
 
 
 #ifdef __cplusplus
@@ -29,22 +30,12 @@ extern "C" {
 #define I2S_LL_TDM_CH_MASK             (0xffff)
 #define I2S_LL_PDM_BCK_FACTOR          (64)
 
-#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH  (9)
-#define I2S_LL_MCLK_DIVIDER_MAX        ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1)
+#define I2S_LL_CLK_FRAC_DIV_N_MAX  256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width
+#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width
 
 #define I2S_LL_PLL_F160M_CLK_FREQ      (160 * 1000000) // PLL_F160M_CLK: 160MHz
 #define I2S_LL_DEFAULT_PLL_CLK_FREQ     I2S_LL_PLL_F160M_CLK_FREQ    // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT
 
-/**
- * @brief I2S clock configuration structure
- * @note Fmclk = Fsclk /(integ+numer/denom)
- */
-typedef struct {
-    uint16_t integ;     // Integer part of I2S module clock divider
-    uint16_t denom;     // Denominator part of I2S module clock divider
-    uint16_t numer;     // Numerator part of I2S module clock divider
-} i2s_ll_mclk_div_t;
-
 /**
  * @brief I2S module general init, enable I2S clock.
  *
@@ -304,7 +295,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -317,13 +308,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 /**
@@ -344,7 +335,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val)
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -357,13 +348,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 /**

+ 17 - 26
components/hal/esp32c6/include/hal/i2s_ll.h

@@ -19,6 +19,7 @@
 #include "soc/i2s_struct.h"
 #include "soc/pcr_struct.h"
 #include "hal/i2s_types.h"
+#include "hal/hal_utils.h"
 
 
 #ifdef __cplusplus
@@ -30,22 +31,12 @@ extern "C" {
 #define I2S_LL_TDM_CH_MASK             (0xffff)
 #define I2S_LL_PDM_BCK_FACTOR          (64)
 
-#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH  (9)
-#define I2S_LL_MCLK_DIVIDER_MAX        ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1)
+#define I2S_LL_CLK_FRAC_DIV_N_MAX  256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width
+#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width
 
 #define I2S_LL_PLL_F160M_CLK_FREQ      (160 * 1000000) // PLL_F160M_CLK: 160MHz
 #define I2S_LL_DEFAULT_PLL_CLK_FREQ     I2S_LL_PLL_F160M_CLK_FREQ    // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT
 
-/**
- * @brief I2S clock configuration structure
- * @note Fmclk = Fsclk /(integ+numer/denom)
- */
-typedef struct {
-    uint16_t integ;     // Integer part of I2S module clock divider
-    uint16_t denom;     // Denominator part of I2S module clock divider
-    uint16_t numer;     // Numerator part of I2S module clock divider
-} i2s_ll_mclk_div_t;
-
 /**
  * @brief I2S module general init, enable I2S clock.
  *
@@ -313,7 +304,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -326,13 +317,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 /**
@@ -353,7 +344,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val)
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -366,13 +357,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 /**

+ 17 - 26
components/hal/esp32h2/include/hal/i2s_ll.h

@@ -19,6 +19,7 @@
 #include "soc/i2s_struct.h"
 #include "soc/pcr_struct.h"
 #include "hal/i2s_types.h"
+#include "hal/hal_utils.h"
 
 
 #ifdef __cplusplus
@@ -30,23 +31,13 @@ extern "C" {
 #define I2S_LL_TDM_CH_MASK             (0xffff)
 #define I2S_LL_PDM_BCK_FACTOR          (64)
 
-#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH  (9)
-#define I2S_LL_MCLK_DIVIDER_MAX        ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1)
+#define I2S_LL_CLK_FRAC_DIV_N_MAX  256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width
+#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width
 
 #define I2S_LL_PLL_F96M_CLK_FREQ      (96 * 1000000) // PLL_F96M_CLK: 96MHz
 #define I2S_LL_PLL_F64M_CLK_FREQ      (64 * 1000000) // PLL_F64M_CLK: 64MHz
 #define I2S_LL_DEFAULT_PLL_CLK_FREQ   I2S_LL_PLL_F96M_CLK_FREQ  // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT
 
-/**
- * @brief I2S clock configuration structure
- * @note Fmclk = Fsclk /(integ+numer/denom)
- */
-typedef struct {
-    uint16_t integ;     // Integer part of I2S module clock divider
-    uint16_t denom;     // Denominator part of I2S module clock divider
-    uint16_t numer;     // Numerator part of I2S module clock divider
-} i2s_ll_mclk_div_t;
-
 /**
  * @brief I2S module general init, enable I2S clock.
  *
@@ -320,7 +311,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -333,13 +324,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 /**
@@ -360,7 +351,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val)
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -373,13 +364,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 /**

+ 7 - 15
components/hal/esp32s2/include/hal/i2s_ll.h

@@ -19,6 +19,7 @@
 #include "soc/i2s_periph.h"
 #include "soc/i2s_struct.h"
 #include "hal/i2s_types.h"
+#include "hal/hal_utils.h"
 
 
 #ifdef __cplusplus
@@ -30,8 +31,9 @@ extern "C" {
 
 #define I2S_LL_BCK_MAX_PRESCALE  (64)
 
-#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH  (6)
-#define I2S_LL_MCLK_DIVIDER_MAX        ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1)
+#define I2S_LL_CLK_FRAC_DIV_N_MAX  256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width
+#define I2S_LL_CLK_FRAC_DIV_AB_MAX 64  // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 6 bit-width
+
 
 #define I2S_LL_EVENT_RX_EOF         BIT(9)
 #define I2S_LL_EVENT_TX_EOF         BIT(12)
@@ -45,16 +47,6 @@ extern "C" {
 #define I2S_LL_PLL_F160M_CLK_FREQ   (160 * 1000000) // PLL_F160M_CLK: 160MHz
 #define I2S_LL_DEFAULT_PLL_CLK_FREQ     I2S_LL_PLL_F160M_CLK_FREQ    // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT
 
-/**
- * @brief I2S clock configuration structure
- * @note Fmclk = Fsclk /(integ+numer/denom)
- */
-typedef struct {
-    uint16_t integ;     // Integer part of I2S module clock divider
-    uint16_t denom;     // Denominator part of I2S module clock divider
-    uint16_t numer;     // Numerator part of I2S module clock divider
-} i2s_ll_mclk_div_t;
-
 /**
  * @brief Enable DMA descriptor owner check
  *
@@ -303,14 +295,14 @@ static inline void i2s_ll_set_raw_mclk_div(i2s_dev_t *hw, uint32_t mclk_div, uin
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
      * otherwise the clock division might be inaccurate.
      * the general idea is to set a value that unlike to calculate from the regular decimal */
     i2s_ll_set_raw_mclk_div(hw, 7, 47, 3);
-    i2s_ll_set_raw_mclk_div(hw, mclk_div->integ, mclk_div->denom, mclk_div->numer);
+    i2s_ll_set_raw_mclk_div(hw, mclk_div->integer, mclk_div->denominator, mclk_div->numerator);
 }
 
 /**
@@ -331,7 +323,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val)
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     // TX and RX channel on ESP32 shares a same mclk
     i2s_ll_tx_set_mclk(hw, mclk_div);

+ 17 - 26
components/hal/esp32s3/include/hal/i2s_ll.h

@@ -18,6 +18,7 @@
 #include "soc/i2s_periph.h"
 #include "soc/i2s_struct.h"
 #include "hal/i2s_types.h"
+#include "hal/hal_utils.h"
 
 
 #ifdef __cplusplus
@@ -30,22 +31,12 @@ extern "C" {
 #define I2S_LL_TDM_CH_MASK (0xffff)
 #define I2S_LL_PDM_BCK_FACTOR          (64)
 
-#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH  (9)
-#define I2S_LL_MCLK_DIVIDER_MAX        ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1)
+#define I2S_LL_CLK_FRAC_DIV_N_MAX  256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width
+#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width
 
 #define I2S_LL_PLL_F160M_CLK_FREQ      (160 * 1000000) // PLL_F160M_CLK: 160MHz
 #define I2S_LL_DEFAULT_PLL_CLK_FREQ     I2S_LL_PLL_F160M_CLK_FREQ    // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT
 
-/**
- * @brief I2S clock configuration structure
- * @note Fmclk = Fsclk /(integ+numer/denom)
- */
-typedef struct {
-    uint16_t integ;     // Integer part of I2S module clock divider
-    uint16_t denom;     // Denominator part of I2S module clock divider
-    uint16_t numer;     // Numerator part of I2S module clock divider
-} i2s_ll_mclk_div_t;
-
 /**
  * @brief I2S module general init, enable I2S clock.
  *
@@ -304,7 +295,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -317,13 +308,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 /**
@@ -344,7 +335,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val)
  * @param hw Peripheral I2S hardware instance address.
  * @param mclk_div The mclk division coefficients
  */
-static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div)
+static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div)
 {
     /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate
      * Set to particular coefficients first then update to the target coefficients,
@@ -357,13 +348,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc
     uint32_t div_z = 0;
     uint32_t div_yn1 = 0;
     /* If any of denominator and numerator is 0, set all the coefficients to 0 */
-    if (mclk_div->denom && mclk_div->numer) {
-        div_yn1 = mclk_div->numer * 2 > mclk_div->denom;
-        div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer;
-        div_x = mclk_div->denom / div_z - 1;
-        div_y = mclk_div->denom % div_z;
+    if (mclk_div->denominator && mclk_div->numerator) {
+        div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator;
+        div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator;
+        div_x = mclk_div->denominator / div_z - 1;
+        div_y = mclk_div->denominator % div_z;
     }
-    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1);
+    i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
 }
 
 

+ 58 - 35
components/hal/hal_utils.c

@@ -5,6 +5,7 @@
  */
 
 #include "hal/hal_utils.h"
+#include "hal/assert.h"
 
 /**
  * @brief helper function, calculate the Greatest Common Divisor
@@ -31,36 +32,42 @@ 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 hal_utils_calc_clk_div_frac_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div)
 {
-    uint32_t div_denom = 1;
+    HAL_ASSERT(clk_info->max_fract > 2);
+    uint32_t div_denom = 2;
     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;
+        // Carry bit if the decimal is greater than 1.0 - 1.0 / ((max_fract - 1) * 2)
+        if (freq_error < clk_info->exp_freq_hz - clk_info->exp_freq_hz / (clk_info->max_fract - 1) * 2) {
+            // 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;
+        } else {
+            div_integ++;
+        }
+    }
+
+    // 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 || div_integ == 0) {
+        return 0;
     }
 
     // Assign result
-    clk_div->integ = div_integ;
-    clk_div->denom = div_denom;
-    clk_div->numer = div_numer;
+    clk_div->integer     = div_integ;
+    clk_div->denominator = div_denom;
+    clk_div->numerator   = div_numer;
 
     // Return the actual frequency
     if (div_numer) {
@@ -70,23 +77,19 @@ uint32_t hal_utils_calc_clk_div_fast(const hal_utils_clk_info_t *clk_info, hal_u
     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 hal_utils_calc_clk_div_frac_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div)
 {
-    uint32_t div_denom = 1;
+    HAL_ASSERT(clk_info->max_fract > 2);
+    uint32_t div_denom = 2;
     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)) {
+        // Carry bit if the decimal is greater than 1.0 - 1.0 / ((max_fract - 1) * 2)
+        if (freq_error < clk_info->exp_freq_hz - clk_info->exp_freq_hz / (clk_info->max_fract - 1) * 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++) {
+            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) {
@@ -99,15 +102,16 @@ uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, h
             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) {
+
+    // 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 || div_integ == 0) {
         return 0;
     }
 
     // Assign result
-    clk_div->integ = div_integ;
-    clk_div->denom = div_denom;
-    clk_div->numer = div_numer;
+    clk_div->integer     = div_integ;
+    clk_div->denominator = div_denom;
+    clk_div->numerator   = div_numer;
 
     // Return the actual frequency
     if (div_numer) {
@@ -116,3 +120,22 @@ uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, h
     }
     return clk_info->src_freq_hz / div_integ;
 }
+
+uint32_t hal_utils_calc_clk_div_integer(const hal_utils_clk_info_t *clk_info, uint32_t *int_div)
+{
+    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 there is error and always round up,
+       Or, do the normal rounding and error >= (src/n + src/(n+1)) / 2,
+       then carry the bit */
+    if ((freq_error && clk_info->round_opt == HAL_DIV_ROUND_UP) || (clk_info->round_opt == HAL_DIV_ROUND &&
+        (freq_error >= clk_info->src_freq_hz / (2 * div_integ * (div_integ + 1))))) {
+        div_integ++;
+    }
+
+    // Assign result
+    *int_div = div_integ;
+    // Return the actual frequency
+    return clk_info->src_freq_hz / div_integ;
+}

+ 12 - 40
components/hal/i2s_hal.c

@@ -29,46 +29,18 @@ static const float cut_off_coef[21][3] = {
  *
  * @param sclk      system clock
  * @param mclk      module clock
- * @param integer   output the integer part of the division
- * @param denominator   output the denominator part of the division
- * @param numerator     output the numerator part of the division
+ * @param mclk_div   output the mclk division coefficients
  */
-void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, i2s_ll_mclk_div_t *mclk_div)
+void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, hal_utils_clk_div_t *mclk_div)
 {
-    int ma = 0;
-    int mb = 0;
-    int min = INT32_MAX;
-    uint32_t div_denom = 1;
-    uint32_t div_numer = 0;
-    uint32_t div_inter = sclk / mclk;
-    uint32_t freq_diff = sclk % mclk;
-
-    if (freq_diff) {
-        float decimal = freq_diff / (float)mclk;
-        // Carry bit if the decimal is greater than 1.0 - 1.0 / (I2S_LL_MCLK_DIVIDER_MAX * 2)
-        if (decimal <= 1.0 - 1.0 / (float)(I2S_LL_MCLK_DIVIDER_MAX * 2)) {
-            for (int a = 2; a <= I2S_LL_MCLK_DIVIDER_MAX; a++) {
-                int b = (int)(a * (freq_diff / (double)mclk) + 0.5);
-                ma = freq_diff * a;
-                mb = mclk * b;
-                if (ma == mb) {
-                    div_denom = (uint32_t)a;
-                    div_numer = (uint32_t)b;
-                    break;
-                }
-                if (abs(mb - ma) < min) {
-                    div_denom = (uint32_t)a;
-                    div_numer = (uint32_t)b;
-                    min = abs(mb - ma);
-                }
-            }
-        } else {
-            div_inter++;
-        }
-    }
-    mclk_div->integ = div_inter;
-    mclk_div->denom = div_denom;
-    mclk_div->numer = div_numer;
+    hal_utils_clk_info_t i2s_clk_info = {
+        .src_freq_hz = sclk,
+        .exp_freq_hz = mclk,
+        .max_integ = I2S_LL_CLK_FRAC_DIV_N_MAX,
+        .min_integ = 1,
+        .max_fract = I2S_LL_CLK_FRAC_DIV_AB_MAX,
+    };
+    hal_utils_calc_clk_div_frac_accurate(&i2s_clk_info, mclk_div);
 }
 
 void i2s_hal_init(i2s_hal_context_t *hal, int port_id)
@@ -79,7 +51,7 @@ void i2s_hal_init(i2s_hal_context_t *hal, int port_id)
 
 void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src)
 {
-    i2s_ll_mclk_div_t mclk_div = {};
+    hal_utils_clk_div_t mclk_div = {};
 #if SOC_I2S_HW_VERSION_2
     i2s_ll_tx_enable_clock(hal->dev);
     i2s_ll_mclk_bind_to_tx_clk(hal->dev);
@@ -92,7 +64,7 @@ void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *cl
 
 void i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src)
 {
-    i2s_ll_mclk_div_t mclk_div = {};
+    hal_utils_clk_div_t mclk_div = {};
 #if SOC_I2S_HW_VERSION_2
     i2s_ll_rx_enable_clock(hal->dev);
     i2s_ll_mclk_bind_to_rx_clk(hal->dev);

+ 35 - 10
components/hal/include/hal/hal_utils.h

@@ -12,6 +12,16 @@
 extern "C" {
 #endif
 
+/**
+ * @brief Integer division operation
+ *
+ */
+typedef enum {
+    HAL_DIV_ROUND_DOWN,     /*!< Round the division down to the floor integer */
+    HAL_DIV_ROUND_UP,       /*!< Round the division up to the ceiling integer */
+    HAL_DIV_ROUND,          /*!< Round the division to the nearest integer (round up if fraction >= 1/2, round down if fraction < 1/2) */
+} hal_utils_div_round_opt_t;
+
 /**
  * @brief Clock infomation
  *
@@ -21,7 +31,11 @@ typedef struct {
     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) */
+    union {
+        uint32_t max_fract;     /*!< The max value of the denominator and numerator, numerator range: [0, max_fract), denominator range: [1, max_fract)
+                                 *   Please make sure max_fract > 2 when calculate the division with fractal part */
+        hal_utils_div_round_opt_t round_opt;     /*!< Integer division operation. For the case that doesn't have fractal part, set this field to the to specify the rounding method  */
+    };
 } hal_utils_clk_info_t;
 
 /**
@@ -29,36 +43,47 @@ typedef struct {
  *
  */
 typedef struct {
-    uint32_t integ;     /*!< Integer part of division */
-    uint32_t denom;     /*!< Denominator part of division */
-    uint32_t numer;     /*!< Numerator part of division */
+    uint32_t integer;       /*!< Integer part of division */
+    uint32_t denominator;   /*!< Denominator part of division */
+    uint32_t numerator;     /*!< Numerator part of division */
 } hal_utils_clk_div_t;
 
 /**
- * @brief Calculate the clock division
+ * @brief Calculate the clock division with fractal part fast
  * @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
+ * @param[out] clk_div      The clock division with integral and fractal part
  * @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);
+uint32_t hal_utils_calc_clk_div_frac_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div);
 
 /**
- * @brief Calculate the clock division
+ * @brief Calculate the clock division with fractal part accurately
  * @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
+ * @param[out] clk_div      The clock division with integral and fractal part
+ * @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_frac_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div);
+
+/**
+ * @brief Calculate the clock division without fractal part
+ *
+ * @param[in]  clk_info     The clock infomation
+ * @param[out] int_div      The clock integral 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);
+uint32_t hal_utils_calc_clk_div_integer(const hal_utils_clk_info_t *clk_info, uint32_t *int_div);
 
 #ifdef __cplusplus
 }

+ 1 - 1
components/hal/include/hal/i2s_hal.h

@@ -130,7 +130,7 @@ void i2s_hal_init(i2s_hal_context_t *hal, int port_id);
  * @param mclk      module clock
  * @param mclk_div  mclk division coefficients, including integer part and decimal part
  */
-void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, i2s_ll_mclk_div_t *mclk_div);
+void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, hal_utils_clk_div_t *mclk_div);
 
 /**
  * @brief Set tx channel clock

+ 14 - 43
components/hal/lcd_hal.c

@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
  *
  * SPDX-License-Identifier: Apache-2.0
  */
@@ -7,31 +7,13 @@
 #include "hal/lcd_hal.h"
 #include "hal/lcd_ll.h"
 #include "hal/log.h"
+#include "hal/hal_utils.h"
 
 void lcd_hal_init(lcd_hal_context_t *hal, int id)
 {
     hal->dev = LCD_LL_GET_HW(id);
 }
 
-/**
- * @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;
-}
-
 uint32_t lcd_hal_cal_pclk_freq(lcd_hal_context_t *hal, uint32_t src_freq_hz, uint32_t expect_pclk_freq_hz, int lcd_clk_flags)
 {
     // lcd_clk = module_clock_src / (n + b / a)
@@ -40,30 +22,19 @@ uint32_t lcd_hal_cal_pclk_freq(lcd_hal_context_t *hal, uint32_t src_freq_hz, uin
     if (mo == 1 && !(lcd_clk_flags & LCD_HAL_PCLK_FLAG_ALLOW_EQUAL_SYSCLK)) {
         mo = 2;
     }
-    uint32_t n = src_freq_hz / expect_pclk_freq_hz / mo;
-    uint32_t a = 0;
-    uint32_t b = 0;
-    // delta_hz / expect_pclk_freq_hz <==> b / a
-    uint32_t delta_hz = src_freq_hz / mo - expect_pclk_freq_hz * n;
-    // fractional divider
-    if (delta_hz) {
-        uint32_t gcd = _gcd(expect_pclk_freq_hz, delta_hz);
-        a = expect_pclk_freq_hz / gcd;
-        b = delta_hz / gcd;
-        // normalize div_a and div_b
-        uint32_t d = a / LCD_LL_CLK_FRAC_DIV_AB_MAX + 1;
-        a /= d;
-        b /= d;
-    }
+    hal_utils_clk_info_t lcd_clk_info = {
+        .src_freq_hz = src_freq_hz,
+        .exp_freq_hz = expect_pclk_freq_hz * mo,
+        .max_integ = LCD_LL_CLK_FRAC_DIV_N_MAX,
+        .min_integ = 2,
+        .max_fract = LCD_LL_CLK_FRAC_DIV_AB_MAX,
+    };
+    hal_utils_clk_div_t lcd_clk_div = {};
+    uint32_t real_freq = hal_utils_calc_clk_div_frac_fast(&lcd_clk_info, &lcd_clk_div);
+    HAL_EARLY_LOGD("lcd_hal", "n=%"PRIu32",a=%"PRIu32",b=%"PRIu32",mo=%"PRIu32, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator, mo);
 
-    HAL_EARLY_LOGD("lcd_hal", "n=%"PRIu32",a=%"PRIu32",b=%"PRIu32",mo=%"PRIu32"", n, a, b, mo);
-
-    lcd_ll_set_group_clock_coeff(hal->dev, n, a, b);
+    lcd_ll_set_group_clock_coeff(hal->dev, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator);
     lcd_ll_set_pixel_clock_prescale(hal->dev, mo);
 
-    if (delta_hz) {
-        return ((uint64_t)src_freq_hz * a) / (n * a + b) / mo;
-    } else {
-        return src_freq_hz / n / mo;
-    }
+    return real_freq / mo;
 }

+ 10 - 0
components/hal/test_apps/hal_utils/CMakeLists.txt

@@ -0,0 +1,10 @@
+# For more information about build system see
+# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
+# The following five lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+set(COMPONENTS main)
+
+project(test_hal_utils)

+ 2 - 0
components/hal/test_apps/hal_utils/README.md

@@ -0,0 +1,2 @@
+| Supported Targets | Linux |
+| ----------------- | ----- |

+ 5 - 0
components/hal/test_apps/hal_utils/main/CMakeLists.txt

@@ -0,0 +1,5 @@
+idf_component_register(SRCS "test_app_main.c"
+                            "test_calc_clk_div.c"
+                    INCLUDE_DIRS "."
+                    REQUIRES unity hal
+                    WHOLE_ARCHIVE)

+ 27 - 0
components/hal/test_apps/hal_utils/main/test_app_main.c

@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include "unity.h"
+#include "unity_test_runner.h"
+
+void app_main(void)
+{
+/**
+ *  _   _    _    _       _   _ _____ ___ _     ____
+ * | | | |  / \  | |     | | | |_   _|_ _| |   / ___|
+ * | |_| | / _ \ | |     | | | | | |  | || |   \___ \
+ * |  _  |/ ___ \| |___  | |_| | | |  | || |___ ___) |
+ * |_| |_/_/   \_\_____|  \___/  |_| |___|_____|____/
+ */
+    printf("  _   _    _    _       _   _ _____ ___ _     ____  \r\n");
+    printf(" | | | |  / \\  | |     | | | |_   _|_ _| |   / ___| \r\n");
+    printf(" | |_| | / _ \\ | |     | | | | | |  | || |   \\___ \\ \r\n");
+    printf(" |  _  |/ ___ \\| |___  | |_| | | |  | || |___ ___) |\r\n");
+    printf(" |_| |_/_/   \\_\\_____|  \\___/  |_| |___|_____|____/ \r\n");
+
+    unity_run_menu();
+}

+ 150 - 0
components/hal/test_apps/hal_utils/main/test_calc_clk_div.c

@@ -0,0 +1,150 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include "unity.h"
+#include "hal/hal_utils.h"
+
+
+TEST_CASE("test_integral_division", "[clk_div]")
+{
+    uint32_t int_div = 0;
+    hal_utils_clk_info_t clk_info = {
+        .src_freq_hz = 80 * 1000 * 1000,
+        .exp_freq_hz = 57 * 1000 * 1000,
+        .max_integ = 256,
+        .min_integ = 1,
+        .round_opt = 0,  // round down
+    };
+    // Round down test
+    uint32_t real_freq = 0;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(1, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz, real_freq);
+
+    // Normal round test
+    clk_info.round_opt = 2;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(2, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq);
+
+    clk_info.exp_freq_hz = 60 * 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(2, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq);
+
+    clk_info.exp_freq_hz = 63 * 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(1, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz, real_freq);
+
+    // Round up test
+    clk_info.round_opt = 1;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(2, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq);
+
+    // Integral division
+    clk_info.exp_freq_hz = 40 * 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(2, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq);
+    clk_info.round_opt = 0;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(2, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq);
+    clk_info.round_opt = 2;
+    real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div);
+    TEST_ASSERT_EQUAL_UINT32(2, int_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq);
+}
+
+TEST_CASE("test_fractal_division", "[clk_div]")
+{
+    hal_utils_clk_div_t clk_div = {};
+    hal_utils_clk_info_t clk_info = {
+        .src_freq_hz = 160 * 1000 * 1000,
+        .exp_freq_hz = 16 * 1024 * 1024,
+        .max_integ = 256,
+        .min_integ = 1,
+        .max_fract = 512,
+    };
+    uint32_t real_freq = 0;
+    // Fractal division with error
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(9, clk_div.integer);
+    TEST_ASSERT_UINT32_WITHIN(clk_info.exp_freq_hz * 0.001, clk_info.exp_freq_hz, real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(9, clk_div.integer);
+    TEST_ASSERT_UINT32_WITHIN(clk_info.exp_freq_hz * 0.0001, clk_info.exp_freq_hz, real_freq);
+
+    // Fractal division with no error
+    clk_info.exp_freq_hz = 50 * 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(3, clk_div.integer);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(3, clk_div.integer);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq);
+
+    // Integral division
+    clk_info.exp_freq_hz = 40 * 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(4, clk_div.integer);
+    TEST_ASSERT_EQUAL_UINT32(0, clk_div.numerator);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(4, clk_div.integer);
+    TEST_ASSERT_EQUAL_UINT32(0, clk_div.numerator);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq);
+}
+
+TEST_CASE("test_fault_division", "[clk_div]")
+{
+    hal_utils_clk_div_t clk_div = {};
+    hal_utils_clk_info_t clk_info = {
+        .src_freq_hz = 160 * 1000 * 1000,
+        .exp_freq_hz = 1250 * 1000,
+        .max_integ = 128,
+        .min_integ = 2,
+        .max_fract = 512,
+    };
+    uint32_t real_freq = 0;
+    // Equal to the max integer
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+
+    // Exceed the max integer
+    clk_info.exp_freq_hz = 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+
+    // Blow the min integer
+    clk_info.exp_freq_hz = 125 * 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+
+    // Equal to the min integer
+    clk_info.exp_freq_hz = 80 * 1000 * 1000;
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq);
+
+    // divide 0 case
+    clk_info.exp_freq_hz = 200 * 1000 * 1000;
+    clk_info.min_integ = 0;
+    real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+    real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
+    TEST_ASSERT_FALSE(real_freq);
+}

+ 11 - 0
components/hal/test_apps/hal_utils/pytest_hal_utils.py

@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
+
+import pytest
+from pytest_embedded import Dut
+
+
+@pytest.mark.linux
+@pytest.mark.host_test
+def test_hal_utils(dut: Dut) -> None:
+    dut.run_all_single_board_cases()

+ 2 - 0
components/hal/test_apps/hal_utils/sdkconfig.defaults

@@ -0,0 +1,2 @@
+CONFIG_IDF_TARGET="linux"
+CONFIG_ESP_TASK_WDT_EN=n