Merge branch 'refactor/split_bod_hal_v6.0' into 'release/v6.0'

refactor(bod): Move bod hal to pmu hal component (backport v6.0)

See merge request espressif/esp-idf!46411
This commit is contained in:
morris
2026-03-23 11:02:07 +08:00
19 changed files with 11 additions and 21 deletions
@@ -1,156 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The ll is not public api, don't use in application code.
* See readme.md in hal/readme.md
******************************************************************************/
#pragma once
#include <stdbool.h>
#include "esp_bit_defs.h"
#include "soc/lp_analog_peri_struct.h"
#include "hal/regi2c_ctrl.h"
#include "hal/psdet_types.h"
#include "soc/regi2c_brownout.h"
#include "hal/efuse_hal.h"
#include "soc/chip_revision.h"
#define BROWNOUT_DETECTOR_LL_INTERRUPT_MASK (BIT(31))
#define BROWNOUT_DETECTOR_LL_FIB_ENABLE (BIT(1))
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief suspend the flash when a brown out happens.
*
* @param enable true: suspend flash. false: not suspend
*/
static inline void brownout_ll_enable_flash_suspend(bool enable)
{
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_close_flash_ena = enable;
}
/**
* @brief power down the RF circuits when a brown out happens
*
* @param enable true: power down. false: not power down.
*/
static inline void brownout_ll_enable_rf_power_down(bool enable)
{
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_pd_rf_ena = enable;
}
/**
* @brief Configure the brown out detector to do a hardware reset
*
* @note: If brown out interrupt is also used, the hardware reset can be disabled,
* because we can call software reset in the interrupt handler.
*
* @param reset_ena true: enable reset. false: disable reset.
* @param reset_wait brown out reset wait cycles
* @param reset_level reset level
*/
static inline void brownout_ll_reset_config(bool reset_ena, uint32_t reset_wait, brownout_reset_level_t reset_level)
{
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_reset_wait = reset_wait;
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_reset_ena = reset_ena;
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_reset_sel = reset_level;
}
/**
* @brief Set brown out threshold voltage
*
* @param threshold brownout threshold
*/
static inline void brownout_ll_set_threshold(uint8_t threshold)
{
if (!ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 100)) {
threshold = 0; // Fix this level as 0 so that on v0.x brownout value will be fixed around 2.52v.
}
REGI2C_WRITE_MASK(I2C_BOD, I2C_BOD_THRESHOLD_L, threshold);
REGI2C_WRITE_MASK(I2C_BOD, I2C_BOD_THRESHOLD_H, threshold);
}
/**
* @brief Set this bit to enable the brown out detection
*
* @param bod_enable true: enable, false: disable
*/
static inline void brownout_ll_bod_enable(bool bod_enable)
{
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_intr_ena = bod_enable;
}
/**
* @brief configure the waiting cycles before sending an interrupt
*
* @param cycle waiting cycles.
*/
static inline void brownout_ll_set_intr_wait_cycles(uint8_t cycle)
{
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_intr_wait = cycle;
}
/**
* @brief Enable brown out interrupt
*
* @param enable true: enable, false: disable
*
* @note Avoid concurrency risky with vbat_ll_enable_intr_mask
*/
static inline void brownout_ll_intr_enable(bool enable)
{
LP_ANA_PERI.int_ena.bod_mode0_int_ena = enable;
}
/**
* @brief Enable brownout hardware reset (mode1)
*
* @param enable true: enable, false: disable
*/
static inline void brownout_ll_ana_reset_enable(bool enable)
{
// give BOD mode1 control permission to the software
LP_ANA_PERI.fib_enable.val &= ~BROWNOUT_DETECTOR_LL_FIB_ENABLE;
// then we can enable or disable if we want the BOD mode1 to reset the system
LP_ANA_PERI.bod_mode1_cntl.bod_mode1_reset_ena = enable;
}
/**
* @brief Clear interrupt bits.
*/
__attribute__((always_inline))
static inline void brownout_ll_intr_clear(void)
{
LP_ANA_PERI.int_clr.val = BROWNOUT_DETECTOR_LL_INTERRUPT_MASK;
}
/**
* @brief Clear BOD internal count.
*/
static inline void brownout_ll_clear_count(void)
{
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_cnt_clr = 1;
LP_ANA_PERI.bod_mode0_cntl.bod_mode0_cnt_clr = 0;
}
/**
* @brief Get interrupt status register address
*
* @return Register address
*/
static inline volatile void *brownout_ll_intr_get_status_reg(void)
{
return &LP_ANA_PERI.int_st;
}
#ifdef __cplusplus
}
#endif
-285
View File
@@ -1,285 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "soc/chip_revision.h"
#include "esp_bit_defs.h"
#include "hal/misc.h"
#include "hal/efuse_hal.h"
#include "hal/pmu_types.h"
#include "soc/pmu_struct.h"
#include "soc/efuse_struct.h"
#include "hal/config.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 are adjustable by setting "mul" and "dref" registers
#define LDO_LL_RECOMMEND_MAX_VOLTAGE_MV 2700
#define LDO_LL_RECOMMEND_MIN_VOLTAGE_MV 500
#define LDO_LL_RAIL_VOLTAGE_MV 3300
/**
* @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_ID2UNIT(ldo_id) ((ldo_id) - 1)
/**
* @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
*
* @param ldo_chan LDO channel ID, note, this is indexed from 1
* @return True for valid, false for invalid
*/
__attribute__((always_inline))
static inline bool ldo_ll_is_valid_ldo_channel(int ldo_chan)
{
return (ldo_chan > 0) && (ldo_chan <= LDO_LL_NUM_UNITS);
}
/**
* @brief Convert voltage to dref and mul value
*
* @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
* @param use_rail_voltage Returned value to indicate if the rail voltage should be used
*/
__attribute__((always_inline))
static inline void ldo_ll_voltage_to_dref_mul(int ldo_unit, int voltage_mv, uint8_t *dref, uint8_t *mul, bool *use_rail_voltage)
{
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 == 1800) {
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;
// if the expected voltage is 3.3V, use the rail voltage directly
*use_rail_voltage = (voltage_mv == LDO_LL_RAIL_VOLTAGE_MV);
}
/**
* @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;
/**
* 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 use_rail_voltage Use rail voltage directly (i.e. bypass the LDO)
*/
__attribute__((always_inline))
static inline void ldo_ll_adjust_voltage(int ldo_unit, uint8_t dref, uint8_t mul, bool use_rail_voltage)
{
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 = use_rail_voltage;
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;
}
/**
* @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_unit, bool enable)
{
uint8_t index_array[LDO_LL_NUM_UNITS] = {0, 3, 1, 4};
if (ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 100) && (ldo_unit == 0)) {
// If chip_rev >= v1.0, slp_mem_dbias[3] is used to control the volt output of VO1.
PMU.hp_sys[PMU_MODE_HP_ACTIVE].regulator0.xpd_0p1a = (enable ? 8 : 0);
}
PMU.ext_ldo[index_array[ldo_unit]].pmu_ext_ldo.xpd = enable;
}
/**
* @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
}
#endif
@@ -1,166 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The ll is not public api, don't use in application code.
* See readme.md in hal/readme.md
******************************************************************************/
#pragma once
#include <stdbool.h>
#include "esp_bit_defs.h"
#include "soc/lp_analog_peri_struct.h"
#include "hal/assert.h"
#include "hal/regi2c_ctrl.h"
#include "soc/regi2c_brownout.h"
typedef enum {
VBAT_LL_CHARGER_UPVOLTAGE_INTR = BIT(27),
VBAT_LL_CHARGER_UNDERVOLTAGE_INTR = BIT(28),
VBAT_LL_BROWNOUT_INTR = BIT(30),
} vbat_ll_intr_t;
#define VBAT_LL_CHARGER_MASK (BIT(27)|BIT(28))
#define VBAT_LL_DETECT_MASK (BIT(30))
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set vbat brownout threshold voltage
*
* @param threshold vbat brownout threshold
*/
static inline void vbat_ll_set_brownout_threshold(uint8_t threshold)
{
// Give same value
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_L, threshold);
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_H, threshold);
}
/**
* @brief Set vbat charge threshold voltage
*
* @param threshold vbat charge threshold
*/
static inline void vbat_ll_set_charger_threshold(uint8_t threshold_l, uint8_t threshold_h)
{
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_CHARGER_L, threshold_l);
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_CHARGER_H, threshold_h);
}
/**
* @brief Enable or disable the VBAT charger comparator
*
* @param enable Set to `true` to enable the comparator, or `false` to disable it.
*/
static inline void vbat_ll_enable_charger_comparator(bool enable)
{
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_FORCE_PU_VBAT_CHARGER, enable);
}
/**
* @brief Set the under voltage filter time for the charger detector
*
* @param time_tick The filter time in ticks (unit depends on the hardware implementation).
*/
static inline void vbat_ll_set_undervoltage_filter_time(uint32_t time_tick)
{
HAL_ASSERT(time_tick < (2<<10));
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_undervoltage_target = time_tick;
}
/**
* @brief Set the upvoltage filter time for the charger detector
*
* @param time_tick The filter time in ticks (unit depends on the hardware implementation).
*/
static inline void vbat_ll_set_upvoltage_filter_time(uint32_t time_tick)
{
HAL_ASSERT(time_tick < (2<<10));
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_upvoltage_target = time_tick;
}
/**
* @brief Set the charger resistor value for VBAT charging
*
* @param resistor Resistor value to be set (unit depends on the hardware implementation).
*/
static inline void vbat_ll_set_charger_resistor(uint32_t resistor)
{
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DRES_CHARGER, resistor);
}
/*
* @brief Start or stop the VBAT battery charging process
*
* @param start Set to true to start charging, or false to stop charging.
*/
static inline void vbat_ll_start_battery_charge(bool start)
{
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_charger = start;
LP_ANA_PERI.vddbat_bod_cntl.vddbat_charger = start;
}
/**
* @brief Enable the interrupt mask for vbat usage
*
* @param mask A bitmask representing the interrupts to enable.
* Each bit corresponds to a specific interrupt source.
* @param enable true for enabling the interrupt, otherwise false.
*
* @note Avoid concurrency risky with brownout_ll_intr_enable
*/
static inline void vbat_ll_enable_intr_mask(uint32_t mask, bool enable)
{
if (enable) {
LP_ANA_PERI.int_ena.val |= mask;
} else {
LP_ANA_PERI.int_ena.val &= ~mask;
}
}
/**
* @brief Clear the interrupt mask for vbat usage
*
* @param mask A bitmask representing the interrupts to clear.
* Each bit corresponds to a specific interrupt source.
*/
static inline void vbat_ll_clear_intr_mask(uint32_t mask)
{
LP_ANA_PERI.int_clr.val = mask;
}
/**
* @brief Get the current interrupt mask for vbat usage
*
* @param intr_status Pointer to a variable where the interrupt status mask will be stored.
* The function will write the current interrupt status to this variable.
*/
static inline void vbat_ll_get_interrupt_status(uint32_t *intr_status)
{
*intr_status = LP_ANA_PERI.int_st.val;
}
/**
* @brief Clear the VBAT count for charge detection
*
* This function clears the internal counter that tracks the number of charge events detected
* related to the VBAT power supply. It is typically used to reset the count for monitoring purposes.
*/
static inline void vbat_ll_clear_count(void)
{
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_cnt_clr = 1;
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_cnt_clr = 0;
}
#ifdef __cplusplus
}
#endif