Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions host/usb/src/usb_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Warning: The USB Host Library API is still a beta version and may be subject to
#endif
#endif // SOC_USB_OTG_SUPPORTED

#ifdef CONFIG_ESP_SLEEP_EVENT_CALLBACKS
#include "esp_private/sleep_event.h"
#endif // CONFIG_ESP_SLEEP_EVENT_CALLBACKS

DEFINE_CRIT_SECTION_LOCK_STATIC(host_lock);
#define HOST_ENTER_CRITICAL_ISR() esp_os_enter_critical_isr(&host_lock)
#define HOST_EXIT_CRITICAL_ISR() esp_os_exit_critical_isr(&host_lock)
Expand Down Expand Up @@ -492,6 +496,8 @@ static void get_config_desc_transfer_cb(usb_transfer_t *transfer)
xSemaphoreGive(transfer_done);
}

// ------------------- Power management Related ----------------------

/**
* @brief Automatic suspend timer timer callback
*
Expand Down Expand Up @@ -525,6 +531,104 @@ static void auto_suspend_timer_cb(TimerHandle_t xTimer)
HOST_EXIT_CRITICAL();
}

#ifdef CONFIG_ESP_SLEEP_EVENT_CALLBACKS

/** Max time to wait for async root-port suspend (EP idle → HCD suspend) before light sleep */
#define USB_HOST_LIGHT_SLEEP_SUSPEND_WAIT_MS 500

/**
* @brief Block until hub reports root port suspended, yielding so USB host/hub processing can run.
*
* usb_host_lib_root_port_suspend() only starts the suspend pipeline; root_port_suspended becomes
* true after HCD_PORT_CMD_SUSPEND completes (see hub root_port_req PORT_REQ_SUSPEND).
*
* @param[in] timeout_ticks Max time to wait for root port to suspend, in FreeRTOS ticks
*
* @return
* - ESP_OK: Root port is suspended
* - ESP_ERR_TIMEOUT: Timed out waiting for root port to suspend
*/
static esp_err_t wait_until_root_port_suspended(TickType_t timeout_ticks)
{
const TickType_t start = xTaskGetTickCount();

while (1) {
if (hub_root_is_suspended()) {
return ESP_OK;
}
if ((xTaskGetTickCount() - start) >= timeout_ticks) {
return ESP_ERR_TIMEOUT;
}
vTaskDelay(10);
}
}

/**
* @brief Callback function to enter light sleep event
*
* - The function is called when the system is about to enter light sleep
* - The function suspends the root port and the devices connected to it to lower power consumption during light sleep
* - The function waits until the root port is suspended before allowing the system to enter light sleep
*
* @param[in] user_arg User argument, not used
* @param[in] ext_arg External argument, not used
* @return
* - ESP_OK: Root port successfully suspended, can enter light sleep
* - ESP_ERR_TIMEOUT: Timed out waiting for root port to suspend
* - Other error codes from usb_host_lib_root_port_suspend()
*/
static esp_err_t enter_light_sleep(void *user_arg, void *ext_arg)
{
ESP_EARLY_LOGD(USB_HOST_TAG, "Enter light sleep cb");
ESP_RETURN_ON_ERROR(usb_host_lib_root_port_suspend(), USB_HOST_TAG, "Failed to suspend root port before light sleep");
ESP_RETURN_ON_ERROR(
wait_until_root_port_suspended(pdMS_TO_TICKS(USB_HOST_LIGHT_SLEEP_SUSPEND_WAIT_MS)),
USB_HOST_TAG, "Timed out waiting for root port suspend before light sleep");
return ESP_OK;
}

/**
* @brief Callback function for exit light sleep event
*
* - The function is called when the system wakes up from light sleep
* - The function resumes the root port and the devices connected to it
* to restore normal operation after waking up from light sleep
*
* @param[in] user_arg User argument
* @param[in] ext_arg External argument
* @return
* - ESP_OK: Root port successfully resumed, normal operation restored after light sleep
* - Other error codes from usb_host_lib_root_port_resume()
*/
static esp_err_t exit_light_sleep(void *user_arg, void *ext_arg)
{
ESP_EARLY_LOGD(USB_HOST_TAG, "Exit light sleep cb");
ESP_RETURN_ON_ERROR(usb_host_lib_root_port_resume(), USB_HOST_TAG, "Failed to resume root port after light sleep");
return ESP_OK;
}

/**
* @brief Callback configuration for enter light sleep event
*/
static const esp_sleep_event_cb_config_t enter_light_sleep_cb = {
.cb = enter_light_sleep,
.user_arg = NULL,
.prior = 2,
.next = NULL,
};

/**
* @brief Callback configuration for exit light sleep event
*/
static const esp_sleep_event_cb_config_t exit_light_sleep_cb = {
.cb = exit_light_sleep,
.user_arg = NULL,
.prior = 2,
.next = NULL,
};

#endif // CONFIG_ESP_SLEEP_EVENT_CALLBACKS

// ------------------------------------------------ Library Functions --------------------------------------------------

// ----------------------- Public --------------------------
Expand Down Expand Up @@ -686,6 +790,12 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
ESP_ERROR_CHECK(hub_root_start());
}

// Register light sleep callbacks to automatically suspend/resume the root hub when light sleep is entered/exited
#ifdef CONFIG_ESP_SLEEP_EVENT_CALLBACKS
ESP_ERROR_CHECK(esp_sleep_register_event_callback(SLEEP_EVENT_SW_GOTO_SLEEP, &enter_light_sleep_cb));
ESP_ERROR_CHECK(esp_sleep_register_event_callback(SLEEP_EVENT_SW_EXIT_SLEEP, &exit_light_sleep_cb));
#endif // CONFIG_ESP_SLEEP_EVENT_CALLBACKS

ret = ESP_OK;
return ret;

Expand Down Expand Up @@ -727,6 +837,11 @@ esp_err_t usb_host_uninstall(void)
ESP_ERR_INVALID_STATE);
HOST_EXIT_CRITICAL();

#ifdef CONFIG_ESP_SLEEP_EVENT_CALLBACKS
ESP_ERROR_CHECK(esp_sleep_unregister_event_callback(SLEEP_EVENT_SW_GOTO_SLEEP, &enter_light_sleep));
ESP_ERROR_CHECK(esp_sleep_unregister_event_callback(SLEEP_EVENT_SW_EXIT_SLEEP, &exit_light_sleep));
#endif // CONFIG_ESP_SLEEP_EVENT_CALLBACKS

// Stop the root hub
ESP_ERROR_CHECK(hub_root_stop());

Expand Down Expand Up @@ -936,6 +1051,8 @@ esp_err_t usb_host_lib_root_port_resume(void)
return ret;
}



esp_err_t usb_host_lib_set_auto_suspend(usb_host_lib_auto_suspend_tmr_t timer_type, size_t timer_interval_ms)
{
HOST_ENTER_CRITICAL();
Expand Down
11 changes: 11 additions & 0 deletions host/usb/test/target_test/usb_sleep_modes/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

set(EXTRA_COMPONENT_DIRS "../common")

# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)

project(test_app_usb_sleep_modes)
7 changes: 7 additions & 0 deletions host/usb/test/target_test/usb_sleep_modes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- |

# USB: Sleep modes test application

- USB Host usage with Light sleep
- USB Host usage with Deep sleep
6 changes: 6 additions & 0 deletions host/usb/test/target_test/usb_sleep_modes/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
REQUIRES usb unity common esp_timer
WHOLE_ARCHIVE)
Loading
Loading