feat(ldo): add ldo regulator driver for public use

This commit is contained in:
morris
2024-03-16 23:58:01 +08:00
parent 950740dc62
commit 061e5dc695
11 changed files with 342 additions and 72 deletions

View File

@@ -73,8 +73,8 @@ if(NOT BOOTLOADER_BUILD)
endif()
endif()
if(CONFIG_SOC_MULTI_USAGE_LDO_SUPPORTED)
list(APPEND srcs "ldo/esp_ldo.c")
if(CONFIG_SOC_GP_LDO_SUPPORTED)
list(APPEND srcs "ldo/esp_ldo.c" "ldo/esp_ldo_regulator.c")
if(CONFIG_SPIRAM OR CONFIG_SOC_CLK_MPLL_SUPPORTED)
list(APPEND srcs "ldo/esp_ldo_psram.c")
endif()
@@ -168,7 +168,7 @@ else()
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS include include/soc include/soc/${target} dma/include
INCLUDE_DIRS include include/soc include/soc/${target} dma/include ldo/include
PRIV_INCLUDE_DIRS port/include include/esp_private
REQUIRES ${requires}
PRIV_REQUIRES "${priv_requires}"

View File

@@ -0,0 +1,169 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "soc/soc_caps.h"
#include "hal/ldo_ll.h"
#include "esp_ldo_regulator.h"
static const char *TAG = "ldo";
typedef struct ldo_regulator_channel_t {
int chan_id;
int voltage_mv;
int ref_cnt;
struct {
uint32_t adjustable : 1;
} flags;
} ldo_regulator_channel_t;
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] = {
.chan_id = -1,
.voltage_mv = 0,
.ref_cnt = 0,
.flags.adjustable = 1,
},
};
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(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);
ldo_regulator_channel_t *channel = &s_ldo_channels[unit_id];
bool check_adjustable_constraint_valid = true;
bool check_voltage_constraint_valid = true;
portENTER_CRITICAL(&s_spinlock);
if (config->flags.adjustable) {
// the user wants to adjust it
// but the channel is marked as not adjustable
if (channel->flags.adjustable == 0) {
check_adjustable_constraint_valid = false;
} else if (channel->ref_cnt != 0) {
// or the channel is already in use by others
// but we don't allow different users to adjust the same LDO, in case they set to different voltages
// that's to say, if the LDO channel is adjustable, it can only have one reference
check_adjustable_constraint_valid = false;
}
} else {
// the user doesn't want to adjust the voltage
// but the channel is already in use by others
if (channel->ref_cnt != 0) {
if (channel->flags.adjustable) {
// we don't allow to have another non-adjustable user
check_adjustable_constraint_valid = false;
} else if (channel->voltage_mv != config->voltage_mv) {
// the voltage is different from us
check_voltage_constraint_valid = false;
}
}
}
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);
ldo_ll_enable(unit_id, true);
}
// update the channel attributes
channel->ref_cnt++;
channel->voltage_mv = config->voltage_mv;
channel->flags.adjustable = config->flags.adjustable;
channel->chan_id = config->chan_id;
}
portEXIT_CRITICAL(&s_spinlock);
ESP_RETURN_ON_FALSE(check_voltage_constraint_valid, ESP_ERR_INVALID_ARG, TAG,
"can't change the voltage for a non-adjustable channel, expect:%dmV, current:%dmV",
config->voltage_mv, channel->voltage_mv);
ESP_RETURN_ON_FALSE(check_adjustable_constraint_valid, ESP_ERR_INVALID_ARG, TAG,
"can't acquire the channel, already in use by others or not adjustable");
if (out_handle) {
*out_handle = channel;
}
return ESP_OK;
}
esp_err_t esp_ldo_release_channel(esp_ldo_channel_handle_t chan)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
int unit_id = LDO_ID2UNIT(chan->chan_id);
bool is_valid_state = true;
portENTER_CRITICAL(&s_spinlock);
if (chan->ref_cnt <= 0) {
is_valid_state = false;
} else {
chan->ref_cnt--;
if (chan->ref_cnt == 0) {
// if the channel is not in use, we need to disable it
ldo_ll_enable(unit_id, false);
// and reset the ldo voltage
chan->voltage_mv = 0;
chan->flags.adjustable = (s_ldo_channel_adjustable_mask & (1 << unit_id)) != 0;
chan->chan_id = -1;
}
}
portEXIT_CRITICAL(&s_spinlock);
ESP_RETURN_ON_FALSE(is_valid_state, ESP_ERR_INVALID_STATE, TAG, "LDO channel released too many times");
return ESP_OK;
}
esp_err_t esp_ldo_channel_adjust_voltage(esp_ldo_channel_handle_t chan, int voltage_mv)
{
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(chan->flags.adjustable, ESP_ERR_NOT_SUPPORTED, TAG, "LDO is not adjustable");
// check if the voltage is within the valid range
ESP_RETURN_ON_FALSE(voltage_mv >= LDO_LL_MIN_VOLTAGE_MV && voltage_mv <= LDO_LL_MAX_VOLTAGE_MV,
ESP_ERR_INVALID_ARG, TAG, "invalid voltage value: %d", voltage_mv);
// About Thread Safety:
// because there won't be more than 1 consumer for the same adjustable LDO channel (guaranteed by esp_ldo_acquire_channel)
// this function should be thread safe as long as the LDO channel handle is thread safe,
// 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);
return ESP_OK;
}
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++) {
char *buf = line;
size_t len = sizeof(line);
memset(line, 0x0, len);
snprintf(buf, len, "%-5d %-5d %-10d %-12d %-5s\n",
i,
s_ldo_channels[i].chan_id,
s_ldo_channels[i].ref_cnt,
s_ldo_channels[i].voltage_mv,
s_ldo_channels[i].flags.adjustable ? "yes" : "no");
fputs(line, stream);
}
return ESP_OK;
}

