From 631e15c6eb6bf4f6d22ca22918738f0e92678b51 Mon Sep 17 00:00:00 2001 From: morris Date: Wed, 7 Aug 2024 19:09:49 +0800 Subject: [PATCH] feat(ldo): add config to let hardware control the ldo output If LDO1 is used by spi flash, then we recommend to give the ownership to the hardware. Software just read the parameters from the efuse and set to PMU. --- components/esp_hw_support/clk_ctrl_os.c | 2 +- .../esp_hw_support/ldo/esp_ldo_regulator.c | 26 +- .../ldo/include/esp_ldo_regulator.h | 1 + .../esp_hw_support/port/esp32p4/Kconfig.ldo | 37 ++- components/hal/esp32p4/include/hal/ldo_ll.h | 225 ++++++++++++------ components/spi_flash/esp_flash_spi_init.c | 7 +- 6 files changed, 206 insertions(+), 92 deletions(-) diff --git a/components/esp_hw_support/clk_ctrl_os.c b/components/esp_hw_support/clk_ctrl_os.c index a8b2560abb..30842ff964 100644 --- a/components/esp_hw_support/clk_ctrl_os.c +++ b/components/esp_hw_support/clk_ctrl_os.c @@ -142,7 +142,7 @@ esp_err_t periph_rtc_apll_freq_set(uint32_t expt_freq, uint32_t *real_freq) esp_err_t IRAM_ATTR periph_rtc_mpll_acquire(void) { // power up LDO for the MPLL -#if defined(CONFIG_ESP_LDO_CHAN_PSRAM_DOMAIN) && CONFIG_ESP_LDO_CHAN_PSRAM_DOMAIN != -1 +#if CONFIG_ESP_LDO_RESERVE_PSRAM esp_ldo_channel_config_t ldo_mpll_config = { .chan_id = CONFIG_ESP_LDO_CHAN_PSRAM_DOMAIN, .voltage_mv = CONFIG_ESP_LDO_VOLTAGE_PSRAM_DOMAIN, diff --git a/components/esp_hw_support/ldo/esp_ldo_regulator.c b/components/esp_hw_support/ldo/esp_ldo_regulator.c index 97dcc6f4bb..2a59dd6de5 100644 --- a/components/esp_hw_support/ldo/esp_ldo_regulator.c +++ b/components/esp_hw_support/ldo/esp_ldo_regulator.c @@ -31,8 +31,8 @@ static portMUX_TYPE s_spinlock = portMUX_INITIALIZER_UNLOCKED; static const uint32_t s_ldo_channel_adjustable_mask = LDO_LL_ADJUSTABLE_CHAN_MASK; // each bit represents if the LDO channel is adjustable in hardware -static ldo_regulator_channel_t s_ldo_channels[LDO_LL_UNIT_NUM] = { - [0 ... LDO_LL_UNIT_NUM - 1] = { +static ldo_regulator_channel_t s_ldo_channels[LDO_LL_NUM_UNITS] = { + [0 ... LDO_LL_NUM_UNITS - 1] = { .chan_id = -1, .voltage_mv = 0, .ref_cnt = 0, @@ -43,7 +43,7 @@ static ldo_regulator_channel_t s_ldo_channels[LDO_LL_UNIT_NUM] = { esp_err_t esp_ldo_acquire_channel(const esp_ldo_channel_config_t *config, esp_ldo_channel_handle_t *out_handle) { ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - ESP_RETURN_ON_FALSE(ldo_ll_is_valid_ldo_id(config->chan_id), ESP_ERR_INVALID_ARG, TAG, "invalid ldo channel ID"); + ESP_RETURN_ON_FALSE(ldo_ll_is_valid_ldo_channel(config->chan_id), ESP_ERR_INVALID_ARG, TAG, "invalid ldo channel ID"); ESP_RETURN_ON_FALSE(config->voltage_mv <= LDO_LL_MAX_VOLTAGE_MV, ESP_ERR_INVALID_ARG, TAG, "invalid voltage value: %d", config->voltage_mv); int unit_id = LDO_ID2UNIT(config->chan_id); @@ -78,8 +78,16 @@ esp_err_t esp_ldo_acquire_channel(const esp_ldo_channel_config_t *config, esp_ld } if (check_voltage_constraint_valid && check_adjustable_constraint_valid) { if (channel->ref_cnt == 0) { - // if the channel is not in use, we need to set the voltage and enable it - ldo_ll_set_output_voltage_mv(unit_id, config->voltage_mv); + // if the channel is not in use, we need to set the initial voltage and enable it + uint8_t dref = 0; + 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); + // 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 + ldo_ll_enable_ripple_suppression(unit_id, true); ldo_ll_enable(unit_id, true); } // update the channel attributes @@ -143,7 +151,11 @@ esp_err_t esp_ldo_channel_adjust_voltage(esp_ldo_channel_handle_t chan, int volt // i.e., the handle is not shared between threads without mutex protection chan->voltage_mv = voltage_mv; int unit_id = LDO_ID2UNIT(chan->chan_id); - ldo_ll_set_output_voltage_mv(unit_id, voltage_mv); + uint8_t dref = 0; + 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); return ESP_OK; } @@ -153,7 +165,7 @@ esp_err_t esp_ldo_dump(FILE *stream) char line[100]; fprintf(stream, "ESP LDO Channel State:\n"); fprintf(stream, "%-5s %-5s %-10s %-12s %-5s\n", "Index", "ID", "ref_cnt", "voltage_mv", "adjustable"); - for (int i = 0; i < LDO_LL_UNIT_NUM; i++) { + for (int i = 0; i < LDO_LL_NUM_UNITS; i++) { char *buf = line; size_t len = sizeof(line); memset(line, 0x0, len); 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 0540541003..868166acd5 100644 --- a/components/esp_hw_support/ldo/include/esp_ldo_regulator.h +++ b/components/esp_hw_support/ldo/include/esp_ldo_regulator.h @@ -30,6 +30,7 @@ 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 */ } flags; /*!< Flags for the LDO channel */ } esp_ldo_channel_config_t; diff --git a/components/esp_hw_support/port/esp32p4/Kconfig.ldo b/components/esp_hw_support/port/esp32p4/Kconfig.ldo index 6c0bea47ed..0e5a20fdd8 100644 --- a/components/esp_hw_support/port/esp32p4/Kconfig.ldo +++ b/components/esp_hw_support/port/esp32p4/Kconfig.ldo @@ -1,19 +1,26 @@ menu "LDO Regulator Configurations" depends on SOC_GP_LDO_SUPPORTED + config ESP_LDO_RESERVE_SPI_NOR_FLASH + bool "Reserve one LDO regulator channel for SPI NOR Flash (READ HELP)" + default y + help + The LDO channel 1 can be used to power the SPI Flash chip, + because the channel 1 is enabled by default after power on reset. + If your SPI flash chip is not powered by ESP internal LDO, you can disable this option. + Then you will free up one LDO channel for other general purpose. + config ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN int "LDO regulator channel that used to power SPI NOR Flash (READ HELP)" + depends on ESP_LDO_RESERVE_SPI_NOR_FLASH default 1 - range -1 4 + range 1 1 help - The internal LDO regulator can be used to power the SPI Flash specific power domain. - This option is to select which LDO channel to connect to that domain. - Please set this option correctly according to your schematic. - Set to -1 if the Flash is using any external power supply. + Select which LDO channel to connect to the SPI Flash chip. choice ESP_LDO_VOLTAGE_SPI_NOR_FLASH_DOMAIN prompt "SPI NOR Flash power domain voltage" - depends on ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN != -1 + depends on ESP_LDO_RESERVE_SPI_NOR_FLASH default ESP_LDO_VOLTAGE_SPI_NOR_FLASH_3300_MV help Select the voltage used by the Flash power domain. @@ -26,19 +33,25 @@ menu "LDO Regulator Configurations" int default 3300 if ESP_LDO_VOLTAGE_SPI_NOR_FLASH_3300_MV + config ESP_LDO_RESERVE_PSRAM + bool "Reserve one LDO regulator channel for PSRAM (READ HELP)" + default y + help + The LDO channel 2 can be used to power the PSRAM chip. + If the PSRAM chip is not powered by ESP internal LDO, you can disable this option. + Then you will free up one LDO channel for other general purpose. + config ESP_LDO_CHAN_PSRAM_DOMAIN int "LDO regulator channel that used to power PSRAM and MPLL (READ HELP)" + depends on ESP_LDO_RESERVE_PSRAM default 2 - range -1 4 + range 2 2 help - The internal LDO regulator can be used to power the PSRAM specific power domain. - This option is to select which LDO channel to connect to that domain. - Please set this option correctly according to your schematic. - Set to -1 if the PSRAM is using any external power supply. + Select which LDO channel to connect to the PSRAM chip. choice ESP_LDO_VOLTAGE_PSRAM_DOMAIN prompt "PSRAM power domain voltage" - depends on ESP_LDO_CHAN_PSRAM_DOMAIN != -1 + depends on ESP_LDO_RESERVE_PSRAM default ESP_LDO_VOLTAGE_PSRAM_1900_MV help Select the voltage used by the PSRAM power domain. diff --git a/components/hal/esp32p4/include/hal/ldo_ll.h b/components/hal/esp32p4/include/hal/ldo_ll.h index a047487ae3..6e707890ad 100644 --- a/components/hal/esp32p4/include/hal/ldo_ll.h +++ b/components/hal/esp32p4/include/hal/ldo_ll.h @@ -4,29 +4,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -/******************************************************************************* - * NOTICE - * The ll is not public api, don't use in application code. - * See readme.md in hal/include/hal/readme.md - ******************************************************************************/ - #pragma once #include #include #include "esp_bit_defs.h" -#include "hal/assert.h" +#include "hal/misc.h" #include "soc/pmu_struct.h" #ifdef __cplusplus extern "C" { #endif -/** - * LDO capabilities - */ -#define LDO_LL_UNIT_NUM 4 - +#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_MAX_VOLTAGE_MV 3300 @@ -48,91 +38,186 @@ extern "C" { #define LDO_ID2UNIT(ldo_id) ((ldo_id) - 1) /** - * @brief Check if a LDO ID is valid + * @brief LDO unit owner + */ +typedef enum { + LDO_LL_UNIT_OWNER_HW, // LDO unit is controlled by hardware + LDO_LL_UNIT_OWNER_SW, // LDO unit is controlled by software +} ldo_ll_unit_owner_t; + +/** + * @brief Check if a LDO channel is valid * - * @return True for valid + * @return True for valid, false for invalid */ __attribute__((always_inline)) -static inline bool ldo_ll_is_valid_ldo_id(int ldo_id) +static inline bool ldo_ll_is_valid_ldo_channel(int ldo_chan) { - return ((ldo_id > 0) && (ldo_id <= LDO_LL_UNIT_NUM)); + return (ldo_chan > 0) && (ldo_chan <= LDO_LL_NUM_UNITS); } /** - * @brief Enable a LDO + * @brief Convert voltage to dref and mul value * - * @param ldo_id LDO ID + * @note Vref = (dref < 9)?(0.5+dref*0.05):(1+(dref-9)*0.1) + * @note Vout = (Vref*K+Vos)*(1+0.25*mul*C), K, Vos, C are constants saved in the eFuse, for calibration + * + * @param ldo_unit LDO unit + * @param voltage_mv Voltage in mV + * @param dref Returned dref value + * @param mul Returned mul value + */ +__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; + } +} + +/** + * @brief Set owner of a LDO unit + * + * @note Even if the LDO unit is controlled by hardware, its voltage can still be changed by software by `ldo_ll_adjust_voltage` + * + * @param ldo_unit LDO unit + * @param owner Owner of the LDO unit + */ +__attribute__((always_inline)) +static inline void ldo_ll_set_owner(int ldo_unit, ldo_ll_unit_owner_t owner) +{ + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + /* + * force_tieh_sel: + * - 0: efuse, i.e. by hardware + * - 1: tieh_sel, i.e. by software + */ + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.force_tieh_sel = owner; +} + +/** + * @brief Enable a LDO unit + * + * @param ldo_unit LDO unit * @param enable True: enable; False: disable */ __attribute__((always_inline)) -static inline void ldo_ll_enable(int ldo_id, bool enable) +static inline void ldo_ll_enable(int ldo_unit, bool enable) { - HAL_ASSERT(ldo_id < LDO_LL_UNIT_NUM); - uint8_t index_array[LDO_LL_UNIT_NUM] = {0,3,1,4}; - PMU.ext_ldo[index_array[ldo_id]].pmu_ext_ldo.xpd = enable; + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.xpd = enable; } /** - * @brief Enable a LDO + * @brief Adjust voltage of a LDO unit * - * @param ldo_id LDO ID - * @param voltage_mv Voltage in mV + * @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_set_output_voltage_mv(int ldo_id, int voltage_mv) +static inline void ldo_ll_adjust_voltage(int ldo_unit, uint8_t dref, uint8_t mul) { - int dref = 0, mul = 0; - + 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; /** - * Vout = dref * mul + * tieh: + * - 0: Vref * Mul + * - 1: 3.3V * - * mul reg[2:0]: - * mul = 1~2.75, step = 0.25 - - * dref reg[3:0]: - * 0~8 : dref = 0.5V ~ 0.9V, step 50mV - * 9~15 : dref = 1.0V ~ 1.6V, step 100mV - */ - 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; - } - /** * tieh_sel: * - 0: tieh; * - 1: sdmmc0_tieh; * - 2: 3.3V; * - 3: sdmmc1_tieh; - * - * tieh: - * - 0: dref * mul - * - 1: 3.3V - * - * force_tieh_sel: - * - 0: efuse - * - 1: tieh_sel */ - uint8_t index_array[LDO_LL_UNIT_NUM] = {0,3,1,4}; - PMU.ext_ldo[index_array[ldo_id]].pmu_ext_ldo.tieh_sel = 0; - PMU.ext_ldo[index_array[ldo_id]].pmu_ext_ldo.tieh = 0; - PMU.ext_ldo[index_array[ldo_id]].pmu_ext_ldo.force_tieh_sel = 1; - PMU.ext_ldo[index_array[ldo_id]].pmu_ext_ldo_ana.dref = dref; - PMU.ext_ldo[index_array[ldo_id]].pmu_ext_ldo_ana.mul = mul; + 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 + * + * @param ldo_unit LDO unit + * @param enable True: enable; False: disable + */ +__attribute__((always_inline)) +static inline void ldo_ll_enable_power_on_delay(int ldo_unit, bool enable) +{ + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.tieh_pos_en = enable; +} + +/** + * @brief Enable power off delay of a LDO unit + * + * @param ldo_unit LDO unit + * @param enable True: enable; False: disable + */ +__attribute__((always_inline)) +static inline void ldo_ll_enable_power_off_delay(int ldo_unit, bool enable) +{ + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.tieh_neg_en = enable; +} + +/** + * @brief Set power on delay target of a LDO unit + * + * @param ldo_unit LDO unit + * @param target0 Target 0 + * @param target1 Target 1 + */ +__attribute__((always_inline)) +static inline void ldo_ll_set_delay_target(int ldo_unit, uint8_t target0, uint8_t target1) +{ + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + HAL_FORCE_MODIFY_U32_REG_FIELD(PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo, target0, target0); + HAL_FORCE_MODIFY_U32_REG_FIELD(PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo, target1, target1); +} + +/** + * @brief Enable current limit of a LDO unit to avoid inrush current + * + * @param ldo_unit LDO unit + * @param enable True: enable; False: disable + */ +__attribute__((always_inline)) +static inline void ldo_ll_enable_current_limit(int ldo_unit, bool enable) +{ + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo_ana.en_cur_lim = enable; +} + +/** + * @brief Enable ripple suppression of a LDO unit + * + * @param ldo_unit LDO unit + * @param enable True: enable; False: disable + */ +__attribute__((always_inline)) +static inline void ldo_ll_enable_ripple_suppression(int ldo_unit, bool enable) +{ + uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4}; + PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo_ana.en_vdet = enable; } #ifdef __cplusplus diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index 7c7235eab6..9f6c54678f 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -437,15 +437,18 @@ esp_err_t esp_flash_app_init(void) // Acquire the LDO channel used by the SPI NOR flash // in case the LDO voltage is changed by other users -#if defined(CONFIG_ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN) && CONFIG_ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN != -1 +#if CONFIG_ESP_LDO_RESERVE_SPI_NOR_FLASH static esp_ldo_channel_handle_t s_ldo_chan = NULL; esp_ldo_channel_config_t ldo_config = { .chan_id = CONFIG_ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN, .voltage_mv = CONFIG_ESP_LDO_VOLTAGE_SPI_NOR_FLASH_DOMAIN, + .flags = { + .owned_by_hw = true, // LDO output is totally controlled by hardware + }, }; err = esp_ldo_acquire_channel(&ldo_config, &s_ldo_chan); if (err != ESP_OK) return err; -#endif +#endif // CONFIG_ESP_LDO_RESERVE_SPI_NOR_FLASH spi_flash_init_lock(); spi_flash_guard_set(&g_flash_guard_default_ops);