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.
This commit is contained in:
morris 2024-08-07 19:09:49 +08:00
parent 11831cc0db
commit 631e15c6eb
6 changed files with 206 additions and 92 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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.

View File

@ -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 <stdint.h>
#include <stdbool.h>
#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

View File

@ -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);