From 26545860f7cf45b78a9df293714b35cb8a477953 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Nov 2024 22:48:08 +0800 Subject: [PATCH] feat(ldo): load calibration parameters from efuse --- .../esp_hw_support/ldo/esp_ldo_regulator.c | 6 +- .../ldo/include/esp_ldo_regulator.h | 3 +- .../main/test_ldo.c | 8 +- components/hal/esp32p4/include/hal/efuse_ll.h | 4 +- components/hal/esp32p4/include/hal/ldo_ll.h | 170 +++++++++++------- 5 files changed, 121 insertions(+), 70 deletions(-) diff --git a/components/esp_hw_support/ldo/esp_ldo_regulator.c b/components/esp_hw_support/ldo/esp_ldo_regulator.c index 2a59dd6de5..63595dbbf2 100644 --- a/components/esp_hw_support/ldo/esp_ldo_regulator.c +++ b/components/esp_hw_support/ldo/esp_ldo_regulator.c @@ -24,6 +24,7 @@ typedef struct ldo_regulator_channel_t { int ref_cnt; struct { uint32_t adjustable : 1; + uint32_t bypass : 1; } flags; } ldo_regulator_channel_t; @@ -83,7 +84,7 @@ esp_err_t esp_ldo_acquire_channel(const esp_ldo_channel_config_t *config, esp_ld uint8_t mul = 0; // calculate the dref and mul ldo_ll_voltage_to_dref_mul(unit_id, config->voltage_mv, &dref, &mul); - ldo_ll_adjust_voltage(unit_id, dref, mul); + ldo_ll_adjust_voltage(unit_id, dref, mul, config->flags.bypass); // set the ldo unit owner ship ldo_ll_set_owner(unit_id, config->flags.owned_by_hw ? LDO_LL_UNIT_OWNER_HW : LDO_LL_UNIT_OWNER_SW); // suppress voltage ripple @@ -94,6 +95,7 @@ esp_err_t esp_ldo_acquire_channel(const esp_ldo_channel_config_t *config, esp_ld channel->ref_cnt++; channel->voltage_mv = config->voltage_mv; channel->flags.adjustable = config->flags.adjustable; + channel->flags.bypass = config->flags.bypass; channel->chan_id = config->chan_id; } portEXIT_CRITICAL(&s_spinlock); @@ -155,7 +157,7 @@ esp_err_t esp_ldo_channel_adjust_voltage(esp_ldo_channel_handle_t chan, int volt uint8_t mul = 0; // calculate the dref and mul ldo_ll_voltage_to_dref_mul(unit_id, voltage_mv, &dref, &mul); - ldo_ll_adjust_voltage(unit_id, dref, mul); + ldo_ll_adjust_voltage(unit_id, dref, mul, chan->flags.bypass); return ESP_OK; } diff --git a/components/esp_hw_support/ldo/include/esp_ldo_regulator.h b/components/esp_hw_support/ldo/include/esp_ldo_regulator.h index 868166acd5..3bf31507cd 100644 --- a/components/esp_hw_support/ldo/include/esp_ldo_regulator.h +++ b/components/esp_hw_support/ldo/include/esp_ldo_regulator.h @@ -30,7 +30,8 @@ typedef struct { /// Extra flags of a LDO channel struct ldo_extra_flags { uint32_t adjustable : 1; /*!< Whether the LDO channel is adjustable, and the voltage can be updated by `esp_ldo_channel_adjust_voltage` */ - uint32_t owned_by_hw: 1; /*!< If the LDO channel is owned by hardware, then software configurations will be overridden by hardware */ + uint32_t owned_by_hw: 1; /*!< If the LDO channel is owned by hardware, then software configurations can be overridden by hardware (e.g. eFuse) */ + uint32_t bypass: 1; /*!< Whether to bypass the regulator, i.e., the input voltage is sourced directly to the output */ } flags; /*!< Flags for the LDO channel */ } esp_ldo_channel_config_t; diff --git a/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_ldo.c b/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_ldo.c index 39f57f1b5c..f2231dc509 100644 --- a/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_ldo.c +++ b/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_ldo.c @@ -25,7 +25,7 @@ TEST_CASE("LDO channel acquire and release (no adjustable)", "[LDO]") TEST_ASSERT_EQUAL(success_ldo_chans[0], success_ldo_chans[1]); TEST_ASSERT_EQUAL(success_ldo_chans[0], success_ldo_chans[2]); // can't acquire with a different voltage - ldo_chan_config.voltage_mv = 3300; + ldo_chan_config.voltage_mv = 2500; TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_ldo_acquire_channel(&ldo_chan_config, &fail_ldo_chan)); // the channel has been acquired as "not adjustable" before, so we can't acquire it as "adjustable" again ldo_chan_config = (esp_ldo_channel_config_t) { @@ -36,7 +36,7 @@ TEST_CASE("LDO channel acquire and release (no adjustable)", "[LDO]") TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_ldo_acquire_channel(&ldo_chan_config, &fail_ldo_chan)); // can't change the voltage for a non-adjustable channel - TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, esp_ldo_channel_adjust_voltage(success_ldo_chans[0], 3300)); + TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, esp_ldo_channel_adjust_voltage(success_ldo_chans[0], 1900)); for (int i = 0; i < 3; i++) { TEST_ESP_OK(esp_ldo_release_channel(success_ldo_chans[i])); @@ -62,7 +62,7 @@ TEST_CASE("LDO channel acquire and release (adjustable)", "[LDO]") TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_ldo_acquire_channel(&ldo_chan_config, &fail_ldo_chan)); // can change voltage for an adjustable channel - TEST_ESP_OK(esp_ldo_channel_adjust_voltage(success_ldo_chan, 3300)); + TEST_ESP_OK(esp_ldo_channel_adjust_voltage(success_ldo_chan, 2500)); TEST_ESP_OK(esp_ldo_release_channel(success_ldo_chan)); } @@ -71,7 +71,7 @@ TEST_CASE("LDO channel state dump", "[LDO][manual][ignore]") esp_ldo_channel_handle_t success_ldo_chans[3] = {}; esp_ldo_channel_config_t ldo_chan_config = { .chan_id = 2, - .voltage_mv = 1800, + .voltage_mv = 1900, }; TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_chan_config, &success_ldo_chans[0])); diff --git a/components/hal/esp32p4/include/hal/efuse_ll.h b/components/hal/esp32p4/include/hal/efuse_ll.h index c287b964dc..11fd66c61d 100644 --- a/components/hal/esp32p4/include/hal/efuse_ll.h +++ b/components/hal/esp32p4/include/hal/efuse_ll.h @@ -73,7 +73,7 @@ __attribute__((always_inline)) static inline bool efuse_ll_get_disable_wafer_ver __attribute__((always_inline)) static inline uint32_t efuse_ll_get_blk_version_major(void) { - return EFUSE.rd_mac_sys_2.disable_blk_version_major; + return EFUSE.rd_mac_sys_2.blk_version_major; } __attribute__((always_inline)) static inline uint32_t efuse_ll_get_blk_version_minor(void) @@ -83,7 +83,7 @@ __attribute__((always_inline)) static inline uint32_t efuse_ll_get_blk_version_m __attribute__((always_inline)) static inline bool efuse_ll_get_disable_blk_version_major(void) { - return EFUSE.rd_mac_sys_2.blk_version_major; + return EFUSE.rd_mac_sys_2.disable_blk_version_major; } __attribute__((always_inline)) static inline uint32_t efuse_ll_get_chip_ver_pkg(void) diff --git a/components/hal/esp32p4/include/hal/ldo_ll.h b/components/hal/esp32p4/include/hal/ldo_ll.h index 73a63eff19..abfd44e012 100644 --- a/components/hal/esp32p4/include/hal/ldo_ll.h +++ b/components/hal/esp32p4/include/hal/ldo_ll.h @@ -14,31 +14,23 @@ #include "hal/efuse_hal.h" #include "hal/pmu_types.h" #include "soc/pmu_struct.h" +#include "soc/efuse_struct.h" #ifdef __cplusplus extern "C" { #endif -#define LDO_LL_NUM_UNITS 4 // NUmber of LDO units -#define LDO_LL_ADJUSTABLE_CHAN_MASK 0x0F // all the 4 channels can be adjustable +#define LDO_LL_NUM_UNITS 4 // Number of LDO units +#define LDO_LL_ADJUSTABLE_CHAN_MASK 0x0F // all the 4 channels are adjustable by setting "mul" and "dref" registers #define LDO_LL_MAX_VOLTAGE_MV 3300 #define LDO_LL_MIN_VOLTAGE_MV 500 /** - * LDO LL macros, these macros are in the unit of mV + * @brief In the analog design, the LDO output "channel" is index from 1, i.e., VO1, VO2, VO3, VO4. + * But in software, we mapped them to "LDO unit", which is index from 0, i.e., 0, 1, 2, 3. */ -#define LDO_LL_EXT_LDO_DREF_VOL_H_BASE 1000 -#define LDO_LL_EXT_LDO_DREF_VOL_H_STEP 100 -#define LDO_LL_EXT_LDO_DREF_VOL_L_BASE 500 -#define LDO_LL_EXT_LDO_DREF_VOL_L_STEP 50 -#define LDO_LL_EXT_LDO_MUL_VOL_BASE 1000 -#define LDO_LL_EXT_LDO_MUL_VOL_STEP 250 - -/** - * LDO ID to real unit ID - */ -#define LDO_ID2UNIT(ldo_id) ((ldo_id) - 1) +#define LDO_ID2UNIT(ldo_id) ((ldo_id) - 1) /** * @brief LDO unit owner @@ -51,6 +43,7 @@ typedef enum { /** * @brief Check if a LDO channel is valid * + * @param ldo_chan LDO channel ID, note, this is indexed from 1 * @return True for valid, false for invalid */ __attribute__((always_inline)) @@ -73,26 +66,76 @@ static inline bool ldo_ll_is_valid_ldo_channel(int ldo_chan) __attribute__((always_inline)) static inline void ldo_ll_voltage_to_dref_mul(int ldo_unit, int voltage_mv, uint8_t *dref, uint8_t *mul) { - // TODO [IDF-10754]: also take the calibration parameters into account - if (voltage_mv <= 500) { - *dref = 0; - *mul = 0; - } else if (voltage_mv <= 900) { - *mul = 0; - *dref = (voltage_mv - LDO_LL_EXT_LDO_DREF_VOL_L_BASE) / LDO_LL_EXT_LDO_DREF_VOL_L_STEP; - } else if (voltage_mv <= 1600) { - *mul = 1; - *dref = 6 + (voltage_mv - LDO_LL_EXT_LDO_DREF_VOL_H_BASE) / LDO_LL_EXT_LDO_DREF_VOL_H_STEP; - } else if (voltage_mv <= 2000) { - *mul = 4; - *dref = (voltage_mv / 2 - LDO_LL_EXT_LDO_DREF_VOL_L_BASE) / LDO_LL_EXT_LDO_DREF_VOL_L_STEP; - } else if (voltage_mv <= 3200) { - *mul = 4; - *dref = 9 + (voltage_mv / 2 - LDO_LL_EXT_LDO_DREF_VOL_H_BASE) / LDO_LL_EXT_LDO_DREF_VOL_H_STEP; - } else { - *mul = 7; - *dref = 15; + uint8_t efuse_k = 0; + uint8_t efuse_vos = 0; + uint8_t efuse_c = 0; + // to avoid using FPU, enlarge the constants by 1000 as fixed point + int K_1000 = 1000; + int Vos_1000 = 0; + int C_1000 = 1000; + + if (efuse_hal_blk_version() >= 1) { + // load the calibration values from the eFuse + if (ldo_unit == 2) { + efuse_k = EFUSE.rd_mac_sys_3.ldo_vo3_k; + efuse_vos = EFUSE.rd_mac_sys_3.ldo_vo3_vos; + efuse_c = EFUSE.rd_mac_sys_3.ldo_vo3_c; + } + if (ldo_unit == 3) { + efuse_k = (EFUSE.rd_mac_sys_4.ldo_vo4_k_1 << 6) + EFUSE.rd_mac_sys_3.ldo_vo4_k; + efuse_vos = EFUSE.rd_mac_sys_4.ldo_vo4_vos; + efuse_c = EFUSE.rd_mac_sys_4.ldo_vo4_c; + } + // convert the eFuse calibration values to fixed point, note these values are signed + if (efuse_k) { + K_1000 = efuse_k & 0x80 ? -1 * (efuse_k & 0x7F) + 975 : efuse_k + 975; + } + if (efuse_vos) { + Vos_1000 = efuse_vos & 0x20 ? -1 * (efuse_vos & 0x1F) - 3 : efuse_vos - 3; + } + if (efuse_c) { + C_1000 = efuse_c & 0x20 ? -1 * (efuse_c & 0x1F) + 990 : efuse_c + 990; + } } + + // iterate all the possible dref and mul values to find the best match + int min_voltage_diff = 400000000; + uint8_t matched_dref = 0; + uint8_t matched_mul = 0; + for (uint8_t dref_val = 0; dref_val < 16; dref_val++) { + int vref_20 = (dref_val < 9) ? (10 + dref_val) : (20 + (dref_val - 9) * 2); + for (uint8_t mul_val = 0; mul_val < 8; mul_val++) { + int vout_80000000 = (vref_20 * K_1000 + 20 * Vos_1000) * (4000 + mul_val * C_1000); + int diff = voltage_mv * 80000 - vout_80000000; + if (diff < 0) { + diff = -diff; + } + if (diff < min_voltage_diff) { + min_voltage_diff = diff; + matched_dref = dref_val; + matched_mul = mul_val; + } + } + } + + if (efuse_hal_blk_version() >= 1) { + // For unit0 and unit1, the mul and dref value are calibrated and saved in the efuse, load them when available + if (ldo_unit == 0 && voltage_mv == 1800) { + if (EFUSE.rd_mac_sys_2.ldo_vo1_dref && EFUSE.rd_mac_sys_3.ldo_vo1_mul) { + matched_mul = EFUSE.rd_mac_sys_3.ldo_vo1_mul; + matched_dref = EFUSE.rd_mac_sys_2.ldo_vo1_dref; + } + } + if (ldo_unit == 1 && voltage_mv == 1900) { + if (EFUSE.rd_mac_sys_2.ldo_vo2_dref && EFUSE.rd_mac_sys_3.ldo_vo2_mul) { + matched_mul = EFUSE.rd_mac_sys_3.ldo_vo2_mul; + matched_dref = EFUSE.rd_mac_sys_2.ldo_vo2_dref; + } + } + } + + *dref = matched_dref; + *mul = matched_mul; } /** @@ -113,6 +156,39 @@ static inline void ldo_ll_set_owner(int ldo_unit, ldo_ll_unit_owner_t owner) * - 1: tieh_sel, i.e. by software */ PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.force_tieh_sel = owner; + /** + * tieh_sel: + * - 0: tieh; + * - 1: sdmmc0_tieh; + * - 2: 3.3V; + * - 3: sdmmc1_tieh; + */ + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.tieh_sel = 0; +} + +/** + * @brief Adjust voltage of a LDO unit + * + * @note When bypass is enabled, the input voltage is sourced directly to the output. + * The dref and mul values will be ignored. + * + * @param ldo_unit LDO unit + * @param dref A parameter which controls the internal reference voltage + * @param mul Multiply factor + * @param bypass True: bypass; False: not bypass. + */ +__attribute__((always_inline)) +static inline void ldo_ll_adjust_voltage(int ldo_unit, uint8_t dref, uint8_t mul, bool bypass) +{ + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + /** + * tieh: + * - 0: Vref * Mul + * - 1: 3.3V + */ + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.tieh = bypass; + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo_ana.dref = dref; + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo_ana.mul = mul; } /** @@ -132,34 +208,6 @@ static inline void ldo_ll_enable(int ldo_unit, bool enable) PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.xpd = enable; } -/** - * @brief Adjust voltage of a LDO unit - * - * @param ldo_unit LDO unit - * @param dref A parameter which controls the internal reference voltage - * @param mul Multiply factor - */ -__attribute__((always_inline)) -static inline void ldo_ll_adjust_voltage(int ldo_unit, uint8_t dref, uint8_t mul) -{ - uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; - PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo_ana.dref = dref; - PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo_ana.mul = mul; - /** - * tieh: - * - 0: Vref * Mul - * - 1: 3.3V - * - * tieh_sel: - * - 0: tieh; - * - 1: sdmmc0_tieh; - * - 2: 3.3V; - * - 3: sdmmc1_tieh; - */ - PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.tieh_sel = 0; - PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.tieh = 0; -} - /** * @brief Enable power on delay of a LDO unit *