View File

@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include "esp_err.h"
#include "hal/ldo_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of LDO regulator channel handle
*/
typedef struct ldo_regulator_channel_t *esp_ldo_channel_handle_t;
/**
* @brief LDO channel configurations
*/
typedef struct {
int chan_id; /*!< You must set the LDO channel ID according to the datasheet, e.g., set it to 1 for LDO_VO1 */
int voltage_mv; /*!< The voltage value to be set to the LDO channel */
/// 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` */
} flags; /*!< Flags for the LDO channel */
} esp_ldo_channel_config_t;
/**
* @brief Acquire an LDO channel with the specified configuration
*
* @note This function can't automatically search a LDO channel for you, you must specify a LDO channel ID manually, based on your schematic.
* @note The same channel can be acquired multiple times in different places of the application code, however,
* if the LDO channel is adjustable, you can't acquire it multiple times, in case user A changes the voltage and breaks the voltage setting of user B.
* @note You should release the channel by `esp_ldo_release_channel` when it's no longer needed.
*
* @param[in] config The configuration of the LDO channel
* @param[out] out_handle The returned LDO channel handle
* @return
* - ESP_OK: Acquire the LDO channel successfully
* - ESP_ERR_INVALID_ARG: Acquire the LDO channel failed due to invalid arguments
* - ESP_FAIL: Acquire the LDO channel failed due to other reasons
*/
esp_err_t esp_ldo_acquire_channel(const esp_ldo_channel_config_t *config, esp_ldo_channel_handle_t *out_handle);
/**
* @brief Release the LDO channel
*
* @param[in] chan The LDO channel handle returned from `esp_ldo_acquire_channel`
* @return
* - ESP_OK: Release the LDO channel successfully
* - ESP_ERR_INVALID_ARG: Release the LDO channel failed due to invalid arguments
* - ESP_ERR_INVALID_STATE: Release the LDO channel failed due to invalid state, e.g., the channel handle is double released
* - ESP_FAIL: Release the LDO channel failed due to other reasons
*/
esp_err_t esp_ldo_release_channel(esp_ldo_channel_handle_t chan);
/**
* @brief Adjust the voltage of the LDO channel
*
* @param[in] chan The LDO channel handle returned from `esp_ldo_acquire_channel`
* @param[in] voltage_mv The voltage value to be set to the LDO channel, in millivolts
* @return
* - ESP_OK: Adjust the voltage of the LDO channel successfully
* - ESP_ERR_INVALID_ARG: Adjust the voltage of the LDO channel failed due to invalid arguments
* - ESP_ERR_NOT_SUPPORTED: Adjust the voltage of the LDO channel failed due to the channel is not adjustable
* - ESP_FAIL: Adjust the voltage of the LDO channel failed due to other reasons
*/
esp_err_t esp_ldo_channel_adjust_voltage(esp_ldo_channel_handle_t chan, int voltage_mv);
/**
* @brief Dump LDO channel status to the specified stream
*
* @param[in] stream IO stream. Can be stdout, stderr, or a file/string stream.
* @return
* - ESP_OK: Dump the LDO channel status successfully
* - ESP_FAIL: Dump the LDO channel status failed
*/
esp_err_t esp_ldo_dump(FILE *stream);
#ifdef __cplusplus
}
#endif

View File

@@ -1,4 +0,0 @@
[mapping:ldo_driver]
archive: libesp_hw_support.a
entries:
esp_ldo: esp_ldo_init_unit_early (noflash)

View File

@@ -9,7 +9,7 @@ set(srcs "test_app_main.c"
"test_key_mgr.c"
)
if(CONFIG_SOC_MULTI_USAGE_LDO_SUPPORTED)
if(CONFIG_SOC_GP_LDO_SUPPORTED)
list(APPEND srcs "test_ldo.c")
endif()

View File

@@ -1,74 +1,89 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "unity.h"
#include "esp_private/esp_ldo.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_ldo_regulator.h"
TEST_CASE("LDO unit early / normal allocation", "[LDO]")
TEST_CASE("LDO channel acquire and release (no adjustable)", "[LDO]")
{
esp_ldo_unit_init_cfg_t init_early_unit_cfg = {
.unit_id = LDO_UNIT_3,
.cfg = {
.voltage_mv = 1800,
},
.flags.enable_unit = true,
esp_ldo_channel_handle_t success_ldo_chans[3] = {};
esp_ldo_channel_handle_t fail_ldo_chan = NULL;
esp_ldo_channel_config_t ldo_chan_config = {
.chan_id = 4,
.voltage_mv = 1800,
};
esp_ldo_unit_handle_t early_unit = esp_ldo_init_unit_early(&init_early_unit_cfg);
TEST_ASSERT(esp_ldo_enable_unit(early_unit) == ESP_ERR_INVALID_STATE);
TEST_ESP_OK(esp_ldo_disable_unit(early_unit));
esp_ldo_unit_handle_t unit = NULL;
esp_ldo_unit_init_cfg_t init_unit_cfg = {
.unit_id = LDO_UNIT_4,
.cfg = {
.voltage_mv = 2500,
},
for (int i = 0; i < 3; i++) {
TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_chan_config, &success_ldo_chans[i]));
}
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;
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) {
.chan_id = 4,
.voltage_mv = 1800,
.flags.adjustable = true,
};
TEST_ESP_OK(esp_ldo_init_unit(&init_unit_cfg, &unit));
TEST_ESP_OK(esp_ldo_enable_unit(unit));
TEST_ESP_OK(esp_ldo_disable_unit(unit));
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_ldo_acquire_channel(&ldo_chan_config, &fail_ldo_chan));
init_unit_cfg.flags.shared_ldo = true;
esp_ldo_unit_handle_t shared_unit = NULL;
TEST_ESP_OK(esp_ldo_init_unit(&init_unit_cfg, &shared_unit));
// 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_OK(esp_ldo_deinit_unit(early_unit));
TEST_ESP_OK(esp_ldo_deinit_unit(shared_unit));
TEST_ESP_OK(esp_ldo_deinit_unit(unit));
for (int i = 0; i < 3; i++) {
TEST_ESP_OK(esp_ldo_release_channel(success_ldo_chans[i]));
}
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_ldo_release_channel(success_ldo_chans[0]));
}
TEST_CASE("LDO unit output", "[LDO][mannual][ignore]")
TEST_CASE("LDO channel acquire and release (adjustable)", "[LDO]")
{
esp_ldo_unit_init_cfg_t early_unit_cfg = {
.unit_id = LDO_UNIT_2,
.cfg = {
.voltage_mv = 1800,
},
.flags.shared_ldo = true,
.flags.enable_unit = true,
esp_ldo_channel_handle_t success_ldo_chan = NULL;
esp_ldo_channel_handle_t fail_ldo_chan = NULL;
esp_ldo_channel_config_t ldo_chan_config = {
.chan_id = 4,
.voltage_mv = 1800,
.flags.adjustable = true,
};
esp_ldo_unit_handle_t early_unit2 = esp_ldo_init_unit_early(&early_unit_cfg);
assert(early_unit2);
TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_chan_config, &success_ldo_chan));
early_unit_cfg.unit_id = LDO_UNIT_3;
early_unit_cfg.cfg.voltage_mv = 3300;
esp_ldo_unit_handle_t early_unit3 = esp_ldo_init_unit_early(&early_unit_cfg);
assert(early_unit3);
// can't acquire multiple handles for the same adjustable channel
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_ldo_acquire_channel(&ldo_chan_config, &fail_ldo_chan));
// even we acquire another copy as non-adjustable, it's still not allowed
ldo_chan_config.flags.adjustable = false;
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_ldo_acquire_channel(&ldo_chan_config, &fail_ldo_chan));
early_unit_cfg.unit_id = LDO_UNIT_4;
early_unit_cfg.cfg.voltage_mv = 1100;
esp_ldo_unit_handle_t early_unit4 = esp_ldo_init_unit_early(&early_unit_cfg);
assert(early_unit4);
// can change voltage for an adjustable channel
TEST_ESP_OK(esp_ldo_channel_adjust_voltage(success_ldo_chan, 3300));
TEST_ESP_OK(esp_ldo_release_channel(success_ldo_chan));
}
esp_ldo_usage_dump(stdout);
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,
};
TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_chan_config, &success_ldo_chans[0]));
ldo_chan_config.chan_id = 3;
ldo_chan_config.voltage_mv = 2500;
TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_chan_config, &success_ldo_chans[1]));
ldo_chan_config.chan_id = 4;
ldo_chan_config.voltage_mv = 1100;
TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_chan_config, &success_ldo_chans[2]));
esp_ldo_dump(stdout);
while (1) {
vTaskDelay(1);

View File

@@ -6,25 +6,21 @@
#include "unity.h"
#include "test_mipi_dsi_board.h"
#include "esp_private/esp_ldo.h"
#include "esp_ldo_regulator.h"
static esp_ldo_unit_handle_t phy_pwr_unit = NULL;
static esp_ldo_channel_handle_t ldo_phy_chan = NULL;
void test_bsp_enable_dsi_phy_power(void)
{
// Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
esp_ldo_unit_init_cfg_t ldo_cfg = {
.unit_id = TEST_MIPI_DSI_PHY_PWR_LDO_UNIT,
.cfg = {
.voltage_mv = TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
},
esp_ldo_channel_config_t ldo_cfg = {
.chan_id = TEST_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
TEST_ESP_OK(esp_ldo_init_unit(&ldo_cfg, &phy_pwr_unit));
TEST_ESP_OK(esp_ldo_enable_unit(phy_pwr_unit));
TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_cfg, &ldo_phy_chan));
}
void test_bsp_disable_dsi_phy_power(void)
{
TEST_ESP_OK(esp_ldo_disable_unit(phy_pwr_unit));
TEST_ESP_OK(esp_ldo_deinit_unit(phy_pwr_unit));
TEST_ESP_OK(esp_ldo_release_channel(ldo_phy_chan));
}

View File

@@ -20,7 +20,7 @@ extern "C" {
#define MIPI_DSI_LCD_VBP 16
#define MIPI_DSI_LCD_VFP 16
#define TEST_MIPI_DSI_PHY_PWR_LDO_UNIT 3
#define TEST_MIPI_DSI_PHY_PWR_LDO_CHAN 3
#define TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
void test_bsp_enable_dsi_phy_power(void);

View File

@@ -18,7 +18,6 @@
#include "hal/assert.h"
#include "soc/pmu_struct.h"
#ifdef __cplusplus
extern "C" {
#endif
@@ -28,6 +27,11 @@ extern "C" {
*/
#define LDO_LL_UNIT_NUM 4
#define LDO_LL_ADJUSTABLE_CHAN_MASK 0x0F // all the 4 channels can be adjustable
#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
*/

View File

@@ -227,7 +227,7 @@ config SOC_SPI_FLASH_SUPPORTED
bool
default y
config SOC_MULTI_USAGE_LDO_SUPPORTED
config SOC_GP_LDO_SUPPORTED
bool
default y

View File

@@ -85,7 +85,7 @@
#define SOC_SPI_FLASH_SUPPORTED 1
// #define SOC_TOUCH_SENSOR_SUPPORTED 1 //TODO: IDF-7477
// #define SOC_RNG_SUPPORTED 1 //TODO: IDF-6522
#define SOC_MULTI_USAGE_LDO_SUPPORTED 1
#define SOC_GP_LDO_SUPPORTED 1 // General purpose LDO
// #define SOC_PPA_SUPPORTED 1 //TODO: IDF-6878
#define SOC_LIGHT_SLEEP_SUPPORTED 1
// #define SOC_DEEP_SLEEP_SUPPORTED 1 //TODO: IDF-7529