mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
refactor(ulp): build the ULP coprocessor code as per-architecture components
A ULP program is built against ulp_riscv, lp_core or ulp_fsm, each stating its own sources, dependencies and memory layout. Sources shared with the driver stay in components/ulp.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# CMake v2 default ULP project for ulp_embed_binary() callers. The ULP runtime
|
||||
# is built from the ulp component graph, and the legacy source list is attached
|
||||
# directly to the executable, matching the CMake v1 link semantics.
|
||||
# CMake v2 default ULP project for ulp_embed_binary() callers. The ULP program
|
||||
# is built against the component for its architecture, and the call-site source
|
||||
# list is linked before it, matching the build system v1 link semantics.
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/ulp_project.cmake)
|
||||
|
||||
if("${ULP_TYPE}" STREQUAL "fsm")
|
||||
@@ -49,7 +49,9 @@ function(__ulp_embed_binary_add_legacy_sources executable)
|
||||
__idf_build_link_whole_archive(${executable} PRIVATE ${legacy_sources_lib} BEFORE)
|
||||
endfunction()
|
||||
|
||||
set(executable_args COMPONENTS ulp)
|
||||
__ulp_arch_component(ulp_component)
|
||||
|
||||
set(executable_args COMPONENTS ${ulp_component})
|
||||
if(NOT BUILD_FSM)
|
||||
list(APPEND executable_args MAPFILE_TARGET ${ULP_APP_NAME}_mapfile)
|
||||
endif()
|
||||
|
||||
@@ -103,11 +103,11 @@ function(ulp_apply_default_sources ulp_app_name)
|
||||
# replaces the layout, swapped in by the wrapper. This is supported for the
|
||||
# LP-core type only.
|
||||
if(BUILD_RISCV)
|
||||
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_riscv.ld.in)
|
||||
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ld/ulp_riscv.ld.in)
|
||||
elseif(BUILD_LP_CORE)
|
||||
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/lp_core_riscv.ld.in)
|
||||
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/subproject/components/lp_core/ld/lp_core_riscv.ld.in)
|
||||
elseif(BUILD_FSM)
|
||||
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_fsm.ld.in)
|
||||
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/subproject/components/ulp_fsm/ld/ulp_fsm.ld.in)
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to determine ULP type. ")
|
||||
endif()
|
||||
@@ -143,24 +143,27 @@ function(ulp_apply_default_sources ulp_app_name)
|
||||
if(BUILD_RISCV)
|
||||
#risc-v ulp uses extra files for building:
|
||||
list(APPEND ULP_S_SOURCES
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/start.S"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_touch.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_gpio.c"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c")
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_vectors.S"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/start.S"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_adc.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_lock.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_uart.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_print.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_i2c.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_utils.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_touch.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_gpio.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ulp_riscv_interrupt.c")
|
||||
|
||||
target_sources(${ulp_app_name} PRIVATE ${ULP_S_SOURCES})
|
||||
#Makes the csr utillies for riscv visible:
|
||||
target_include_directories(${ulp_app_name} PRIVATE "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/include"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/shared/include"
|
||||
"${IDF_PATH}/components/riscv/include")
|
||||
target_link_options(${ulp_app_name} PRIVATE SHELL:-T ${IDF_PATH}/components/ulp/ld/${IDF_TARGET}.peripherals.ld)
|
||||
target_include_directories(${ulp_app_name} PRIVATE
|
||||
"${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/include"
|
||||
"${IDF_PATH}/components/ulp/ulp_riscv/shared/include"
|
||||
"${IDF_PATH}/components/riscv/include")
|
||||
target_link_options(${ulp_app_name}
|
||||
PRIVATE SHELL:-T
|
||||
${IDF_PATH}/components/ulp/subproject/components/ulp_riscv/ld/${IDF_TARGET}.peripherals.ld)
|
||||
target_compile_definitions(${ulp_app_name} PRIVATE IS_ULP_COCPU)
|
||||
target_compile_definitions(${ulp_app_name} PRIVATE ULP_RISCV_REGISTER_OPS)
|
||||
|
||||
@@ -191,31 +194,31 @@ function(ulp_apply_default_sources ulp_app_name)
|
||||
|
||||
elseif(BUILD_LP_CORE)
|
||||
list(APPEND ULP_S_SOURCES
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/start.S"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/vector.S"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/${IDF_TARGET}/vector_table.S"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/start.S"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/vector.S"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/port/${IDF_TARGET}/vector_table.S"
|
||||
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_startup.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_pmp.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_utils.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_print.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_panic.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_interrupt.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_ubsan.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_mailbox.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_startup.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_pmp.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_utils.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_print.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_panic.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_interrupt.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_ubsan.c"
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_mailbox.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c")
|
||||
|
||||
if(CONFIG_SOC_LP_CORE_SUPPORT_I2C)
|
||||
list(APPEND ULP_S_SOURCES
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c")
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_i2c.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_SPI_SUPPORTED)
|
||||
list(APPEND ULP_S_SOURCES
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_spi.c")
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_spi.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
|
||||
@@ -224,20 +227,20 @@ function(ulp_apply_default_sources ulp_app_name)
|
||||
"${IDF_PATH}/components/esp_driver_uart/src/uart_wakeup.c"
|
||||
"${IDF_PATH}/components/esp_hal_uart/uart_hal_iram.c"
|
||||
"${IDF_PATH}/components/esp_hal_uart/uart_hal.c"
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_uart.c")
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_uart.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_MAILBOX_SUPPORTED)
|
||||
list(APPEND ULP_S_SOURCES
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/lp_core_mailbox_impl_hw.c")
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/port/lp_core_mailbox_impl_hw.c")
|
||||
else()
|
||||
list(APPEND ULP_S_SOURCES
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/lp_core_mailbox_impl_sw.c")
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/port/lp_core_mailbox_impl_sw.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
|
||||
list(APPEND ULP_S_SOURCES
|
||||
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_touch.c")
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/lp_core_touch.c")
|
||||
endif()
|
||||
|
||||
set(target_folder ${IDF_TARGET})
|
||||
@@ -257,8 +260,9 @@ function(ulp_apply_default_sources ulp_app_name)
|
||||
endif()
|
||||
|
||||
target_sources(${ulp_app_name} PRIVATE ${ULP_S_SOURCES})
|
||||
target_include_directories(${ulp_app_name} PRIVATE "${IDF_PATH}/components/ulp/lp_core/lp_core/include"
|
||||
"${IDF_PATH}/components/ulp/lp_core/shared/include")
|
||||
target_include_directories(${ulp_app_name} PRIVATE
|
||||
"${IDF_PATH}/components/ulp/subproject/components/lp_core/include"
|
||||
"${IDF_PATH}/components/ulp/lp_core/shared/include")
|
||||
target_compile_definitions(${ulp_app_name} PRIVATE IS_ULP_COCPU)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# The code compiled for the LP core.
|
||||
#
|
||||
# Sources shared with the driver stay in components/ulp and are named by path
|
||||
# from here.
|
||||
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
get_filename_component(ulp_dir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
|
||||
|
||||
set(srcs "start.S"
|
||||
"vector.S"
|
||||
"port/${target}/vector_table.S"
|
||||
"${ulp_dir}/lp_core/shared/ulp_lp_core_memory_shared.c"
|
||||
"lp_core_startup.c"
|
||||
"lp_core_pmp.c"
|
||||
"lp_core_utils.c"
|
||||
"lp_core_print.c"
|
||||
"lp_core_panic.c"
|
||||
"lp_core_interrupt.c"
|
||||
"lp_core_ubsan.c"
|
||||
"lp_core_mailbox.c"
|
||||
"${ulp_dir}/lp_core/shared/ulp_lp_core_critical_section_shared.c")
|
||||
|
||||
set(requires esp_common
|
||||
esp_hal_gpio
|
||||
esp_hal_uart
|
||||
esp_rom
|
||||
hal
|
||||
riscv
|
||||
soc)
|
||||
set(priv_requires esp_hal_pmu
|
||||
esp_hw_support)
|
||||
|
||||
if(CONFIG_SOC_RTC_TIMER_V2 OR CONFIG_SOC_RTC_TIMER_V3)
|
||||
list(APPEND srcs "${ulp_dir}/lp_core/shared/ulp_lp_core_lp_timer_shared.c")
|
||||
list(APPEND priv_requires esp_hal_clock esp_hal_rtc_timer)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_CORE_SUPPORT_I2C)
|
||||
list(APPEND srcs "lp_core_i2c.c")
|
||||
list(APPEND requires esp_hal_i2c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_SPI_SUPPORTED)
|
||||
list(APPEND srcs "lp_core_spi.c")
|
||||
list(APPEND priv_requires esp_hal_gpspi)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
|
||||
list(APPEND srcs
|
||||
"${ulp_dir}/lp_core/shared/ulp_lp_core_lp_uart_shared.c"
|
||||
"lp_core_uart.c")
|
||||
list(APPEND requires esp_driver_uart)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_MAILBOX_SUPPORTED)
|
||||
list(APPEND srcs "port/lp_core_mailbox_impl_hw.c")
|
||||
else()
|
||||
list(APPEND srcs "port/lp_core_mailbox_impl_sw.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
|
||||
list(APPEND srcs "lp_core_touch.c")
|
||||
list(APPEND requires esp_hal_touch_sens)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_ADC_SUPPORTED)
|
||||
list(APPEND srcs "${ulp_dir}/lp_core/shared/ulp_lp_core_lp_adc_shared.c")
|
||||
list(APPEND requires esp_adc esp_hal_ana_conv)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_VAD_SUPPORTED)
|
||||
list(APPEND srcs "${ulp_dir}/lp_core/shared/ulp_lp_core_lp_vad_shared.c")
|
||||
list(APPEND requires esp_driver_i2s)
|
||||
endif()
|
||||
|
||||
# The ULP subproject in build system v1 adds these sources straight to the
|
||||
# executable, so a strong handler such as ulp_lp_core_panic_handler is not
|
||||
# dropped in favour of a weak default from another runtime object.
|
||||
# WHOLE_ARCHIVE keeps that behaviour here.
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "${ulp_dir}/ulp_common/include"
|
||||
"${ulp_dir}/lp_core/include"
|
||||
"include"
|
||||
"${ulp_dir}/lp_core/shared/include"
|
||||
REQUIRES ${requires}
|
||||
PRIV_REQUIRES ${priv_requires}
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
# -D__ASSEMBLER__ keeps the SoC headers to their macro-only form.
|
||||
set(ulp_ld_flags "-D__ASSEMBLER__ -I\"${CMAKE_CURRENT_SOURCE_DIR}/ld\"")
|
||||
|
||||
# lp_core_riscv.ld includes soc/soc.h and esp_common headers, resolved from the
|
||||
# linked component graph. It also includes the esp_system ld snippets
|
||||
# (ld.common, ld.hp_mem_defs); those live in that component's ld/ directory,
|
||||
# which is not a public include dir, so pass it via FLAGS.
|
||||
idf_component_get_property(esp_system_dir esp_system COMPONENT_DIR)
|
||||
set(lp_core_ld_flags "${ulp_ld_flags} -I\"${esp_system_dir}/ld\" -I\"${esp_system_dir}/ld/${target}\"")
|
||||
|
||||
# A user-supplied LINKER layout replaces the default one composed into
|
||||
# lp_core_riscv.ld.in. It reaches this child project as LP_CORE_LINKER_SCRIPT
|
||||
# (passed by __setup_ulp_project, same as the build system v1 path). Hand it to
|
||||
# the wrapper as the LP_CORE_LINKER_INCLUDE macro it #includes. The escaped
|
||||
# quotes survive separate_arguments() in the linker-script preprocessor so the
|
||||
# macro expands to a quoted header-name token ("/abs/path.ld").
|
||||
if(LP_CORE_LINKER_SCRIPT)
|
||||
message(STATUS "Using custom LP-core linker layout: ${LP_CORE_LINKER_SCRIPT}")
|
||||
string(APPEND lp_core_ld_flags " -DLP_CORE_LINKER_INCLUDE=\\\"${LP_CORE_LINKER_SCRIPT}\\\"")
|
||||
endif()
|
||||
|
||||
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/lp_core_riscv.ld.in" MEMORY
|
||||
FLAGS "${lp_core_ld_flags}")
|
||||
|
||||
target_link_options(${COMPONENT_LIB} INTERFACE
|
||||
-nostartfiles
|
||||
-Wl,--gc-sections
|
||||
-Wl,--no-warn-rwx-segments
|
||||
LINKER:-u,main)
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB} PUBLIC IS_ULP_COCPU)
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough)
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/gpio_types.h"
|
||||
#include "hal/rtc_io_ll.h"
|
||||
|
||||
#define RTCIO_OUTPUT_NORMAL _Pragma ("GCC warning \"'RTCIO_OUTPUT_NORMAL' macro is deprecated\"") RTCIO_LL_OUTPUT_NORMAL
|
||||
#define RTCIO_OUTPUT_OD _Pragma ("GCC warning \"'RTCIO_OUTPUT_OD' macro is deprecated\"") RTCIO_LL_OUTPUT_OD
|
||||
|
||||
#if SOC_RTCIO_PIN_COUNT > 16
|
||||
#error "lp_io_num_t in ulp_lp_core_gpio.h supports up to LP_IO_NUM_15. Update enum and for this chip."
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LP_IO_NUM_0 = 0, /*!< GPIO0, input and output */
|
||||
#if SOC_RTCIO_PIN_COUNT > 1
|
||||
LP_IO_NUM_1 = 1, /*!< GPIO1, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 2
|
||||
LP_IO_NUM_2 = 2, /*!< GPIO2, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 3
|
||||
LP_IO_NUM_3 = 3, /*!< GPIO3, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 4
|
||||
LP_IO_NUM_4 = 4, /*!< GPIO4, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 5
|
||||
LP_IO_NUM_5 = 5, /*!< GPIO5, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 6
|
||||
LP_IO_NUM_6 = 6, /*!< GPIO6, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 7
|
||||
LP_IO_NUM_7 = 7, /*!< GPIO7, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 8
|
||||
LP_IO_NUM_8 = 8, /*!< GPIO8, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 9
|
||||
LP_IO_NUM_9 = 9, /*!< GPIO9, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 10
|
||||
LP_IO_NUM_10 = 10, /*!< GPIO10, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 11
|
||||
LP_IO_NUM_11 = 11, /*!< GPIO11, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 12
|
||||
LP_IO_NUM_12 = 12, /*!< GPIO12, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 13
|
||||
LP_IO_NUM_13 = 13, /*!< GPIO13, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 14
|
||||
LP_IO_NUM_14 = 14, /*!< GPIO14, input and output */
|
||||
#endif
|
||||
#if SOC_RTCIO_PIN_COUNT > 15
|
||||
LP_IO_NUM_15 = 15, /*!< GPIO15, input and output */
|
||||
#endif
|
||||
} lp_io_num_t;
|
||||
|
||||
/** @cond */
|
||||
/// for backward compatible
|
||||
typedef gpio_int_type_t lp_io_intr_type_t;
|
||||
#define LP_IO_INTR_DISABLE GPIO_INTR_DISABLE
|
||||
#define LP_IO_INTR_POSEDGE GPIO_INTR_POSEDGE
|
||||
#define LP_IO_INTR_NEGEDGE GPIO_INTR_NEGEDGE
|
||||
#define LP_IO_INTR_ANYEDGE GPIO_INTR_ANYEDGE
|
||||
#define LP_IO_INTR_LOW_LEVEL GPIO_INTR_LOW_LEVEL
|
||||
#define LP_IO_INTR_HIGH_LEVEL GPIO_INTR_HIGH_LEVEL
|
||||
/** @endcond */
|
||||
|
||||
/**
|
||||
* @brief Initialize a rtcio pin
|
||||
* @note If IO is used in LP application, `rtc_gpio_init` must be called at least once
|
||||
* for the using IO before loading LP core firmware in HP Code.
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to initialize
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_init(lp_io_num_t lp_io_num)
|
||||
{
|
||||
#if SOC_LP_IO_CLOCK_IS_INDEPENDENT
|
||||
_rtcio_ll_enable_io_clock(true);
|
||||
#endif
|
||||
rtcio_ll_function_select(lp_io_num, RTCIO_LL_FUNC_RTC);
|
||||
rtcio_ll_iomux_func_sel(lp_io_num, RTCIO_LL_PIN_FUNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable output
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to enable output for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_output_enable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
rtcio_ll_output_enable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable output
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to disable output for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_output_disable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
rtcio_ll_output_disable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable input
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to enable input for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_input_enable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
rtcio_ll_input_enable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable input
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to disable input for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_input_disable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
rtcio_ll_input_disable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set rtcio output level
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to set the output level for
|
||||
* @param level 0: output low; 1: output high.
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_set_level(lp_io_num_t lp_io_num, uint8_t level)
|
||||
{
|
||||
rtcio_ll_set_level(lp_io_num, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get rtcio output level
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to get the output level for
|
||||
*/
|
||||
static inline uint32_t ulp_lp_core_gpio_get_level(lp_io_num_t lp_io_num)
|
||||
{
|
||||
return rtcio_ll_get_level(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set rtcio output mode
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to set the output mode for
|
||||
* @param mode RTCIO_LL_OUTPUT_NORMAL: normal, RTCIO_LL_OUTPUT_OD: open drain
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_set_output_mode(lp_io_num_t lp_io_num, rtcio_ll_out_mode_t mode)
|
||||
{
|
||||
rtcio_ll_output_mode_set(lp_io_num, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable internal pull-up resistor
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to enable pull-up for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_pullup_enable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
/* Enable internal weak pull-up */
|
||||
rtcio_ll_pullup_enable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable internal pull-up resistor
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to disable pull-up for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_pullup_disable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
/* Disable internal weak pull-up */
|
||||
rtcio_ll_pullup_disable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable internal pull-down resistor
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to enable pull-down for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_pulldown_enable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
/* Enable internal weak pull-down */
|
||||
rtcio_ll_pulldown_enable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable internal pull-down resistor
|
||||
*
|
||||
* @param lp_io_num The rtc io pin to disable pull-down for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_pulldown_disable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
/* Enable internal weak pull-down */
|
||||
rtcio_ll_pulldown_disable(lp_io_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable interrupt for lp io pin
|
||||
*
|
||||
* @param lp_io_num The lp io pin to enable interrupt for
|
||||
* @param intr_type The interrupt type to enable
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_intr_enable(lp_io_num_t lp_io_num, gpio_int_type_t intr_type)
|
||||
{
|
||||
rtcio_ll_intr_enable(lp_io_num, intr_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear interrupt status for all lp io
|
||||
*
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_clear_intr_status(void)
|
||||
{
|
||||
rtcio_ll_clear_interrupt_status();
|
||||
#if SOC_RTC_GPIO_EDGE_WAKEUP_SUPPORTED
|
||||
for (int rtcio_num = 0; rtcio_num < SOC_RTCIO_PIN_COUNT; rtcio_num++) {
|
||||
rtcio_ll_clear_edge_wakeup_latch(rtcio_num);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable wake up for lp io pin
|
||||
*
|
||||
* @param lp_io_num The lp io pin to enable the wake up for
|
||||
* @param intr_type The interrupt type to enable wake up for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_wakeup_enable(lp_io_num_t lp_io_num, gpio_int_type_t intr_type)
|
||||
{
|
||||
rtcio_ll_wakeup_enable(lp_io_num, intr_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable wake up for lp io pin
|
||||
*
|
||||
* @param lp_io_num The lp io pin to disable the wake up for
|
||||
*/
|
||||
static inline void ulp_lp_core_gpio_wakeup_disable(lp_io_num_t lp_io_num)
|
||||
{
|
||||
rtcio_ll_wakeup_disable(lp_io_num);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hal/i2c_types.h"
|
||||
#include "esp_err.h"
|
||||
#include "hal/i2c_ll.h"
|
||||
|
||||
/**
|
||||
* @brief Read from I2C device
|
||||
*
|
||||
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
|
||||
* before invoking this API.
|
||||
*
|
||||
* @param lp_i2c_num LP I2C port number
|
||||
* @param device_addr I2C device address (7-bit)
|
||||
* @param data_rd Buffer to hold data to be read
|
||||
* @param size Size of data to be read in bytes
|
||||
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*
|
||||
* @note the LP I2C does not support 10-bit I2C device addresses.
|
||||
* @note the LP I2C port number is ignored at the moment.
|
||||
*/
|
||||
esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
|
||||
uint8_t *data_rd, size_t size,
|
||||
int32_t cycles_to_wait);
|
||||
|
||||
/**
|
||||
* @brief Write to I2C device
|
||||
*
|
||||
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
|
||||
* before invoking this API.
|
||||
*
|
||||
* @param lp_i2c_num LP I2C port number
|
||||
* @param device_addr I2C device address (7-bit)
|
||||
* @param data_wr Buffer which holds the data to be written
|
||||
* @param size Size of data to be written in bytes
|
||||
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*
|
||||
* @note the LP I2C does not support 10-bit I2C device addresses.
|
||||
* @note the LP I2C port number is ignored at the moment.
|
||||
*/
|
||||
esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
|
||||
const uint8_t *data_wr, size_t size,
|
||||
int32_t cycles_to_wait);
|
||||
|
||||
/**
|
||||
* @brief Write to and then read from an I2C device in a single transaction
|
||||
*
|
||||
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
|
||||
* before invoking this API.
|
||||
*
|
||||
* @param lp_i2c_num LP I2C port number
|
||||
* @param device_addr I2C device address (7-bit)
|
||||
* @param data_wr Buffer which holds the data to be written
|
||||
* @param write_size Size of data to be written in bytes
|
||||
* @param data_rd Buffer to hold data to be read
|
||||
* @param read_size Size of data to be read in bytes
|
||||
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*
|
||||
* @note the LP I2C does not support 10-bit I2C device addresses.
|
||||
* @note the LP I2C port number is ignored at the moment.
|
||||
*/
|
||||
esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
|
||||
const uint8_t *data_wr, size_t write_size,
|
||||
uint8_t *data_rd, size_t read_size,
|
||||
int32_t cycles_to_wait);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable ACK checking by the LP_I2C controller during write operations
|
||||
*
|
||||
* When ACK checking is enabled, the hardware will check the ACK/NACK level received during write
|
||||
* operations against the expected ACK/NACK level. If the received ACK/NACK level does not match the
|
||||
* expected ACK/NACK level then the hardware will generate the I2C_NACK_INT and a STOP condition
|
||||
* will be generated to stop the data transfer.
|
||||
*
|
||||
* @note ACK checking is enabled by default
|
||||
*
|
||||
* @param lp_i2c_num LP I2C port number
|
||||
* @param ack_check_en true: enable ACK check
|
||||
* false: disable ACK check
|
||||
*
|
||||
* @note the LP I2C port number is ignored at the moment.
|
||||
*/
|
||||
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en);
|
||||
|
||||
#if SOC_LP_CORE_SUPPORT_I2C
|
||||
/**
|
||||
* @brief Enable LP I2C master-related interrupts at the peripheral
|
||||
*
|
||||
* Enables the same interrupt sources used by the LP I2C master driver (see I2C_LL_MASTER_EVENT_INTR).
|
||||
*
|
||||
* @param lp_i2c_num Must be the LP I2C port (e.g. LP_I2C_NUM_0), not an HP I2C port.
|
||||
* @param mask Interrupt mask needs to be enabled
|
||||
*/
|
||||
static inline void ulp_lp_core_lp_i2c_intr_enable(i2c_port_t lp_i2c_num, uint32_t mask)
|
||||
{
|
||||
HAL_ASSERT(lp_i2c_num == LP_I2C_NUM_0);
|
||||
i2c_ll_enable_intr_mask(I2C_LL_GET_HW(lp_i2c_num), mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable LP I2C master-related interrupts at the peripheral
|
||||
*
|
||||
* @param lp_i2c_num Must be the LP I2C port (e.g. LP_I2C_NUM_0), not an HP I2C port.
|
||||
* @param mask Interrupt mask needs to be disabled
|
||||
*/
|
||||
static inline void ulp_lp_core_lp_i2c_intr_disable(i2c_port_t lp_i2c_num, uint32_t mask)
|
||||
{
|
||||
HAL_ASSERT(lp_i2c_num == LP_I2C_NUM_0);
|
||||
i2c_ll_disable_intr_mask(I2C_LL_GET_HW(lp_i2c_num), mask);
|
||||
}
|
||||
#endif /* SOC_LP_CORE_SUPPORT_I2C */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
|
||||
#define LP_CORE_ISR_ATTR // On chips with just a single interrupt entry point registers are saved by us before calling the ISR
|
||||
#else
|
||||
#define LP_CORE_ISR_ATTR __attribute__((interrupt, section(".text.handlers")))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Available interrupt handlers for the low power core are as follows:
|
||||
*
|
||||
* ulp_lp_core_lp_io_intr_handler(void);
|
||||
* ulp_lp_core_lp_i2c_intr_handler(void);
|
||||
* ulp_lp_core_lp_uart_intr_handler(void);
|
||||
* ulp_lp_core_lp_timer_intr_handler(void);
|
||||
* ulp_lp_core_lp_pmu_intr_handler(void);
|
||||
* ulp_lp_core_lp_spi_intr_handler(void);
|
||||
* ulp_lp_core_trng_intr_handler(void);
|
||||
* ulp_lp_core_lp_adc_intr_handler(void);
|
||||
* ulp_lp_core_lp_touch_intr_handler(void);
|
||||
* ulp_lp_core_tsens_intr_handler(void);
|
||||
* ulp_lp_core_efuse_intr_handler(void);
|
||||
* ulp_lp_core_lp_sysreg_intr_handler(void);
|
||||
* ulp_lp_core_lp_ana_peri_intr_handler(void);
|
||||
* ulp_lp_core_mailbox_intr_handler(void);
|
||||
* ulp_lp_core_lp_wdt_intr_handler(void);
|
||||
* ulp_lp_core_lp_rtc_intr_handler(void);
|
||||
* ulp_lp_core_lp_pdma_intr_handler(void);
|
||||
* ulp_lp_core_sw_intr_handler(void);
|
||||
*
|
||||
* Not all handlers are available on all chips. Please refer to the Technical Reference Manual for your chip for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Enables interrupts globally for the low power core
|
||||
*
|
||||
*/
|
||||
void ulp_lp_core_intr_enable(void);
|
||||
|
||||
/**
|
||||
* @brief Disables interrupts globally for the low power core
|
||||
*
|
||||
*/
|
||||
void ulp_lp_core_intr_disable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
typedef intptr_t lp_message_t;
|
||||
|
||||
/**
|
||||
* @typedef lp_core_mailbox_callback_t
|
||||
* @brief Represents a callback function type triggered when a message is received in asynchronous mode.
|
||||
*
|
||||
* Note: There is no guarantee regarding the order in which messages are processed between synchronous
|
||||
* and asynchronous modes.
|
||||
*
|
||||
* @param msg The received message, represented as an `lp_message_t` object.
|
||||
*/
|
||||
typedef void (*lp_core_mailbox_callback_t)(lp_message_t msg);
|
||||
|
||||
/**
|
||||
* @brief Mailbox structure.
|
||||
*/
|
||||
typedef struct lp_mailbox_t* lp_mailbox_t;
|
||||
|
||||
/**
|
||||
* @brief Configuration structure for the mailbox
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t dummy; /*!< Placeholder for future field, ignored for now */
|
||||
} lp_mailbox_config_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize a mailbox.
|
||||
*
|
||||
* @param mailbox Pointer to the mailbox instance to initialize.
|
||||
* @param config Pointer to the configuration structure, can be NULL to use default values.
|
||||
*
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t lp_core_mailbox_init(lp_mailbox_t *mailbox, lp_mailbox_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize a mailbox.
|
||||
*
|
||||
* @param mailbox Pointer to the mailbox instance to deinitialize.
|
||||
*
|
||||
*/
|
||||
void lp_core_mailbox_deinit(lp_mailbox_t mailbox);
|
||||
|
||||
/**
|
||||
* @brief Send a message through the mailbox.
|
||||
*
|
||||
* @note This function will put a message in the mailbox and wait for a receiver to get it.
|
||||
*
|
||||
* @param mailbox Pointer to the mailbox instance.
|
||||
* @param msg Message to be sent.
|
||||
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* ESP_INVALID_ARGUMENT if `mailbox` is NULL.
|
||||
* ESP_TIMEOUT if timeout was reached and no receiver took the message.
|
||||
*/
|
||||
esp_err_t lp_core_mailbox_send(lp_mailbox_t mailbox, lp_message_t msg, int32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Send a message through the mailbox asynchronously.
|
||||
*
|
||||
* @note Unlike the synchronous send function, this function does not wait for a receiver to acknowledge
|
||||
* the message.
|
||||
*
|
||||
* @param mailbox Pointer to the mailbox instance.
|
||||
* @param msg Message to be sent.
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* ESP_INVALID_ARGUMENT if `mailbox` is NULL.
|
||||
*/
|
||||
esp_err_t lp_core_mailbox_send_async(lp_mailbox_t mailbox, lp_message_t msg);
|
||||
|
||||
/**
|
||||
* @brief Receive a message from the mailbox.
|
||||
*
|
||||
* @note This function waits for a message to be sent by a sender.
|
||||
*
|
||||
* @param mailbox Pointer to the mailbox instance.
|
||||
* @param msg Pointer to store the received message.
|
||||
* @param timeout Timeout for the operation in CPU cycles. Use -1 to wait indefinitely.
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* ESP_INVALID_ARGUMENT if `mailbox` is NULL.
|
||||
* ESP_TIMEOUT if the timeout expires without receiving a message.
|
||||
*/
|
||||
esp_err_t lp_core_mailbox_receive(lp_mailbox_t mailbox, lp_message_t* msg, int32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Receive a message from the mailbox.
|
||||
*
|
||||
* @param mailbox Pointer to the mailbox instance.
|
||||
* @param count Number of messages to receive asynchronously. To terminate it at anytime,
|
||||
* use lp_core_mailbox_receive_async_cancel() to terminate it.
|
||||
* @param cb Callback invoked as soon as a message is received. Please note that this function
|
||||
* will be invoked from an ISR context.
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* ESP_INVALID_ARGUMENT if `mailbox` or `cb` is NULL.
|
||||
*/
|
||||
esp_err_t lp_core_mailbox_receive_async(lp_mailbox_t mailbox, uint32_t count, lp_core_mailbox_callback_t cb);
|
||||
|
||||
/**
|
||||
* @brief Cancel an ongoing asynchronous mailbox receive.
|
||||
*
|
||||
* This stops any pending lp_core_mailbox_receive_async().
|
||||
*
|
||||
* @param mailbox Pointer to the mailbox instance.
|
||||
* @param remaining Optional pointer to store the number of messages left to receive.
|
||||
*
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_STATE if no async receive was active.
|
||||
*/
|
||||
esp_err_t lp_core_mailbox_receive_async_cancel(lp_mailbox_t mailbox, uint32_t* remaining);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_ULP_LP_CORE_MEMPROT
|
||||
void lp_core_configure_pmp(void);
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/**
|
||||
* @brief Print from the LP core
|
||||
*
|
||||
* @note This function uses the LP UART peripheral to enable prints.The LP UART must be initialized with lp_core_uart_init() before using this function.
|
||||
* @note This function is not a standard printf function and may not support all format specifiers or special characters.
|
||||
*
|
||||
* @param format string to be printed
|
||||
* @param ... variable argument list
|
||||
*
|
||||
*/
|
||||
#if CONFIG_ULP_ROM_PRINT_ENABLE
|
||||
extern int ets_printf(const char* format, ...);
|
||||
#define lp_core_printf ets_printf
|
||||
#else
|
||||
//TODO: Change return type from void to int in IDF 6.0
|
||||
void lp_core_printf(const char* format, ...);
|
||||
#endif /* CONFIG_ULP_ROM_PRINT_ENABLE */
|
||||
|
||||
#if CONFIG_ULP_ROM_PRINT_ENABLE
|
||||
/**
|
||||
* @brief Install LP ROM UART printf function as standard putc handler to enable prints
|
||||
*
|
||||
* @note This function must be called before printing anything when the LP core boots from LP ROM but does not install
|
||||
* putc handler. This is possible when the LP ROM is instructed so by setting bit#1 in the LP_SYSTEM_REG_LP_STORE9_REG register.
|
||||
* Disabling ROM UART init is default behavior in IDF, since the clock configured by the ROM code for UART (XTAL) is normally
|
||||
* powered down during sleep.
|
||||
*/
|
||||
extern void ets_install_uart_printf(void);
|
||||
#define lp_core_install_uart_print ets_install_uart_printf
|
||||
#endif /* CONFIG_ULP_ROM_PRINT_ENABLE */
|
||||
|
||||
/**
|
||||
* @brief Print a single character from the LP core
|
||||
*
|
||||
* @param c character to be printed
|
||||
*/
|
||||
void lp_core_print_char(char c);
|
||||
|
||||
/**
|
||||
* @brief Print a null-terminated string from the LP core
|
||||
*
|
||||
* @param str null-terminated string to be printed
|
||||
*/
|
||||
void lp_core_print_str(const char *str);
|
||||
|
||||
/**
|
||||
* @brief Print a hex value from the LP core
|
||||
*
|
||||
* @param h hex value to be printed
|
||||
*
|
||||
* @note Does not print '0x', only the digits (will always print 8 digits)
|
||||
*/
|
||||
void lp_core_print_hex(int h);
|
||||
|
||||
/**
|
||||
* @brief Print a two digit integer from the LP-Core
|
||||
*
|
||||
* @param d integer to be printed
|
||||
*/
|
||||
void lp_core_print_dec_two_digits(int d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* The LP SPI bus identifier to initiate a transaction on.
|
||||
*/
|
||||
typedef uint32_t lp_spi_bus_t;
|
||||
|
||||
/**
|
||||
* This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t tx_length; /*!< Number of bytes to transmit. Must be 0 when ``tx_buffer`` is NULL. */
|
||||
uint32_t rx_length; /*!< Number of bytes to receive. Must be 0 when ``rx_buffer`` is NULL. */
|
||||
const void *tx_buffer; /*!< Pointer to the transmit buffer, or NULL. */
|
||||
void *rx_buffer; /*!< Pointer to the receive buffer, or NULL. */
|
||||
lp_spi_bus_t bus; /*!< The LP SPI bus to transmit the data on */
|
||||
// The following are only used in master mode transactions
|
||||
int command; /*!< Command data, of which the length is set in the ``command_bits`` field of this structure. */
|
||||
uint32_t address; /*!< Address data, of which the length is set in the ``address_bits`` field of this structure. */
|
||||
uint8_t command_bits; /*!< Default amount of bits in command phase */
|
||||
uint8_t address_bits; /*!< Default amount of bits in address phase */
|
||||
uint8_t dummy_bits; /*!< Amount of dummy bits to insert between address and data phase. */
|
||||
} lp_spi_transaction_t;
|
||||
|
||||
/**
|
||||
* @brief Initiate an LP SPI master transaction.
|
||||
*
|
||||
* @param trans_desc LP SPI transaction configuration descriptor
|
||||
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
* ESP_ERR_INVALID_ARG if the configuration is invalid
|
||||
* ESP_ERR_INVALID_STATE if a previous transaction is still in progress
|
||||
* ESP_ERR_TIMEOUT when the operation times out
|
||||
*/
|
||||
esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait);
|
||||
|
||||
/**
|
||||
* @brief Initiate an LP SPI slave transaction.
|
||||
*
|
||||
* @param trans_desc LP SPI transaction configuration descriptor
|
||||
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
* ESP_ERR_INVALID_ARG if the configuration is invalid
|
||||
* ESP_ERR_INVALID_STATE if a previous transaction is still in progress
|
||||
* ESP_ERR_TIMEOUT when the operation times out
|
||||
*/
|
||||
esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait);
|
||||
|
||||
/**
|
||||
* @brief Preload the LP SPI slave's TX data and arm the peripheral, then return.
|
||||
*
|
||||
* Loads `trans_desc->tx_buffer` into the LP-SPI W0..W15 data buffer,
|
||||
* programs the bit length, and starts the slave user phase. The call
|
||||
* does not block on the master's SCK; the peripheral is left armed and
|
||||
* will sample/drive the bus as soon as the master starts clocking.
|
||||
*
|
||||
* Pair with `lp_core_lp_spi_slave_wait()` to block on completion and
|
||||
* drain the RX buffer. Calling `lp_core_lp_spi_slave_arm()` again while
|
||||
* a previous arm has not been waited on returns `ESP_ERR_INVALID_STATE`.
|
||||
*
|
||||
* This is the LP-SPI counterpart of `spi_slave_queue_trans()`.
|
||||
*
|
||||
* @param trans_desc LP SPI transaction configuration descriptor.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
* ESP_ERR_INVALID_ARG if the configuration is invalid
|
||||
* ESP_ERR_INVALID_STATE if a previous transaction is still in progress
|
||||
*/
|
||||
esp_err_t lp_core_lp_spi_slave_arm(lp_spi_transaction_t *trans_desc);
|
||||
|
||||
/**
|
||||
* @brief Wait for a previously-armed LP SPI slave transaction to complete.
|
||||
*
|
||||
* Must be paired with `lp_core_lp_spi_slave_arm()` using the same `trans_desc`.
|
||||
*
|
||||
* This is the LP-SPI counterpart of `spi_slave_get_trans_result()`.
|
||||
*
|
||||
* @param trans_desc LP SPI transaction configuration descriptor.
|
||||
* @param cycles_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
* ESP_ERR_INVALID_ARG if the configuration is invalid
|
||||
* ESP_ERR_TIMEOUT when the operation times out
|
||||
*/
|
||||
esp_err_t lp_core_lp_spi_slave_wait(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Read raw data of touch sensor on the LP core
|
||||
*
|
||||
* @param[in] touch_num Touch pad index
|
||||
* @param[out] raw_data Raw data buffer pointer to accept touch sensor raw value,
|
||||
* buffer size should be equal to the number of enabled sampling frequencies
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data);
|
||||
|
||||
/**
|
||||
* @brief Read benchmark of touch sensor on the LP core
|
||||
*
|
||||
* @param[in] touch_num Touch pad index
|
||||
* @param[out] benchmark Benchmark data buffer pointer to accept touch sensor benchmark value,
|
||||
* buffer size should be equal to the number of enabled sampling frequencies
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark);
|
||||
|
||||
/**
|
||||
* @brief Read the filtered (smoothened) touch sensor data on the LP core
|
||||
*
|
||||
* @param[in] touch_num Touch pad index
|
||||
* @param[out] smooth_data Smooth data buffer pointer to accept touch sensor smooth value,
|
||||
* buffer size should be equal to the number of enabled sampling frequencies
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data);
|
||||
|
||||
/**
|
||||
* @brief Force reset benchmark to raw data of touch sensor.
|
||||
*
|
||||
* @param[in] touch_num Touch pad index
|
||||
* @param[in] mask Mask of the sample freuqencies that need to be reset
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_reset_benchmark(int touch_num, uint32_t mask);
|
||||
|
||||
/**
|
||||
* @brief Read raw data of touch sensor sleep channel on the LP core
|
||||
*
|
||||
* @param[in] touch_num Touch pad index that has been registered as sleep channel
|
||||
* @param[out] raw_data Raw data buffer pointer to accept touch sensor raw value,
|
||||
* buffer size should be equal to the number of enabled sampling frequencies
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data);
|
||||
|
||||
/**
|
||||
* @brief Read benchmark of touch sensor sleep channel on the LP core
|
||||
*
|
||||
* @param[in] touch_num Touch pad index that has been registered as sleep channel
|
||||
* @param[out] benchmark Benchmark data buffer pointer to accept touch sensor benchmark value,
|
||||
* buffer size should be equal to the number of enabled sampling frequencies
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark);
|
||||
|
||||
/**
|
||||
* @brief Read the filtered (smoothened) touch sensor sleep channel data on the LP core
|
||||
*
|
||||
* @param[in] touch_num Touch pad index that has been registered as sleep channel
|
||||
* @param[out] smooth_dat Smooth data buffer pointer to accept touch sensor smooth value,
|
||||
* buffer size should be equal to the number of enabled sampling frequencies
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data);
|
||||
|
||||
/**
|
||||
* @brief Reset benchmark of touch sensor sleep channel.
|
||||
* @param[in] mask Mask of the sample freuqencies that need to be reset
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_reset_benchmark(uint32_t mask);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "hal/uart_types.h"
|
||||
#include "hal/uart_ll.h"
|
||||
|
||||
#if SOC_LP_UART_SUPPORTED
|
||||
/**
|
||||
* @brief LP UART peripheral interrupt enable
|
||||
* @param uart_num UART port number
|
||||
* @param mask Interrupt mask needs to be enabled
|
||||
*/
|
||||
static inline void ulp_lp_core_lp_uart_intr_enable(uart_port_t uart_num, uint32_t mask)
|
||||
{
|
||||
HAL_ASSERT(uart_num == LP_UART_NUM_0);
|
||||
uart_ll_ena_intr_mask(UART_LL_GET_HW(uart_num), mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief LP UART peripheral interrupt disable
|
||||
* @param uart_num UART port number
|
||||
* @param mask Interrupt mask needs to be disabled
|
||||
*/
|
||||
static inline void ulp_lp_core_lp_uart_intr_disable(uart_port_t uart_num, uint32_t mask)
|
||||
{
|
||||
HAL_ASSERT(uart_num == LP_UART_NUM_0);
|
||||
uart_ll_disable_intr_mask(UART_LL_GET_HW(uart_num), mask);
|
||||
}
|
||||
#endif /* SOC_LP_UART_SUPPORTED */
|
||||
|
||||
/**
|
||||
* @brief Send data to the LP UART port if there is space available in the Tx FIFO
|
||||
*
|
||||
* This function will not wait for enough space in the Tx FIFO to be available.
|
||||
* It will just fill the available Tx FIFO slots and return when the FIFO is full.
|
||||
* If there are no empty slots in the Tx FIFO, this function will not write any data.
|
||||
*
|
||||
* @param lp_uart_num LP UART port number
|
||||
* @param src data buffer address
|
||||
* @param size data length to send
|
||||
*
|
||||
* @return - (-1) Error
|
||||
* - OTHERS (>=0) The number of bytes pushed to the Tx FIFO
|
||||
*/
|
||||
int lp_core_uart_tx_chars(uart_port_t lp_uart_num, const void *src, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Write data to the LP UART port
|
||||
*
|
||||
* This function will write data to the Tx FIFO. If a timeout value is configured, this function will timeout once the number of CPU cycles expire.
|
||||
*
|
||||
* @param lp_uart_num LP UART port number
|
||||
* @param src data buffer address
|
||||
* @param size data length to send
|
||||
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t lp_core_uart_write_bytes(uart_port_t lp_uart_num, const void *src, size_t size, int32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Read data from the LP UART port
|
||||
*
|
||||
* This function will read data from the Rx FIFO. If a timeout value is configured, then this function will timeout once the number of CPU cycles expire.
|
||||
*
|
||||
* @param lp_uart_num LP UART port number
|
||||
* @param buf data buffer address
|
||||
* @param size data length to send
|
||||
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
|
||||
*
|
||||
* @return - (-1) Error
|
||||
* - OTHERS (>=0) The number of bytes read from the Rx FIFO
|
||||
*/
|
||||
int lp_core_uart_read_bytes(uart_port_t lp_uart_num, void *buf, size_t size, int32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Flush LP UART Tx FIFO
|
||||
*
|
||||
* This function is automatically called before the LP core powers down once the main() function returns.
|
||||
* It can also be called manually in the application to flush the Tx FIFO.
|
||||
*
|
||||
* @param lp_uart_num LP UART port number
|
||||
*/
|
||||
void lp_core_uart_tx_flush(uart_port_t lp_uart_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "riscv/csr.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
/**
|
||||
* @brief Traverse all possible wake-up sources and update the wake-up cause so that
|
||||
* ulp_lp_core_get_wakeup_cause can obtain the bitmap of the wake-up reasons.
|
||||
* @note Do not call it from user ULP programs because it will clear the wake-up cause bits
|
||||
* which were set at ULP startup in lp_core_startup().
|
||||
*/
|
||||
void ulp_lp_core_update_wakeup_cause(void);
|
||||
|
||||
/**
|
||||
* @brief Get the wakeup source which caused LP_CPU to wakeup from sleep
|
||||
*
|
||||
* @return Wakeup cause in bit map, for the meaning of each bit, refer
|
||||
* to the definition of wakeup source in lp_core_ll.h
|
||||
*/
|
||||
uint32_t ulp_lp_core_get_wakeup_cause(void);
|
||||
|
||||
/**
|
||||
* @brief Wakeup main CPU from sleep or deep sleep.
|
||||
*
|
||||
* This raises a software interrupt signal, if the
|
||||
* main CPU has configured the ULP as a wakeup source
|
||||
* calling this function will make the main CPU to
|
||||
* exit from sleep or deep sleep.
|
||||
*/
|
||||
void ulp_lp_core_wakeup_main_processor(void);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the current number of CPU cycles.
|
||||
*
|
||||
* @return The current CPU cycle count.
|
||||
*/
|
||||
static inline uint32_t ulp_lp_core_get_cpu_cycles(void)
|
||||
{
|
||||
return RV_READ_CSR(mcycle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether an mcycle-based timeout has elapsed.
|
||||
*
|
||||
* @note A timeout value of -1 means "wait forever".
|
||||
* Other values are interpreted as unsigned cycle counts.
|
||||
*
|
||||
* @param start_cycle_count Cycle counter value captured at timeout start.
|
||||
* @param cycles_to_wait Timeout in CPU cycles, or -1 to disable timeout.
|
||||
*
|
||||
* @return true if timeout elapsed, false otherwise.
|
||||
*/
|
||||
static inline bool ulp_lp_core_is_timeout_elapsed(uint32_t start_cycle_count, int32_t cycles_to_wait)
|
||||
{
|
||||
if (cycles_to_wait == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ulp_lp_core_get_cpu_cycles() - start_cycle_count) >= (uint32_t)cycles_to_wait;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes the co-processor busy-wait for a certain number of microseconds.
|
||||
*
|
||||
* @note The maximum supported delay depends on the LP core clock source and frequency.
|
||||
* For values above the limits below, the computed delay may overflow and the result
|
||||
* is undefined.
|
||||
* - LP core @ 16 MHz (RC_FAST / default): us must be <= 134217727 (about 134.2 s)
|
||||
* - LP core @ 40 MHz (XTAL 40 MHz): us must be <= 53687091 (about 53.7 s)
|
||||
* - LP core @ 48 MHz (XTAL 48 MHz): us must be <= 44739242 (about 44.7 s)
|
||||
*
|
||||
* @param us Number of microseconds to busy-wait for
|
||||
*/
|
||||
void ulp_lp_core_delay_us(uint32_t us);
|
||||
|
||||
/**
|
||||
* @brief Makes the co-processor busy-wait for a certain number of cycles.
|
||||
*
|
||||
* @note The maximum supported delay is 0x7FFFFFFF cycles.
|
||||
* For larger values, the behavior is undefined. Split longer delays into smaller
|
||||
* chunks if needed.
|
||||
*
|
||||
* For reference, this corresponds approximately to:
|
||||
* - LP core @ 16 MHz (RC_FAST / default): 0x7FFFFFFF cycles ≈ 134.2 s
|
||||
* - LP core @ 40 MHz (XTAL 40 MHz): 0x7FFFFFFF cycles ≈ 53.7 s
|
||||
* - LP core @ 48 MHz (XTAL 48 MHz): 0x7FFFFFFF cycles ≈ 44.7 s
|
||||
*
|
||||
* @param cycles Number of cycles to busy-wait for
|
||||
*/
|
||||
void ulp_lp_core_delay_cycles(uint32_t cycles);
|
||||
|
||||
#if SOC_ULP_LP_UART_SUPPORTED
|
||||
/**
|
||||
* @brief Reset LP CORE uart wakeup enable.
|
||||
*/
|
||||
void ulp_lp_core_lp_uart_reset_wakeup_en(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Finishes the ULP program and powers down the ULP
|
||||
* until next wakeup.
|
||||
*
|
||||
* @note This function does not return. After called it will
|
||||
* fully reset the ULP.
|
||||
*
|
||||
* @note The program will automatically call this function when
|
||||
* returning from main().
|
||||
*
|
||||
* @note To stop the ULP from waking up, call ulp_lp_core_lp_timer_disable()
|
||||
* before halting.
|
||||
*
|
||||
*/
|
||||
__attribute__((__noreturn__)) void ulp_lp_core_halt(void);
|
||||
|
||||
/**
|
||||
* @brief The LP core puts itself to sleep and disables all wakeup sources.
|
||||
*/
|
||||
__attribute__((__noreturn__)) void ulp_lp_core_stop_lp_core(void);
|
||||
|
||||
/**
|
||||
* @brief Abort LP core operation.
|
||||
*/
|
||||
void __attribute__((noreturn)) ulp_lp_core_abort(void);
|
||||
|
||||
/**
|
||||
* @brief Trigger a software interrupt on the HP core
|
||||
*/
|
||||
void ulp_lp_core_sw_intr_to_hp_trigger(void);
|
||||
|
||||
/**
|
||||
* @brief Enable the SW triggered interrupt from the PMU
|
||||
*
|
||||
* @note This is the same SW trigger interrupt that is used to wake up the LP CPU
|
||||
*
|
||||
* @param enable true to enable, false to disable
|
||||
*
|
||||
*/
|
||||
void ulp_lp_core_sw_intr_from_hp_enable(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Enable the SW triggered interrupt from the PMU
|
||||
*
|
||||
* @note Alias for the `ulp_lp_core_sw_intr_from_hp_enable` function, for backward compatibility.
|
||||
*
|
||||
* @param enable true to enable, false to disable
|
||||
*/
|
||||
static inline void ulp_lp_core_sw_intr_enable(bool enable)
|
||||
{
|
||||
return ulp_lp_core_sw_intr_from_hp_enable(enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear the interrupt status for the SW triggered interrupt from the PMU
|
||||
*/
|
||||
void ulp_lp_core_sw_intr_from_hp_clear(void);
|
||||
|
||||
/**
|
||||
* @brief Clear the interrupt status for the SW triggered interrupt from the PMU
|
||||
*
|
||||
* @note Alias for the `ulp_lp_core_sw_intr_from_hp_clear` function, for backward compatibility.
|
||||
*/
|
||||
static inline void ulp_lp_core_sw_intr_clear(void)
|
||||
{
|
||||
return ulp_lp_core_sw_intr_from_hp_clear();
|
||||
}
|
||||
|
||||
#if SOC_RTC_TIMER_SUPPORTED
|
||||
/**
|
||||
* @brief Enable the LP Timer interrupt
|
||||
*
|
||||
*/
|
||||
void ulp_lp_core_lp_timer_intr_enable(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Clear the interrupt status for the LP Timer interrupt
|
||||
*
|
||||
*/
|
||||
void ulp_lp_core_lp_timer_intr_clear(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Puts the CPU into a wait state until an interrupt is triggered
|
||||
*
|
||||
* @note The CPU will draw less power when in this state compared to actively running
|
||||
*
|
||||
*/
|
||||
void ulp_lp_core_wait_for_intr(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* LP-core RISC-V linker script, assembled from base + layout + checks.
|
||||
* A custom layout replaces the default via LP_CORE_LINKER_INCLUDE.
|
||||
*/
|
||||
#include "lp_core_riscv_base.ld.in"
|
||||
|
||||
#ifdef LP_CORE_LINKER_INCLUDE
|
||||
#include LP_CORE_LINKER_INCLUDE
|
||||
#else
|
||||
#include "lp_core_riscv_default_layout.ld.in"
|
||||
#endif
|
||||
|
||||
#include "lp_core_riscv_checks.ld.in"
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Base part of the LP-core linker script (base + layout + checks). It defines:
|
||||
* - the usable LP-RAM window bounds LP_CORE_USER_MEMORY_REGION_START and
|
||||
* LP_CORE_USER_MEMORY_REGION_END;
|
||||
* - ENTRY, the interrupt vector table, the HP/LP shared-memory section and a
|
||||
* default __stack_top;
|
||||
* - the section-boundary macros a custom layout composes: LP_CORE_TEXT_START,
|
||||
* LP_CORE_TEXT_END, LP_CORE_DATA_START and LP_CORE_DATA_END, plus the
|
||||
* LP_CORE_DEFAULT_TEXT / LP_CORE_DEFAULT_DATA convenience macros used by the
|
||||
* default layout.
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc.h"
|
||||
#include "ld.common"
|
||||
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
|
||||
#include "ld.hp_mem_defs"
|
||||
#endif
|
||||
|
||||
#if CONFIG_ESP_ROM_HAS_LP_ROM
|
||||
/* With LP-ROM memory layout is different due to LP ROM stack/data */
|
||||
/* For P4 ECO5 we also reserve some RTC MEM at the first for MSPI workaround */
|
||||
#define ULP_MEM_START_ADDRESS SOC_RTC_DRAM_LOW + RESERVE_RTC_MEM + MSPI_WORKAROUND_SIZE
|
||||
#else
|
||||
#define ULP_MEM_START_ADDRESS (SOC_RTC_DRAM_LOW)
|
||||
#endif
|
||||
|
||||
#define ALIGN_DOWN(SIZE, AL) (SIZE & ~(AL - 1))
|
||||
/* Ensure the end where the shared memory starts is aligned to 8 bytes
|
||||
if updating this also update the same in ulp_lp_core_memory_shared.c
|
||||
*/
|
||||
#define ALIGNED_COPROC_MEM ALIGN_DOWN(CONFIG_ULP_COPROC_RESERVE_MEM, 0x8)
|
||||
#define ULP_VECTOR_TABLE_LENGTH 0x80
|
||||
|
||||
/* Free LP-RAM window a layout may use, between the vector table and shared memory. */
|
||||
#define LP_CORE_USER_MEMORY_REGION_START (ULP_MEM_START_ADDRESS + ULP_VECTOR_TABLE_LENGTH)
|
||||
#define LP_CORE_USER_MEMORY_REGION_END (ULP_MEM_START_ADDRESS + ALIGNED_COPROC_MEM - CONFIG_ULP_SHARED_MEM)
|
||||
|
||||
/*
|
||||
* Section-boundary macros a custom layout composes at each region boundary.
|
||||
* ESP-IDF injects any config-dependent handling here, so the layout composes
|
||||
* them the same way regardless of the active configuration.
|
||||
*/
|
||||
|
||||
/* Reset vector and early handlers at the boot offset, placed first in `region`
|
||||
(which must start at LP_CORE_USER_MEMORY_REGION_START). */
|
||||
#define LP_CORE_TEXT_START(region) \
|
||||
. = ORIGIN(region) ; \
|
||||
.rtc_text ALIGN(4) : { \
|
||||
*(.text.vectors) /* Reset vector must link to offset 0x80 */ \
|
||||
*(.text.handlers) \
|
||||
*(.text.handlers.*) \
|
||||
} > region
|
||||
|
||||
/* End of the executable region in LP RAM: the RX/RW boundary the PMP setup uses
|
||||
under memory protection. (Memory protection and running from HP RAM are
|
||||
mutually exclusive, so the boundary always follows the LP-RAM .text here.) */
|
||||
#if CONFIG_ULP_LP_CORE_MEMPROT
|
||||
#define LP_CORE_TEXT_END() . = ALIGN(128) ; _lp_text_end = . ;
|
||||
#else
|
||||
#define LP_CORE_TEXT_END()
|
||||
#endif
|
||||
|
||||
/* Start of the writable data region. */
|
||||
#define LP_CORE_DATA_START() . = ALIGN(128) ; _lp_data_start = . ;
|
||||
|
||||
/* End of the writable data region, before the stack. Marks the lowest address
|
||||
the stack may descend to: the framework derives the default __stack_size from
|
||||
it and bounds-checks the stack against it. A layout must compose this (or
|
||||
assign _lp_data_end directly). */
|
||||
#define LP_CORE_DATA_END() . = ALIGN(4) ; _lp_data_end = . ;
|
||||
|
||||
/* Convenience: standard executable and data section placement into `region`. */
|
||||
#define LP_CORE_DEFAULT_TEXT(region) \
|
||||
.text ALIGN(4) : { *(.text) *(.text*) } > region \
|
||||
.rodata ALIGN(4) : { *(.rodata) *(.rodata*) } > region
|
||||
#define LP_CORE_DEFAULT_DATA(region) \
|
||||
.data ALIGN(4) : { _data_start = . ; *(.data) *(.data*) *(.sdata) *(.sdata*) _data_end = . ; } > region \
|
||||
.bss ALIGN(4) : { _bss_start = . ; *(.bss) *(.bss*) *(.sbss) *(.sbss*) PROVIDE(end = .) ; _bss_end = . ; } > region
|
||||
|
||||
/* Default stack top, at the top of the usable window. A layout whose data does
|
||||
not reach the window end may override it to the top of its own data region.
|
||||
The companion __stack_size defaults to __stack_top - _lp_data_end (computed in
|
||||
the checks part, once the layout has placed its data); a layout may override
|
||||
either. Both are weak defaults, so a custom layout can take ownership. */
|
||||
PROVIDE(__stack_top = LP_CORE_USER_MEMORY_REGION_END);
|
||||
|
||||
ENTRY(reset_vector)
|
||||
|
||||
MEMORY
|
||||
{
|
||||
/* First 128 bytes for exception/interrupt vectors */
|
||||
vector_table(RX) : ORIGIN = ULP_MEM_START_ADDRESS, LENGTH = ULP_VECTOR_TABLE_LENGTH
|
||||
shared_mem_ram(RW) : ORIGIN = LP_CORE_USER_MEMORY_REGION_END, LENGTH = CONFIG_ULP_SHARED_MEM
|
||||
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
|
||||
hp_ram(RWX) : ORIGIN = ULP_HP_MEM_START, LENGTH = ULP_HP_MEM_SIZE
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.vector.text :
|
||||
{
|
||||
/* Exception/interrupt vectors */
|
||||
__mtvec_base = .;
|
||||
KEEP (*(.init.vector .init.vector.*))
|
||||
} > vector_table
|
||||
|
||||
/* Shared memory, agreed with ulp_lp_core_memory_shared.c. */
|
||||
. = ORIGIN(shared_mem_ram);
|
||||
_lp_shared_start = ORIGIN(shared_mem_ram);
|
||||
.shared_mem (ALIGN(4)) : { KEEP(*(.shared_mem)) } > shared_mem_ram
|
||||
|
||||
/* Position the location counter at the window start so the layout part's
|
||||
sections flow into their region. A layout that omits LP_CORE_TEXT_START
|
||||
is then caught by the checks part instead of an obscure overflow. */
|
||||
. = LP_CORE_USER_MEMORY_REGION_START;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Checks part (base + layout + checks). Link-time ASSERTs that catch
|
||||
* misplacement, overflow, and missing symbols in the layout part.
|
||||
*/
|
||||
ASSERT(DEFINED(reset_vector),
|
||||
"LP core linker: reset_vector is not defined")
|
||||
|
||||
ASSERT(__mtvec_base == ULP_MEM_START_ADDRESS,
|
||||
"LP core linker: vector table is not at the reserved LP base")
|
||||
|
||||
ASSERT(reset_vector == LP_CORE_USER_MEMORY_REGION_START,
|
||||
"LP core linker: reset vector is not at the required offset")
|
||||
|
||||
/* The layout must mark the end of writable data so the framework can size and
|
||||
bounds-check the stack. */
|
||||
ASSERT(DEFINED(_lp_data_end),
|
||||
"LP core linker: end of writable data is not marked; compose LP_CORE_DATA_END() (or assign _lp_data_end) in the layout")
|
||||
|
||||
/* Default stack size: all free space between the data end and the stack top.
|
||||
A layout that assigns __stack_size wins (PROVIDE yields and does not evaluate
|
||||
the expression). */
|
||||
PROVIDE(__stack_size = __stack_top - _lp_data_end);
|
||||
|
||||
ASSERT(__stack_top <= _lp_shared_start,
|
||||
"LP core linker: application overruns into the reserved shared-memory region")
|
||||
|
||||
ASSERT(__stack_top <= ULP_MEM_START_ADDRESS + ALIGNED_COPROC_MEM,
|
||||
"LP core linker: image exceeds the reserved LP coprocessor memory")
|
||||
|
||||
/* The stack grows down from __stack_top for __stack_size bytes; its lowest
|
||||
address must not descend into the data region. */
|
||||
ASSERT(__stack_top - __stack_size >= _lp_data_end,
|
||||
"LP core linker: stack region overlaps the data region (__stack_size too large or __stack_top too low)")
|
||||
|
||||
#if CONFIG_ULP_LP_CORE_MEMPROT
|
||||
ASSERT(DEFINED(_lp_text_end),
|
||||
"LP core linker: _lp_text_end must be defined when CONFIG_ULP_LP_CORE_MEMPROT is enabled (it marks the RX/RW boundary used by the PMP setup)")
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Default layout part (base + layout + checks). Replaced by a custom layout
|
||||
* when LP_CORE_LINKER_INCLUDE is set via the LINKER_LAYOUT option. Routes the
|
||||
* application to HP RAM when CONFIG_ULP_COPROC_RUN_FROM_HP_MEM is enabled.
|
||||
*/
|
||||
MEMORY
|
||||
{
|
||||
lp_ram(RWX) : ORIGIN = LP_CORE_USER_MEMORY_REGION_START, LENGTH = LP_CORE_USER_MEMORY_REGION_END - LP_CORE_USER_MEMORY_REGION_START
|
||||
}
|
||||
|
||||
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
|
||||
REGION_ALIAS("default_app_seg", hp_ram);
|
||||
#else
|
||||
REGION_ALIAS("default_app_seg", lp_ram);
|
||||
#endif
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
LP_CORE_TEXT_START(lp_ram)
|
||||
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
|
||||
. = ORIGIN(hp_ram);
|
||||
#endif
|
||||
|
||||
LP_CORE_DEFAULT_TEXT(default_app_seg)
|
||||
|
||||
LP_CORE_TEXT_END()
|
||||
LP_CORE_DATA_START()
|
||||
|
||||
LP_CORE_DEFAULT_DATA(default_app_seg)
|
||||
|
||||
LP_CORE_DATA_END()
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <sys/param.h> /* For MIN macro */
|
||||
#include "soc/soc_caps.h"
|
||||
#include "ulp_lp_core_i2c.h"
|
||||
#include "ulp_lp_core_utils.h"
|
||||
#include "soc/i2c_struct.h"
|
||||
#include "hal/i2c_ll.h"
|
||||
|
||||
#if SOC_LP_CORE_SUPPORT_I2C
|
||||
|
||||
#define LP_I2C_FIFO_LEN I2C_LL_GET(LP_FIFO_LEN)
|
||||
#define LP_I2C_READ_MODE I2C_MASTER_READ
|
||||
#define LP_I2C_WRITE_MODE I2C_MASTER_WRITE
|
||||
#define LP_I2C_ACK I2C_MASTER_ACK
|
||||
#define LP_I2C_NACK I2C_MASTER_NACK
|
||||
|
||||
/* I2C LL context */
|
||||
|
||||
i2c_dev_t *dev = I2C_LL_GET_HW(LP_I2C_NUM_0);
|
||||
|
||||
/* ACK check enable control variable. Enabled by default */
|
||||
static bool s_ack_check_en = true;
|
||||
|
||||
/*
|
||||
* The LP I2C controller uses the LP I2C HW command registers to perform read/write operations.
|
||||
* The cmd registers have the following format:
|
||||
*
|
||||
* 31 30:14 13:11 10 9 8 7:0
|
||||
* |----------|----------|---------|---------|----------|------------|---------|
|
||||
* | CMD_DONE | Reserved | OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num |
|
||||
* |----------|----------|---------|---------|----------|------------|---------|
|
||||
*/
|
||||
static void lp_core_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val,
|
||||
uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num)
|
||||
{
|
||||
if (cmd_idx >= sizeof(dev->command)) {
|
||||
/* We only have limited HW command registers.
|
||||
* Although unlikely, make sure that we do not write to an out of bounds index.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/* Form new command */
|
||||
i2c_ll_hw_cmd_t hw_cmd = {
|
||||
.done = 0, // CMD Done
|
||||
.op_code = op_code, // Opcode
|
||||
.ack_val = ack_val, // ACK bit sent by I2C controller during READ.
|
||||
// Ignored during RSTART, STOP, END and WRITE cmds.
|
||||
.ack_exp = ack_expected, // ACK bit expected by I2C controller during WRITE.
|
||||
// Ignored during RSTART, STOP, END and READ cmds.
|
||||
.ack_en = ack_check_en, // I2C controller verifies that the ACK bit sent by the
|
||||
// slave device matches the ACK expected bit during WRITE.
|
||||
// Ignored during RSTART, STOP, END and READ cmds.
|
||||
.byte_num = byte_num, // Byte Num
|
||||
};
|
||||
|
||||
/* Write new command to cmd register */
|
||||
i2c_ll_master_write_cmd_reg(dev, hw_cmd, cmd_idx);
|
||||
}
|
||||
|
||||
static inline esp_err_t lp_core_i2c_wait_for_interrupt(uint32_t intr_mask, int32_t cycles_to_wait)
|
||||
{
|
||||
uint32_t intr_status = 0;
|
||||
uint32_t start = ulp_lp_core_get_cpu_cycles();
|
||||
|
||||
while (1) {
|
||||
i2c_ll_get_intr_raw_mask(dev, &intr_status);
|
||||
if (intr_status & intr_mask) {
|
||||
if (intr_status & I2C_LL_INTR_NACK) {
|
||||
/* The ACK/NACK received during a WRITE operation does not match the expected ACK/NACK level
|
||||
* Abort and return an error.
|
||||
*/
|
||||
i2c_ll_clear_intr_mask(dev, intr_mask);
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
} else if (intr_status & I2C_LL_INTR_MST_COMPLETE) {
|
||||
/* Transaction complete.
|
||||
* Clear interrupt bits and break
|
||||
*/
|
||||
i2c_ll_clear_intr_mask(dev, intr_mask);
|
||||
break;
|
||||
} else {
|
||||
/* We received an I2C_END_DETECT_INT.
|
||||
* This means we are not yet done with the transaction.
|
||||
* Simply clear the interrupt bit and break.
|
||||
*/
|
||||
i2c_ll_clear_intr_mask(dev, intr_mask);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (ulp_lp_core_is_timeout_elapsed(start, cycles_to_wait)) {
|
||||
/* Timeout. Clear interrupt bits and return an error */
|
||||
i2c_ll_clear_intr_mask(dev, intr_mask);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
/* We reach here only if we are in a good state */
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static inline void lp_core_i2c_config_device_addr(uint32_t cmd_idx, uint16_t device_addr, uint32_t rw_mode, uint8_t *addr_len)
|
||||
{
|
||||
uint8_t data_byte = 0;
|
||||
uint8_t data_len = 0;
|
||||
|
||||
/* 7-bit addressing mode. We do not support 10-bit addressing mode yet (IDF-7364) */
|
||||
|
||||
// Write the device address + R/W mode in the first Tx FIFO slot
|
||||
data_byte = (uint8_t)(((device_addr & 0xFF) << 1) | (rw_mode << 0));
|
||||
i2c_ll_write_txfifo(dev, &data_byte, 1);
|
||||
data_len++;
|
||||
|
||||
/* Update the HW command register. Expect an ACK from the device */
|
||||
lp_core_i2c_format_cmd(cmd_idx, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, data_len);
|
||||
|
||||
/* Return the address length in bytes */
|
||||
*addr_len = data_len;
|
||||
}
|
||||
|
||||
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en)
|
||||
{
|
||||
(void)lp_i2c_num;
|
||||
|
||||
s_ack_check_en = ack_check_en;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
|
||||
uint8_t *data_rd, size_t size,
|
||||
int32_t cycles_to_wait)
|
||||
{
|
||||
(void)lp_i2c_num;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t cmd_idx = 0;
|
||||
|
||||
if (size == 0) {
|
||||
// Quietly return
|
||||
return ESP_OK;
|
||||
} else if (size > UINT8_MAX) {
|
||||
// HW register only has an 8-bit byte-num field
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
/* Execute RSTART command to send the START bit */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
|
||||
|
||||
/* Write device addr and update the HW command register */
|
||||
uint8_t addr_len = 0;
|
||||
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
|
||||
|
||||
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
|
||||
uint32_t intr_mask = I2C_LL_INTR_MST_COMPLETE | I2C_LL_INTR_END_DETECT;
|
||||
i2c_ll_clear_intr_mask(dev, intr_mask);
|
||||
|
||||
/* Read data */
|
||||
uint32_t fifo_size = 0;
|
||||
uint32_t data_idx = 0;
|
||||
int32_t remaining_bytes = size;
|
||||
|
||||
/* The data is received in sequential slots of the Rx FIFO.
|
||||
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
|
||||
*/
|
||||
while (remaining_bytes > 0) {
|
||||
/* Select the amount of data that fits in the Rx FIFO */
|
||||
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
|
||||
|
||||
/* Update the number of bytes remaining to be read */
|
||||
remaining_bytes -= fifo_size;
|
||||
|
||||
/* Update HW command register to read bytes */
|
||||
if (fifo_size == 1) {
|
||||
/* Read 1 byte and send NACK */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
|
||||
|
||||
/* STOP */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
|
||||
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
|
||||
/* This means it is the last transaction.
|
||||
* Read fifo_size - 1 bytes and send ACKs
|
||||
*/
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
|
||||
|
||||
/* Read last byte and send NACK */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
|
||||
|
||||
/* STOP */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
|
||||
} else {
|
||||
/* This means we have to read data more than what can fit in the Rx FIFO.
|
||||
* Read fifo_size bytes and send ACKs
|
||||
*/
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
|
||||
cmd_idx = 0;
|
||||
}
|
||||
|
||||
/* Initiate I2C transfer */
|
||||
i2c_ll_update(dev);
|
||||
i2c_ll_start_trans(dev);
|
||||
|
||||
/* Wait for the transfer to complete */
|
||||
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
|
||||
if (ret != ESP_OK) {
|
||||
/* Transaction error. Abort. */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Read Rx FIFO */
|
||||
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
|
||||
|
||||
/* Update data_idx */
|
||||
data_idx += fifo_size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
|
||||
const uint8_t *data_wr, size_t size,
|
||||
int32_t cycles_to_wait)
|
||||
{
|
||||
(void)lp_i2c_num;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t cmd_idx = 0;
|
||||
|
||||
if (size == 0) {
|
||||
// Quietly return
|
||||
return ESP_OK;
|
||||
} else if (size > UINT8_MAX) {
|
||||
// HW register only has an 8-bit byte-num field
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
/* If SCL is busy, reset the Master FSM */
|
||||
if (i2c_ll_is_bus_busy(dev)) {
|
||||
i2c_ll_master_fsm_rst(dev);
|
||||
}
|
||||
|
||||
/* Reset the Tx and Rx FIFOs */
|
||||
i2c_ll_txfifo_rst(dev);
|
||||
i2c_ll_rxfifo_rst(dev);
|
||||
|
||||
/* Execute RSTART command to send the START bit */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
|
||||
|
||||
/* Write device addr and update the HW command register */
|
||||
uint8_t addr_len = 0;
|
||||
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
|
||||
|
||||
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
|
||||
uint32_t intr_mask = I2C_LL_INTR_MST_COMPLETE | I2C_LL_INTR_END_DETECT;
|
||||
if (s_ack_check_en) {
|
||||
/* Enable NACK interrupt to check for ACK errors */
|
||||
intr_mask |= I2C_LL_INTR_NACK;
|
||||
}
|
||||
i2c_ll_clear_intr_mask(dev, intr_mask);
|
||||
|
||||
/* Write data */
|
||||
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
|
||||
uint32_t fifo_size = 0;
|
||||
uint32_t data_idx = 0;
|
||||
int32_t remaining_bytes = size;
|
||||
|
||||
/* The data to be sent must occupy sequential slots of the Tx FIFO.
|
||||
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
|
||||
*/
|
||||
while (remaining_bytes > 0) {
|
||||
/* Select the amount of data that fits in the Tx FIFO */
|
||||
fifo_size = MIN(remaining_bytes, fifo_available);
|
||||
|
||||
/* Update the number of bytes remaining to be sent */
|
||||
remaining_bytes -= fifo_size;
|
||||
|
||||
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
|
||||
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
|
||||
|
||||
if (remaining_bytes == 0) {
|
||||
/* This means it is the last transaction. Insert a Stop command. */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
|
||||
} else {
|
||||
/* This means we have to send more than what can fit in the Tx FIFO. Insert an End command. */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
|
||||
cmd_idx = 0;
|
||||
}
|
||||
|
||||
/* Initiate I2C transfer */
|
||||
i2c_ll_update(dev);
|
||||
i2c_ll_start_trans(dev);
|
||||
|
||||
/* Wait for the transfer to complete */
|
||||
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
|
||||
if (ret != ESP_OK) {
|
||||
/* Transaction error. Abort. */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Update data_idx */
|
||||
data_idx += fifo_size;
|
||||
|
||||
/* We now have the full fifo available for writing */
|
||||
fifo_available = LP_I2C_FIFO_LEN;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
|
||||
const uint8_t *data_wr, size_t write_size,
|
||||
uint8_t *data_rd, size_t read_size,
|
||||
int32_t cycles_to_wait)
|
||||
{
|
||||
(void)lp_i2c_num;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t cmd_idx = 0;
|
||||
|
||||
if ((write_size == 0) || (read_size == 0)) {
|
||||
// Quietly return
|
||||
return ESP_OK;
|
||||
} else if ((write_size > UINT8_MAX) || (read_size > UINT8_MAX)) {
|
||||
// HW register only has an 8-bit byte-num field
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
/* If SCL is busy, reset the Master FSM */
|
||||
if (i2c_ll_is_bus_busy(dev)) {
|
||||
i2c_ll_master_fsm_rst(dev);
|
||||
}
|
||||
|
||||
/* Reset the Tx and Rx FIFOs */
|
||||
i2c_ll_txfifo_rst(dev);
|
||||
i2c_ll_rxfifo_rst(dev);
|
||||
|
||||
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
|
||||
uint32_t intr_mask = I2C_LL_INTR_MST_COMPLETE | I2C_LL_INTR_END_DETECT;
|
||||
if (s_ack_check_en) {
|
||||
/* Enable NACK interrupt to check for ACK errors */
|
||||
intr_mask |= I2C_LL_INTR_NACK;
|
||||
}
|
||||
i2c_ll_clear_intr_mask(dev, intr_mask);
|
||||
|
||||
/* Execute RSTART command to send the START bit */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
|
||||
|
||||
/* Write device addr and update the HW command register */
|
||||
uint8_t addr_len = 0;
|
||||
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
|
||||
|
||||
/* Write data */
|
||||
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
|
||||
uint32_t fifo_size = 0;
|
||||
uint32_t data_idx = 0;
|
||||
int32_t remaining_bytes = write_size;
|
||||
|
||||
/* The data to be sent must occupy sequential slots of the Tx FIFO.
|
||||
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
|
||||
*/
|
||||
while (remaining_bytes > 0) {
|
||||
/* Select the amount of data that fits in the Tx FIFO */
|
||||
fifo_size = MIN(remaining_bytes, fifo_available);
|
||||
|
||||
/* Update the number of bytes remaining to be sent */
|
||||
remaining_bytes -= fifo_size;
|
||||
|
||||
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
|
||||
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
|
||||
|
||||
/* Insert an End command to signal the end of the write transaction to the HW */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
|
||||
cmd_idx = 0;
|
||||
|
||||
/* Initiate I2C transfer */
|
||||
i2c_ll_update(dev);
|
||||
i2c_ll_start_trans(dev);
|
||||
|
||||
/* Wait for the transfer to complete */
|
||||
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
|
||||
if (ret != ESP_OK) {
|
||||
/* Transaction error. Abort. */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Update data_idx */
|
||||
data_idx += fifo_size;
|
||||
|
||||
/* We now have the full fifo available for writing */
|
||||
fifo_available = LP_I2C_FIFO_LEN;
|
||||
}
|
||||
|
||||
/* Reset command index */
|
||||
cmd_idx = 0;
|
||||
|
||||
/* Execute RSTART command again to send a START condition for the read operation */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
|
||||
|
||||
/* Write device addr again in read mode */
|
||||
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
|
||||
|
||||
/* Read data */
|
||||
fifo_size = 0;
|
||||
data_idx = 0;
|
||||
remaining_bytes = read_size;
|
||||
|
||||
/* The data is received in sequential slots of the Rx FIFO.
|
||||
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
|
||||
*/
|
||||
while (remaining_bytes > 0) {
|
||||
/* Select the amount of data that fits in the Rx FIFO */
|
||||
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
|
||||
|
||||
/* Update the number of bytes remaining to be read */
|
||||
remaining_bytes -= fifo_size;
|
||||
|
||||
/* Update HW command register to read bytes */
|
||||
if (fifo_size == 1) {
|
||||
/* Read 1 byte and send NACK */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
|
||||
|
||||
/* STOP */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
|
||||
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
|
||||
/* This means it is the last transaction.
|
||||
* Read fifo_size - 1 bytes and send ACKs
|
||||
*/
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
|
||||
|
||||
/* Read last byte and send NACK */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
|
||||
|
||||
/* STOP */
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
|
||||
} else {
|
||||
/* This means we have to read data more than what can fit in the Rx FIFO.
|
||||
* Read fifo_size bytes and send ACKs
|
||||
*/
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
|
||||
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
|
||||
cmd_idx = 0;
|
||||
}
|
||||
|
||||
/* Initiate I2C transfer */
|
||||
i2c_ll_update(dev);
|
||||
i2c_ll_start_trans(dev);
|
||||
|
||||
/* Wait for the transfer to complete */
|
||||
ret = lp_core_i2c_wait_for_interrupt(intr_mask, cycles_to_wait);
|
||||
if (ret != ESP_OK) {
|
||||
/* Transaction error. Abort. */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Read Rx FIFO */
|
||||
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
|
||||
|
||||
/* Update data_idx */
|
||||
data_idx += fifo_size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* SOC_LP_CORE_SUPPORT_I2C */
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/lp_core_ll.h"
|
||||
#include "riscv/rv_utils.h"
|
||||
#include "riscv/rvruntime-frames.h"
|
||||
#include "ulp_lp_core_utils.h"
|
||||
|
||||
#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
|
||||
/* Enable interrupt 30, which all external interrupts are routed to*/
|
||||
#define MIE_ALL_INTS_MASK (1 << 30)
|
||||
#else
|
||||
/* Enable all external interrupts routed to CPU, expect HP_INTR,
|
||||
as this would trigger an LP core interrupt for every single interrupt
|
||||
that triggers on HP Core.
|
||||
*/
|
||||
#define MIE_ALL_INTS_MASK 0x3FFF0888
|
||||
#endif
|
||||
|
||||
void ulp_lp_core_intr_enable(void)
|
||||
{
|
||||
/* Enable interrupt globally */
|
||||
RV_SET_CSR(mstatus, MSTATUS_MIE);
|
||||
RV_SET_CSR(mie, MIE_ALL_INTS_MASK);
|
||||
|
||||
}
|
||||
|
||||
void ulp_lp_core_intr_disable(void)
|
||||
{
|
||||
RV_CLEAR_CSR(mie, MIE_ALL_INTS_MASK);
|
||||
/* Disable interrupts globally */
|
||||
RV_CLEAR_CSR(mstatus, MSTATUS_MIE);
|
||||
}
|
||||
|
||||
void __attribute__((weak)) ulp_lp_core_panic_handler(RvExcFrame *frame, int exccause)
|
||||
{
|
||||
ulp_lp_core_abort();
|
||||
}
|
||||
|
||||
static void __attribute__((section(".text.handlers"))) ulp_lp_core_default_intr_handler(void)
|
||||
{
|
||||
ulp_lp_core_abort();
|
||||
}
|
||||
|
||||
/* Default ISR handlers, intended to be overwritten by users */
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_io_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_i2c_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_uart_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_timer_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_pmu_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_spi_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_trng_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_adc_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_touch_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_tsens_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_efuse_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_sysreg_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_ana_peri_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_mailbox_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_wdt_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_rtc_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_lp_pdma_intr_handler(void);
|
||||
void __attribute__((weak, alias("ulp_lp_core_default_intr_handler"))) ulp_lp_core_sw_intr_handler(void);
|
||||
|
||||
#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
|
||||
|
||||
static void* s_intr_handlers[] = {
|
||||
ulp_lp_core_lp_io_intr_handler,
|
||||
ulp_lp_core_lp_i2c_intr_handler,
|
||||
ulp_lp_core_lp_uart_intr_handler,
|
||||
ulp_lp_core_lp_timer_intr_handler,
|
||||
0, // Reserved / Unused
|
||||
ulp_lp_core_lp_pmu_intr_handler,
|
||||
};
|
||||
|
||||
void __attribute__((weak)) ulp_lp_core_intr_handler(void)
|
||||
{
|
||||
uint8_t intr_source = lp_core_ll_get_triggered_interrupt_srcs();
|
||||
for (int i = 0; i < sizeof(s_intr_handlers) / 4; i++) {
|
||||
if (intr_source & (1 << i)) {
|
||||
void (*handler)(void) = s_intr_handlers[i];
|
||||
if (handler) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "hal/misc.h"
|
||||
#include "ulp_lp_core_mailbox.h"
|
||||
#include "ulp_lp_core_mailbox_impl_shared.h"
|
||||
#include "ulp_lp_core_utils.h"
|
||||
#include "ulp_lp_core_interrupts.h"
|
||||
#include "ulp_lp_core_print.h"
|
||||
|
||||
#ifndef BIT
|
||||
#define BIT(i) (1U << (i))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Number of messages allocated for LP -> HP communication
|
||||
*/
|
||||
#define LP_MAILBOX_TX_MSG_COUNT (LP_MAILBOX_MSG_COUNT / 2)
|
||||
|
||||
/**
|
||||
* @brief Starting index of the LP -> HP messages
|
||||
*/
|
||||
#define LP_MAILBOX_TX_MSG_IDX 0U
|
||||
|
||||
/**
|
||||
* @brief Starting index of the HP -> LP messages
|
||||
*/
|
||||
#define LP_MAILBOX_RX_MSG_IDX (LP_MAILBOX_TX_MSG_COUNT)
|
||||
|
||||
/**
|
||||
* @brief Number of messages allocated for HP -> LP communication
|
||||
*/
|
||||
#define LP_MAILBOX_RX_MSG_COUNT (LP_MAILBOX_MSG_COUNT - LP_MAILBOX_RX_MSG_IDX)
|
||||
|
||||
/**
|
||||
* @brief Mask for the HP -> LP messages
|
||||
*/
|
||||
#define LP_MAILBOX_RX_MSG_MASK (((1U << LP_MAILBOX_RX_MSG_COUNT) - 1U) << LP_MAILBOX_RX_MSG_IDX)
|
||||
|
||||
#define LP_MAILBOX_ACK ((lp_message_t) 0xe5U)
|
||||
|
||||
struct lp_mailbox_t {
|
||||
lp_core_mailbox_ctx_t mb_ctx;
|
||||
lp_core_mailbox_callback_t rcv_callback;
|
||||
uint32_t rcv_remaining;
|
||||
int tx_idx;
|
||||
};
|
||||
|
||||
static struct lp_mailbox_t s_mailbox;
|
||||
|
||||
/* In the current implementation, it should always be pointing to `s_mailbox`, but keep it for now in case
|
||||
* we need to have something more modular */
|
||||
static lp_mailbox_t s_isr_arg;
|
||||
|
||||
/******************************************************************************
|
||||
* STATIC HELPERS *
|
||||
******************************************************************************/
|
||||
|
||||
static inline int lp_core_next_msg_idx(int idx)
|
||||
{
|
||||
return (idx + 1) % LP_MAILBOX_TX_MSG_COUNT;
|
||||
}
|
||||
|
||||
static inline bool mb_msg_status(lp_mailbox_t mailbox, int msg_idx)
|
||||
{
|
||||
return lp_core_mailbox_impl_intr_raw(mailbox->mb_ctx) & BIT(msg_idx);
|
||||
}
|
||||
|
||||
static inline bool mb_msg_search_read(lp_mailbox_t mailbox, int* msg_idx, lp_message_t* msg)
|
||||
{
|
||||
for (int i = LP_MAILBOX_RX_MSG_IDX; i < (LP_MAILBOX_RX_MSG_IDX + LP_MAILBOX_RX_MSG_COUNT); i += 2) {
|
||||
if (mb_msg_status(mailbox, i)) {
|
||||
*msg_idx = i;
|
||||
*msg = lp_core_mailbox_impl_get_message(mailbox->mb_ctx, i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool mb_async_mode(lp_mailbox_t mailbox)
|
||||
{
|
||||
return mailbox->rcv_callback != NULL;
|
||||
}
|
||||
|
||||
static inline bool lp_core_mailbox_check_timeout(uint32_t start_cycle, int32_t timeout)
|
||||
{
|
||||
return timeout != -1 && (ulp_lp_core_get_cpu_cycles() - start_cycle) >= (uint32_t) timeout;
|
||||
}
|
||||
|
||||
static void ulp_lp_core_mailbox_intr_handler(void)
|
||||
{
|
||||
lp_message_t received[LP_MAILBOX_RX_MSG_COUNT / 2];
|
||||
int received_count = 0;
|
||||
|
||||
/* `s_isr_arg` cannot be NULL but let's be safe and test it */
|
||||
if (s_isr_arg == NULL || s_isr_arg->mb_ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Acknowledge all the received message as fast as possible */
|
||||
const uint32_t status = lp_core_mailbox_impl_intr_status(s_isr_arg->mb_ctx);
|
||||
/* Mask of all the processed messages */
|
||||
uint32_t clr_mask = 0;
|
||||
|
||||
for (int i = 0; i < LP_MAILBOX_RX_MSG_COUNT; i += 2) {
|
||||
const uint32_t mask = BIT(LP_MAILBOX_RX_MSG_IDX + i);
|
||||
if (status & mask) {
|
||||
clr_mask |= mask;
|
||||
received[received_count] = lp_core_mailbox_impl_get_message(s_isr_arg->mb_ctx, LP_MAILBOX_RX_MSG_IDX + i);
|
||||
/* Acknowledge reception */
|
||||
const int ack_msg_idx = LP_MAILBOX_RX_MSG_IDX + lp_core_next_msg_idx(i);
|
||||
lp_core_mailbox_impl_set_message(s_isr_arg->mb_ctx, ack_msg_idx, LP_MAILBOX_ACK);
|
||||
/* Clear ACK self-interrupt (writing ACK sets LP's own interrupt bit) */
|
||||
lp_core_mailbox_impl_intr_clear(s_isr_arg->mb_ctx, BIT(ack_msg_idx));
|
||||
received_count++;
|
||||
/* We must not acknowledge more messages than what the caller needs */
|
||||
s_isr_arg->rcv_remaining--;
|
||||
if (s_isr_arg->rcv_remaining == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Clear the interrupt status */
|
||||
lp_core_mailbox_impl_intr_clear(s_isr_arg->mb_ctx, clr_mask);
|
||||
|
||||
/* Call the callback for all the message received */
|
||||
for (int i = 0; i < received_count; i++) {
|
||||
s_isr_arg->rcv_callback(received[i]);
|
||||
}
|
||||
|
||||
/* Disable interrupts if no more messages are expected */
|
||||
if (s_isr_arg->rcv_remaining == 0) {
|
||||
lp_core_mailbox_impl_intr_disable(s_isr_arg->mb_ctx, LP_MAILBOX_RX_MSG_MASK);
|
||||
s_isr_arg->rcv_callback = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* PUBLIC API *
|
||||
******************************************************************************/
|
||||
|
||||
esp_err_t lp_core_mailbox_init(lp_mailbox_t *mailbox, lp_mailbox_config_t *config)
|
||||
{
|
||||
/* Configuration is unused for now */
|
||||
(void) config;
|
||||
|
||||
if (mailbox == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (s_mailbox.mb_ctx != NULL) {
|
||||
/* Mailbox has already been initialized! */
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
hal_memset(&s_mailbox, 0, sizeof(s_mailbox));
|
||||
lp_core_mailbox_impl_init();
|
||||
s_mailbox.mb_ctx = lp_core_mailbox_impl_get_context();
|
||||
if (s_mailbox.mb_ctx == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
/* Clear the status and the interrupts for the messages */
|
||||
lp_core_mailbox_impl_intr_disable(s_mailbox.mb_ctx, ~0);
|
||||
lp_core_mailbox_impl_intr_clear(s_mailbox.mb_ctx, ~0);
|
||||
lp_core_mailbox_impl_set_intr_handler(s_mailbox.mb_ctx, ulp_lp_core_mailbox_intr_handler);
|
||||
s_mailbox.tx_idx = 0;
|
||||
*mailbox = &s_mailbox;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void lp_core_mailbox_deinit(lp_mailbox_t mailbox)
|
||||
{
|
||||
if (mailbox != NULL) {
|
||||
lp_core_mailbox_impl_intr_disable(s_mailbox.mb_ctx, ~0);
|
||||
lp_core_mailbox_impl_intr_clear(s_mailbox.mb_ctx, ~0);
|
||||
s_mailbox.mb_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t lp_core_mailbox_send(lp_mailbox_t mailbox, lp_message_t msg, int32_t timeout)
|
||||
{
|
||||
if (mailbox == NULL || timeout < -1) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (mb_async_mode(mailbox)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
/* Get the address of the next free message */
|
||||
const int msg_idx = LP_MAILBOX_TX_MSG_IDX + mailbox->tx_idx;
|
||||
/* The message right after will be used for acknowledgement since writing to one message
|
||||
* sets the "ready" signal for both LP and HP status registers */
|
||||
const int next_idx = lp_core_next_msg_idx(mailbox->tx_idx);
|
||||
const int msg_ack_idx = LP_MAILBOX_TX_MSG_IDX + next_idx;
|
||||
/* The message after will be the next free message */
|
||||
mailbox->tx_idx = lp_core_next_msg_idx(next_idx);
|
||||
/* Make sure both messages have cleared status */
|
||||
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(msg_idx) | BIT(msg_ack_idx));
|
||||
|
||||
/* Send the message and wait for an acknowledgement */
|
||||
lp_core_mailbox_impl_set_message(mailbox->mb_ctx, msg_idx, msg);
|
||||
/* Wait for a response from the HP core, on the same message */
|
||||
const uint32_t start_cycle = ulp_lp_core_get_cpu_cycles();
|
||||
while (1) {
|
||||
if (mb_msg_status(mailbox, msg_ack_idx)) {
|
||||
/* Clear the status */
|
||||
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(msg_idx) | BIT(msg_ack_idx));
|
||||
break;
|
||||
}
|
||||
if (lp_core_mailbox_check_timeout(start_cycle, timeout)) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_mailbox_send_async(lp_mailbox_t mailbox, lp_message_t msg)
|
||||
{
|
||||
if (mailbox == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (mb_async_mode(mailbox)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
/* Get the address of the next free message */
|
||||
const int msg_idx = LP_MAILBOX_TX_MSG_IDX + mailbox->tx_idx;
|
||||
mailbox->tx_idx = (mailbox->tx_idx + 1) % LP_MAILBOX_TX_MSG_COUNT;
|
||||
lp_core_mailbox_impl_set_message(mailbox->mb_ctx, msg_idx, msg);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_mailbox_receive(lp_mailbox_t mailbox, lp_message_t* msg, int32_t timeout)
|
||||
{
|
||||
int msg_idx = 0;
|
||||
|
||||
if (mailbox == NULL || timeout < -1) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (mb_async_mode(mailbox)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Wait for a message from the HP core */
|
||||
const uint32_t start_cycle = ulp_lp_core_get_cpu_cycles();
|
||||
while (1) {
|
||||
/* Get the address of the next free message */
|
||||
if (mb_msg_search_read(mailbox, &msg_idx, msg)) {
|
||||
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(msg_idx));
|
||||
break;
|
||||
}
|
||||
if (lp_core_mailbox_check_timeout(start_cycle, timeout)) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
/* Acknowledge the message by writing any value to the next one (guaranteed no overflow) */
|
||||
const int ack_idx = msg_idx + 1;
|
||||
lp_core_mailbox_impl_set_message(mailbox->mb_ctx, ack_idx, LP_MAILBOX_ACK);
|
||||
/* Clear ACK messages status */
|
||||
lp_core_mailbox_impl_intr_clear(mailbox->mb_ctx, BIT(ack_idx));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_mailbox_receive_async(lp_mailbox_t mailbox, uint32_t count, lp_core_mailbox_callback_t cb)
|
||||
{
|
||||
/* Make sure another asynchronous transaction is not on-going */
|
||||
if (mailbox == NULL || count == 0 || cb == NULL || mb_async_mode(mailbox)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
mailbox->rcv_callback = cb;
|
||||
mailbox->rcv_remaining = count;
|
||||
/* Enable interrupts for receive */
|
||||
s_isr_arg = mailbox;
|
||||
lp_core_mailbox_impl_intr_enable(mailbox->mb_ctx, LP_MAILBOX_RX_MSG_MASK);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_mailbox_receive_async_cancel(lp_mailbox_t mailbox, uint32_t* remaining)
|
||||
{
|
||||
if (mailbox == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!mb_async_mode(mailbox)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
lp_core_mailbox_impl_intr_disable(mailbox->mb_ctx, LP_MAILBOX_RX_MSG_MASK);
|
||||
mailbox->rcv_callback = NULL;
|
||||
if (remaining) {
|
||||
*remaining = mailbox->rcv_remaining;
|
||||
}
|
||||
s_isr_arg = NULL;
|
||||
ulp_lp_core_intr_enable();
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "ulp_lp_core_print.h"
|
||||
#include "riscv/rvruntime-frames.h"
|
||||
|
||||
#if CONFIG_ULP_PANIC_OUTPUT_ENABLE
|
||||
|
||||
static void dump_stack(RvExcFrame *frame, int exccause)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
uint32_t sp = frame->sp;
|
||||
lp_core_print_str("\n\nStack memory:\n");
|
||||
const int per_line = 8;
|
||||
for (i = 0; i < 1024; i += per_line * sizeof(uint32_t)) {
|
||||
uint32_t *spp = (uint32_t *)(sp + i);
|
||||
lp_core_print_hex(sp + i);
|
||||
lp_core_print_str(": ");
|
||||
for (int y = 0; y < per_line; y++) {
|
||||
lp_core_print_str("0x");
|
||||
lp_core_print_hex(spp[y]);
|
||||
lp_core_print_char(y == per_line - 1 ? '\n' : ' ');
|
||||
}
|
||||
}
|
||||
lp_core_print_str("\n");
|
||||
}
|
||||
|
||||
static const char *desc[] = {
|
||||
"MEPC ", "RA ", "SP ", "GP ", "TP ", "T0 ", "T1 ", "T2 ",
|
||||
"S0/FP ", "S1 ", "A0 ", "A1 ", "A2 ", "A3 ", "A4 ", "A5 ",
|
||||
"A6 ", "A7 ", "S2 ", "S3 ", "S4 ", "S5 ", "S6 ", "S7 ",
|
||||
"S8 ", "S9 ", "S10 ", "S11 ", "T3 ", "T4 ", "T5 ", "T6 ",
|
||||
"MSTATUS ", "MTVEC ", "MCAUSE ", "MTVAL ", "MHARTID "
|
||||
};
|
||||
|
||||
static const char *reason[] = {
|
||||
NULL,
|
||||
"Instruction access fault",
|
||||
"Illegal instruction",
|
||||
"Breakpoint",
|
||||
"Load address misaligned",
|
||||
"Load access fault",
|
||||
"Store address misaligned",
|
||||
"Store access fault",
|
||||
};
|
||||
|
||||
void ulp_lp_core_panic_handler(RvExcFrame *frame, int exccause)
|
||||
{
|
||||
#define DIM(arr) (sizeof(arr)/sizeof(*arr))
|
||||
|
||||
const char *exccause_str = "Unhandled interrupt/Unknown cause";
|
||||
|
||||
if (exccause < DIM(reason) && reason[exccause] != NULL) {
|
||||
exccause_str = reason[exccause];
|
||||
}
|
||||
|
||||
lp_core_print_str("Guru Meditation Error: LP Core panic'ed ");
|
||||
lp_core_print_str(exccause_str);
|
||||
lp_core_print_str("\n");
|
||||
lp_core_print_str("Core 0 register dump:\n");
|
||||
|
||||
uint32_t* frame_ints = (uint32_t*) frame;
|
||||
for (int x = 0; x < DIM(desc); x++) {
|
||||
if (desc[x][0] != 0) {
|
||||
const int not_last = (x + 1) % 4;
|
||||
lp_core_print_str(desc[x]);
|
||||
lp_core_print_str(": 0x");
|
||||
lp_core_print_hex(frame_ints[x]);
|
||||
lp_core_print_char(not_last ? ' ' : '\n');
|
||||
}
|
||||
}
|
||||
|
||||
dump_stack(frame, exccause);
|
||||
|
||||
/* idf-monitor uses this string to mark the end of a panic dump */
|
||||
lp_core_print_str("ELF file SHA256: No SHA256 Embedded\n");
|
||||
|
||||
while (1) {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //#if CONFIG_ULP_PANIC_OUTPUT_ENABLE
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc.h"
|
||||
#include "riscv/encoding.h"
|
||||
#include "riscv/csr.h"
|
||||
|
||||
#if CONFIG_ULP_LP_CORE_MEMPROT
|
||||
|
||||
/*
|
||||
* Default PMP layout for the LP-CPU (RISC-V, 16 entries, 128-byte TOR granularity).
|
||||
*
|
||||
* The LP-CPU always runs in M-mode. PMP entries with the L bit set apply to M-mode.
|
||||
* A catch-all entry with L+NONE at the end denies all unmatched accesses.
|
||||
*
|
||||
* Normal mode (code and data in LP RAM):
|
||||
* Entry 0-1: [SOC_RTC_DRAM_LOW, _lp_text_end) → RX (vector table, handlers, .text, .rodata)
|
||||
* Entry 2: [_lp_text_end, SOC_RTC_DRAM_HIGH) → RW (.data, .bss, stack, shared_mem)
|
||||
* Entry 3-4: LP Peripherals → RW
|
||||
* Entry 5-6: HP UART (if CONFIG_ULP_HP_UART_CONSOLE_PRINT) → RW
|
||||
*
|
||||
* Entry 15 (catch-all) → NONE (deny-by-default for all unmatched regions)
|
||||
*/
|
||||
|
||||
/* Linker-exported PMP boundary symbols (see lp_core_riscv.ld) */
|
||||
extern int _lp_text_end;
|
||||
|
||||
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT
|
||||
/* Cover UART0 and UART1 (each 0x1000) so CONFIG_ESP_CONSOLE_UART_NUM 0 or 1 works. */
|
||||
#define HP_UART_PMP_END (DR_REG_UART1_BASE + 0x1000UL)
|
||||
#endif
|
||||
|
||||
void lp_core_configure_pmp(void)
|
||||
{
|
||||
/*
|
||||
* PMP_ENTRY_SET(ENTRY, ADDR, CFG) expands to inline assembly where ENTRY
|
||||
* must be a compile-time integer literal — a variable cannot be used.
|
||||
* Entry numbers are therefore hardcoded explicitly below.
|
||||
*/
|
||||
const unsigned NONE = PMP_L;
|
||||
const unsigned RX = PMP_L | PMP_R | PMP_X;
|
||||
const unsigned RW = PMP_L | PMP_R | PMP_W;
|
||||
|
||||
/* --- LP RAM text region: vector table + handlers (+ .text/.rodata in normal mode) ---
|
||||
* Entry 0: lower TOR bound (LP RAM base), no permissions
|
||||
* Entry 1: upper bound = _lp_text_end, permissions = RX */
|
||||
PMP_ENTRY_SET(0, SOC_RTC_DRAM_LOW, NONE);
|
||||
PMP_ENTRY_SET(1, (uintptr_t)&_lp_text_end, PMP_TOR | RX);
|
||||
|
||||
/* --- LP RAM data region: .data/.bss/stack/shared_mem ---
|
||||
* Entry 2: upper bound = LP RAM high, permissions = RW */
|
||||
PMP_ENTRY_SET(2, SOC_RTC_DRAM_HIGH, PMP_TOR | RW);
|
||||
|
||||
/* --- LP Peripherals ---
|
||||
* Entry 3: lower TOR bound
|
||||
* Entry 4: upper bound, permissions = RW */
|
||||
PMP_ENTRY_SET(3, SOC_LP_PERIPH_LOW, NONE);
|
||||
PMP_ENTRY_SET(4, SOC_LP_PERIPH_HIGH, PMP_TOR | RW);
|
||||
|
||||
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT
|
||||
/* --- HP UART (entries 5-6) --- */
|
||||
PMP_ENTRY_SET(5, DR_REG_UART0_BASE, NONE);
|
||||
PMP_ENTRY_SET(6, HP_UART_PMP_END, PMP_TOR | RW);
|
||||
#endif
|
||||
|
||||
/* --- Catch-all: deny all unmatched addresses ---
|
||||
* Entry 15 covers the entire 32-bit address space with no permissions.
|
||||
* Any access that didn't match the entries above is denied. */
|
||||
PMP_ENTRY_SET(15, PMPADDR_ALL, PMP_NAPOT | NONE);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ULP_LP_CORE_MEMPROT */
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "ulp_lp_core_uart.h"
|
||||
#include "hal/uart_hal.h"
|
||||
#include "esp_rom_serial_output.h"
|
||||
|
||||
#define BINARY_SUPPORT 1
|
||||
|
||||
#define is_digit(c) ((c >= '0') && (c <= '9'))
|
||||
|
||||
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT
|
||||
void __attribute__((alias("hp_uart_send_char"))) lp_core_print_char(char c);
|
||||
|
||||
static void hp_uart_send_char(char t)
|
||||
{
|
||||
uart_dev_t *uart = (uart_dev_t *)UART_LL_GET_HW(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
|
||||
while (uart_ll_get_txfifo_len(uart) < 2) {
|
||||
;
|
||||
}
|
||||
uart_ll_write_txfifo(uart, &t, 1);
|
||||
}
|
||||
#elif !CONFIG_ULP_ROM_PRINT_ENABLE && CONFIG_SOC_ULP_LP_UART_SUPPORTED
|
||||
void __attribute__((alias("lp_uart_send_char"))) lp_core_print_char(char c);
|
||||
#define LP_UART_PORT_NUM LP_UART_NUM_0
|
||||
static void lp_uart_send_char(char c)
|
||||
{
|
||||
int tx_len = 0;
|
||||
int loop_cnt = 0;
|
||||
/* Write one byte to LP UART. Break after few iterations if we are stuck for any reason. */
|
||||
while (tx_len != 1 && loop_cnt < 1000) {
|
||||
tx_len = lp_core_uart_tx_chars(LP_UART_PORT_NUM, (const void *)&c, 1);
|
||||
loop_cnt++;
|
||||
}
|
||||
}
|
||||
#else
|
||||
void __attribute__((alias("lp_rom_send_char"))) lp_core_print_char(char c);
|
||||
static void lp_rom_send_char(char c)
|
||||
{
|
||||
esp_rom_output_putc(c);
|
||||
}
|
||||
#endif // CONFIG_ULP_HP_UART_CONSOLE_PRINT
|
||||
|
||||
#if !CONFIG_ULP_ROM_PRINT_ENABLE
|
||||
|
||||
// Ported over ROM function _cvt()
|
||||
static int lp_core_cvt(unsigned long long val, char *buf, long radix, char *digits)
|
||||
{
|
||||
#ifdef SUPPORT_LITTLE_RADIX
|
||||
char temp[64];
|
||||
#else
|
||||
char temp[32];
|
||||
#endif
|
||||
char *cp = temp;
|
||||
int length = 0;
|
||||
|
||||
if (val == 0) {
|
||||
/* Special case */
|
||||
*cp++ = '0';
|
||||
} else {
|
||||
while (val) {
|
||||
*cp++ = digits[val % radix];
|
||||
val /= radix;
|
||||
}
|
||||
}
|
||||
while (cp != temp) {
|
||||
*buf++ = *--cp;
|
||||
length++;
|
||||
}
|
||||
*buf = '\0';
|
||||
return (length);
|
||||
}
|
||||
|
||||
// Ported over ROM function ets_vprintf()
|
||||
static int lp_core_ets_vprintf(void (*putc)(char c), const char *fmt, va_list ap)
|
||||
{
|
||||
#ifdef BINARY_SUPPORT
|
||||
char buf[sizeof(long long) * 8];
|
||||
#else
|
||||
char buf[32];
|
||||
#endif
|
||||
char c, sign, *cp = buf;
|
||||
int left_prec, right_prec, zero_fill, pad, pad_on_right, islong, islonglong;
|
||||
long long val = 0;
|
||||
int res = 0, length = 0;
|
||||
|
||||
while ((c = *fmt++) != '\0') {
|
||||
if (c == '%') {
|
||||
c = *fmt++;
|
||||
left_prec = right_prec = pad_on_right = islong = islonglong = 0;
|
||||
if (c == '-') {
|
||||
c = *fmt++;
|
||||
pad_on_right++;
|
||||
}
|
||||
if (c == '0') {
|
||||
zero_fill = true;
|
||||
c = *fmt++;
|
||||
} else {
|
||||
zero_fill = false;
|
||||
}
|
||||
while (is_digit(c)) {
|
||||
left_prec = (left_prec * 10) + (c - '0');
|
||||
c = *fmt++;
|
||||
}
|
||||
if (c == '.') {
|
||||
c = *fmt++;
|
||||
zero_fill++;
|
||||
while (is_digit(c)) {
|
||||
right_prec = (right_prec * 10) + (c - '0');
|
||||
c = *fmt++;
|
||||
}
|
||||
} else {
|
||||
right_prec = left_prec;
|
||||
}
|
||||
sign = '\0';
|
||||
if (c == 'l') {
|
||||
c = *fmt++;
|
||||
islong = 1;
|
||||
if (c == 'l') {
|
||||
c = *fmt++;
|
||||
islonglong = 1;
|
||||
islong = 0;
|
||||
}
|
||||
}
|
||||
switch (c) {
|
||||
case 'p':
|
||||
islong = 1;
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'x':
|
||||
case 'X':
|
||||
case 'u':
|
||||
case 'U':
|
||||
#ifdef BINARY_SUPPORT
|
||||
case 'b':
|
||||
case 'B':
|
||||
#endif
|
||||
if (islonglong) {
|
||||
val = va_arg(ap, long long);
|
||||
} else if (islong) {
|
||||
val = (long long)va_arg(ap, long);
|
||||
} else {
|
||||
val = (long long)va_arg(ap, int);
|
||||
}
|
||||
|
||||
if ((c == 'd') || (c == 'D')) {
|
||||
if (val < 0) {
|
||||
sign = '-';
|
||||
val = -val;
|
||||
}
|
||||
} else {
|
||||
if (islonglong) {
|
||||
;
|
||||
} else if (islong) {
|
||||
val &= ((long long)1 << (sizeof(long) * 8)) - 1;
|
||||
} else {
|
||||
val &= ((long long)1 << (sizeof(int) * 8)) - 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 'p':
|
||||
(*putc)('0');
|
||||
(*putc)('x');
|
||||
zero_fill = true;
|
||||
left_prec = sizeof(unsigned long) * 2;
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'u':
|
||||
case 'U':
|
||||
case 'x':
|
||||
case 'X':
|
||||
switch (c) {
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'u':
|
||||
case 'U':
|
||||
length = lp_core_cvt(val, buf, 10, "0123456789");
|
||||
break;
|
||||
case 'p':
|
||||
case 'x':
|
||||
length = lp_core_cvt(val, buf, 16, "0123456789abcdef");
|
||||
break;
|
||||
case 'X':
|
||||
length = lp_core_cvt(val, buf, 16, "0123456789ABCDEF");
|
||||
break;
|
||||
}
|
||||
cp = buf;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
cp = va_arg(ap, char *);
|
||||
if (cp == NULL) {
|
||||
cp = "<null>";
|
||||
}
|
||||
length = 0;
|
||||
while (cp[length] != '\0') {
|
||||
length++;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
case 'C':
|
||||
c = va_arg(ap, int /*char*/);
|
||||
(*putc)(c);
|
||||
res++;
|
||||
continue;
|
||||
#ifdef BINARY_SUPPORT
|
||||
case 'b':
|
||||
case 'B':
|
||||
length = left_prec;
|
||||
if (left_prec == 0) {
|
||||
if (islonglong) {
|
||||
length = sizeof(long long) * 8;
|
||||
} else if (islong) {
|
||||
length = sizeof(long) * 8;
|
||||
} else {
|
||||
length = sizeof(int) * 8;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < length - 1; i++) {
|
||||
buf[i] = ((val & ((long long)1 << i)) ? '1' : '.');
|
||||
}
|
||||
cp = buf;
|
||||
break;
|
||||
#endif
|
||||
case '%':
|
||||
(*putc)('%');
|
||||
break;
|
||||
default:
|
||||
(*putc)('%');
|
||||
(*putc)(c);
|
||||
res += 2;
|
||||
}
|
||||
pad = left_prec - length;
|
||||
if (sign != '\0') {
|
||||
pad--;
|
||||
}
|
||||
if (zero_fill) {
|
||||
c = '0';
|
||||
if (sign != '\0') {
|
||||
(*putc)(sign);
|
||||
res++;
|
||||
sign = '\0';
|
||||
}
|
||||
} else {
|
||||
c = ' ';
|
||||
}
|
||||
if (!pad_on_right) {
|
||||
while (pad-- > 0) {
|
||||
(*putc)(c);
|
||||
res++;
|
||||
}
|
||||
}
|
||||
if (sign != '\0') {
|
||||
(*putc)(sign);
|
||||
res++;
|
||||
}
|
||||
while (length-- > 0) {
|
||||
c = *cp++;
|
||||
(*putc)(c);
|
||||
res++;
|
||||
}
|
||||
if (pad_on_right) {
|
||||
while (pad-- > 0) {
|
||||
(*putc)(' ');
|
||||
res++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(*putc)(c);
|
||||
res++;
|
||||
}
|
||||
}
|
||||
return (res);
|
||||
}
|
||||
|
||||
int lp_core_printf(const char* format, ...)
|
||||
{
|
||||
/* Create a variable argument list */
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
/* Pass the input string and the argument list to ets_vprintf() */
|
||||
int ret = lp_core_ets_vprintf(lp_core_print_char, format, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_ULP_ROM_PRINT_ENABLE */
|
||||
|
||||
void lp_core_print_str(const char *str)
|
||||
{
|
||||
for (int i = 0; str[i] != 0; i++) {
|
||||
lp_core_print_char(str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void lp_core_print_hex(int h)
|
||||
{
|
||||
int x;
|
||||
int c;
|
||||
// Does not print '0x', only the digits (8 digits to print)
|
||||
for (x = 0; x < 8; x++) {
|
||||
c = (h >> 28) & 0xf; // extract the leftmost byte
|
||||
if (c < 10) {
|
||||
lp_core_print_char('0' + c);
|
||||
} else {
|
||||
lp_core_print_char('a' + c - 10);
|
||||
}
|
||||
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
|
||||
}
|
||||
}
|
||||
|
||||
void lp_core_print_dec_two_digits(int d)
|
||||
{
|
||||
// can print at most 2 digits!
|
||||
int n1, n2;
|
||||
n1 = d % 10; // extract ones digit
|
||||
n2 = d / 10; // extract tens digit
|
||||
if (n2 == 0) {
|
||||
lp_core_print_char(' ');
|
||||
} else {
|
||||
lp_core_print_char(n2 + '0');
|
||||
}
|
||||
lp_core_print_char(n1 + '0');
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_LP_SPI_SUPPORTED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "esp_err.h"
|
||||
#include "ulp_lp_core_spi.h"
|
||||
#include "ulp_lp_core_utils.h"
|
||||
#include "hal/lp_spi_ll.h"
|
||||
|
||||
static lp_spi_ll_dev_t *lp_spi_dev = LP_SPI_LL_GET_HW();
|
||||
|
||||
/* Tracks an outstanding lp_core_lp_spi_slave_arm() that has not yet been
|
||||
* paired with a slave_wait(). The LP_SPI_CMD.reg_usr bit is not a reliable
|
||||
* "busy" indicator in slave mode (the slave holds it set while merely armed
|
||||
* and waiting for the master's SCK), so we serialise arm/wait in software.
|
||||
*/
|
||||
static volatile bool s_slave_armed = false;
|
||||
|
||||
/* LP SPI data buffer is W0..W15 (16 x 32-bit = 64 B). Per TRM, transfers
|
||||
* beyond 64 B repeatedly fetch from W15[31:24], so byte 63 is replayed for
|
||||
* every byte past 64. Skipping W15 (cap at 60 B / W0..W14) avoids that
|
||||
* aliasing region entirely; longer transfers are split into back-to-back
|
||||
* 60 B hardware transactions.
|
||||
*/
|
||||
#define LP_SPI_MAX_DATA_REG_NUM ((LP_SPI_LL_MAX_BUFFER_SIZE / 4) - 1) /* 15 */
|
||||
#define LP_SPI_CHUNK_BYTES (LP_SPI_MAX_DATA_REG_NUM * 4) /* 60 */
|
||||
|
||||
static inline esp_err_t lp_core_spi_wait_for_interrupt(int32_t cycles_to_wait)
|
||||
{
|
||||
uint32_t timeout_start = ulp_lp_core_get_cpu_cycles();
|
||||
while (!lp_spi_ll_get_int_trans_done(lp_spi_dev)) {
|
||||
if (ulp_lp_core_is_timeout_elapsed(timeout_start, cycles_to_wait)) {
|
||||
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////// Public APIs ///////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
/* Require at least one of tx_buffer/rx_buffer; length must be 0 when its
|
||||
* buffer is NULL.
|
||||
*/
|
||||
if (trans_desc == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (trans_desc->tx_buffer != NULL && trans_desc->tx_length == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (trans_desc->rx_buffer != NULL && trans_desc->rx_length == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* The peripheral has a single shared bit-length register
|
||||
* (LP_SPI_MS_DLEN.reg_ms_data_bitlen), so we program it for
|
||||
* max(tx_length, rx_length) bytes to avoid truncating the longer side.
|
||||
*/
|
||||
uint32_t tx_total = trans_desc->tx_buffer ? trans_desc->tx_length : 0;
|
||||
uint32_t rx_total = trans_desc->rx_buffer ? trans_desc->rx_length : 0;
|
||||
uint32_t bus_total = tx_total > rx_total ? tx_total : rx_total;
|
||||
if (bus_total == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Make sure that we do not have any ongoing transactions */
|
||||
if (lp_spi_ll_is_busy(lp_spi_dev)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
|
||||
|
||||
/* Configure dummy bits */
|
||||
lp_spi_ll_set_dummy_en(lp_spi_dev, trans_desc->dummy_bits != 0);
|
||||
if (trans_desc->dummy_bits) {
|
||||
lp_spi_ll_set_dummy_cyclelen(lp_spi_dev, trans_desc->dummy_bits);
|
||||
}
|
||||
|
||||
/* Configure the command and command bit length */
|
||||
lp_spi_ll_set_command_en(lp_spi_dev, trans_desc->command_bits != 0);
|
||||
if (trans_desc->command_bits) {
|
||||
lp_spi_ll_set_command_bitlen(lp_spi_dev, trans_desc->command_bits);
|
||||
lp_spi_ll_set_command_value(lp_spi_dev,
|
||||
lp_spi_ll_get_wr_bit_order(lp_spi_dev)
|
||||
? trans_desc->command
|
||||
: __builtin_bswap32(trans_desc->command << (32 - trans_desc->command_bits)));
|
||||
}
|
||||
|
||||
/* Configure the address and address bit length */
|
||||
lp_spi_ll_set_address_en(lp_spi_dev, trans_desc->address_bits != 0);
|
||||
if (trans_desc->address_bits) {
|
||||
lp_spi_ll_set_address_bitlen(lp_spi_dev, trans_desc->address_bits - 1);
|
||||
lp_spi_ll_set_address_value(lp_spi_dev,
|
||||
lp_spi_ll_get_wr_bit_order(lp_spi_dev)
|
||||
? __builtin_bswap32(trans_desc->address)
|
||||
: trans_desc->address << (32 - trans_desc->address_bits));
|
||||
}
|
||||
|
||||
/* MOSI gated by tx_buffer to avoid clocking stale W0..W15 on read-only. */
|
||||
lp_spi_ll_set_mosi_en(lp_spi_dev, trans_desc->tx_buffer != NULL);
|
||||
lp_spi_ll_set_miso_en(lp_spi_dev, trans_desc->rx_buffer != NULL);
|
||||
|
||||
/* Drive the bus one hardware transaction at a time. Each iteration:
|
||||
* 1. clip ``chunk`` to the remaining bytes, capped at LP_SPI_CHUNK_BYTES;
|
||||
* 2. preload ``tx_chunk`` TX bytes into W0.. (only if the caller still
|
||||
* has TX bytes left for this chunk -- TX may end before RX);
|
||||
* 3. program the shared bit-length register for ``chunk * 8`` SCKs;
|
||||
* 4. AFIFO reset + apply config + kick (ordering as per the TRM).
|
||||
* 5. block on TRANS_DONE.
|
||||
* 6. drain ``rx_chunk`` RX bytes from W0.. (only if the caller still
|
||||
* wants RX bytes for this chunk -- RX may end before TX).
|
||||
*/
|
||||
uint32_t bus_done = 0;
|
||||
while (bus_done < bus_total) {
|
||||
uint32_t chunk = bus_total - bus_done;
|
||||
if (chunk > LP_SPI_CHUNK_BYTES) {
|
||||
chunk = LP_SPI_CHUNK_BYTES;
|
||||
}
|
||||
|
||||
if (trans_desc->tx_buffer != NULL && bus_done < tx_total) {
|
||||
uint32_t tx_chunk = tx_total - bus_done;
|
||||
if (tx_chunk > chunk) {
|
||||
tx_chunk = chunk;
|
||||
}
|
||||
lp_spi_ll_write_buffer_bytes(lp_spi_dev,
|
||||
(const uint8_t *)trans_desc->tx_buffer + bus_done,
|
||||
tx_chunk);
|
||||
}
|
||||
|
||||
lp_spi_ll_set_data_bitlen(lp_spi_dev, chunk * 8 - 1);
|
||||
|
||||
lp_spi_ll_reset_fifos(lp_spi_dev);
|
||||
|
||||
lp_spi_ll_apply_config(lp_spi_dev);
|
||||
lp_spi_ll_start_user_transaction(lp_spi_dev);
|
||||
|
||||
ret = lp_core_spi_wait_for_interrupt(cycles_to_wait);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (trans_desc->rx_buffer != NULL && bus_done < rx_total) {
|
||||
uint32_t rx_chunk = rx_total - bus_done;
|
||||
if (rx_chunk > chunk) {
|
||||
rx_chunk = chunk;
|
||||
}
|
||||
lp_spi_ll_read_buffer_bytes(lp_spi_dev,
|
||||
(uint8_t *)trans_desc->rx_buffer + bus_done,
|
||||
rx_chunk);
|
||||
}
|
||||
|
||||
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
|
||||
|
||||
bus_done += chunk;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Arm = preload TX + start user phase, return immediately. Pair with
|
||||
* lp_core_lp_spi_slave_wait(). Splitting arm/wait lets the caller signal
|
||||
* the master only after the slave is actually listening for SCK.
|
||||
*/
|
||||
esp_err_t lp_core_lp_spi_slave_arm(lp_spi_transaction_t *trans_desc)
|
||||
{
|
||||
/* Require at least one of tx_buffer/rx_buffer; length must be 0 when its
|
||||
* buffer is NULL.
|
||||
*/
|
||||
if (trans_desc == NULL ||
|
||||
(trans_desc->rx_buffer == NULL && trans_desc->tx_buffer == NULL)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (trans_desc->rx_buffer != NULL && trans_desc->rx_length == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (trans_desc->tx_buffer != NULL && trans_desc->tx_length == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Refuse to re-arm while a previous arm has not been waited on,
|
||||
* otherwise the preload below would clobber its W0..W15 mid-transfer.
|
||||
*/
|
||||
if (s_slave_armed) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Clear stale TRANS_DONE so the paired wait sees only this arm. */
|
||||
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
|
||||
|
||||
/* Slave direction is reversed vs. master: MOSI carries master->slave
|
||||
* (caller RX), MISO carries slave->master (caller TX).
|
||||
*/
|
||||
lp_spi_ll_set_mosi_en(lp_spi_dev, trans_desc->rx_buffer != NULL);
|
||||
lp_spi_ll_set_miso_en(lp_spi_dev, trans_desc->tx_buffer != NULL);
|
||||
|
||||
/* Same single shared bit-length register as master
|
||||
* (LP_SPI_MS_DLEN.reg_ms_data_bitlen). The slave runs a single hardware
|
||||
* shot capped at LP_SPI_CHUNK_BYTES (60 B, W0..W14, W15 reserved per
|
||||
* TRM); longer transfers must be split by the caller into successive
|
||||
* arm/wait pairs.
|
||||
*/
|
||||
uint32_t rx_total = trans_desc->rx_buffer ? trans_desc->rx_length : 0;
|
||||
uint32_t tx_total = trans_desc->tx_buffer ? trans_desc->tx_length : 0;
|
||||
uint32_t arm_bytes = rx_total > tx_total ? rx_total : tx_total;
|
||||
if (arm_bytes > LP_SPI_CHUNK_BYTES) {
|
||||
arm_bytes = LP_SPI_CHUNK_BYTES;
|
||||
}
|
||||
lp_spi_ll_set_data_bitlen(lp_spi_dev, arm_bytes * 8 - 1);
|
||||
|
||||
/* Preload TX into W0.. for the slave to drive on MISO when the master
|
||||
* starts clocking.
|
||||
*/
|
||||
if (trans_desc->tx_buffer != NULL) {
|
||||
uint32_t tx_preload = tx_total > LP_SPI_CHUNK_BYTES
|
||||
? LP_SPI_CHUNK_BYTES
|
||||
: tx_total;
|
||||
lp_spi_ll_write_buffer_bytes(lp_spi_dev,
|
||||
(const uint8_t *)trans_desc->tx_buffer,
|
||||
tx_preload);
|
||||
}
|
||||
|
||||
/* Reset AFIFOs after preload, before start. */
|
||||
lp_spi_ll_reset_fifos(lp_spi_dev);
|
||||
|
||||
/* Skip apply_config() in slave mode: reg_update is master-only and
|
||||
* re-triggering it here was observed to clock out the previous
|
||||
* transaction's data.
|
||||
*/
|
||||
lp_spi_ll_start_user_transaction(lp_spi_dev);
|
||||
|
||||
s_slave_armed = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Block on TRANS_DONE from the matching arm, then drain whatever the master
|
||||
* actually clocked into W0..W15. Pair with lp_core_lp_spi_slave_arm().
|
||||
*/
|
||||
esp_err_t lp_core_lp_spi_slave_wait(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait)
|
||||
{
|
||||
if (trans_desc == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
/* Reject ``wait()`` without a preceding ``arm()`` -- otherwise we would
|
||||
* block on whatever stale TRANS_DONE happens to be latched.
|
||||
*/
|
||||
if (!s_slave_armed) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Block until TRANS_DONE or timeout (cycles_to_wait is in LP CPU cycles). */
|
||||
esp_err_t ret = lp_core_spi_wait_for_interrupt(cycles_to_wait);
|
||||
if (ret != ESP_OK) {
|
||||
/* Clear the armed latch on the timeout path too so the caller can
|
||||
* recover by issuing a fresh ``arm()``; otherwise the next
|
||||
* ``arm()`` would return ESP_ERR_INVALID_STATE forever.
|
||||
* ``lp_core_spi_wait_for_interrupt()`` already cleared
|
||||
* TRANS_DONE on its timeout exit, so no extra latch clear here.
|
||||
*/
|
||||
s_slave_armed = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
s_slave_armed = false;
|
||||
|
||||
/* Clear the latch so the next arm starts from a clean state. */
|
||||
lp_spi_ll_clear_int_trans_done(lp_spi_dev);
|
||||
|
||||
/* The master, not the slave, drives SCK, so the actually-received length
|
||||
* is decided by the master and only known after TRANS_DONE. Query the
|
||||
* hardware bit counter (LP_SPI_SLAVE1.reg_slv_data_bitlen), clamp it
|
||||
* against the caller's rx_length, round up to whole bytes, then drain
|
||||
* that many bytes from W0.. into rx_buffer.
|
||||
*/
|
||||
if (trans_desc->rx_buffer != NULL) {
|
||||
uint32_t rx_total = trans_desc->rx_length;
|
||||
uint32_t slave_bitlen = lp_spi_ll_get_slave_rcv_bitlen(lp_spi_dev);
|
||||
uint32_t req_bitlen = rx_total * 8;
|
||||
uint32_t valid_bitlen = slave_bitlen > req_bitlen ? req_bitlen : slave_bitlen;
|
||||
uint32_t valid_bytes = (valid_bitlen + 7) / 8;
|
||||
if (valid_bytes > rx_total) {
|
||||
valid_bytes = rx_total;
|
||||
}
|
||||
if (valid_bytes > 0) {
|
||||
lp_spi_ll_read_buffer_bytes(lp_spi_dev,
|
||||
(uint8_t *)trans_desc->rx_buffer,
|
||||
valid_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t cycles_to_wait)
|
||||
{
|
||||
esp_err_t ret = lp_core_lp_spi_slave_arm(trans_desc);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
return lp_core_lp_spi_slave_wait(trans_desc, cycles_to_wait);
|
||||
}
|
||||
|
||||
#endif /* SOC_LP_SPI_SUPPORTED */
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_rom_caps.h"
|
||||
#include "rom/ets_sys.h"
|
||||
#include "ulp_lp_core_utils.h"
|
||||
#include "ulp_lp_core_lp_timer_shared.h"
|
||||
#include "ulp_lp_core_memory_shared.h"
|
||||
#include "ulp_lp_core_print.h"
|
||||
#if CONFIG_ULP_LP_CORE_MEMPROT
|
||||
#include "ulp_lp_core_pmp.h"
|
||||
#endif
|
||||
|
||||
extern void main();
|
||||
|
||||
/* Initialize lp core related system functions before calling user's main*/
|
||||
void lp_core_startup()
|
||||
{
|
||||
|
||||
#if CONFIG_ULP_HP_UART_CONSOLE_PRINT && ESP_ROM_HAS_LP_ROM
|
||||
ets_install_putc1(lp_core_print_char);
|
||||
#endif
|
||||
|
||||
ulp_lp_core_update_wakeup_cause();
|
||||
|
||||
#if CONFIG_ULP_LP_CORE_MEMPROT
|
||||
lp_core_configure_pmp();
|
||||
#endif
|
||||
|
||||
main();
|
||||
|
||||
ulp_lp_core_memory_shared_cfg_t* shared_mem = ulp_lp_core_memory_shared_cfg_get();
|
||||
|
||||
#if SOC_RTC_TIMER_SUPPORTED
|
||||
uint64_t sleep_duration_ticks = shared_mem->sleep_duration_ticks;
|
||||
|
||||
if (sleep_duration_ticks) {
|
||||
ulp_lp_core_lp_timer_set_wakeup_ticks(sleep_duration_ticks);
|
||||
}
|
||||
#endif // SOC_RTC_TIMER_SUPPORTED
|
||||
|
||||
ulp_lp_core_halt();
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/touch_sensor_ll.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/* Check Touch Channel correctness */
|
||||
#define LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(channel) \
|
||||
{ \
|
||||
if (channel >= SOC_TOUCH_MAX_CHAN_ID || \
|
||||
channel < SOC_TOUCH_MIN_CHAN_ID) { \
|
||||
return ESP_ERR_INVALID_ARG; \
|
||||
} \
|
||||
} \
|
||||
|
||||
esp_err_t lp_core_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!raw_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read raw touch data */
|
||||
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
|
||||
for (int i = 0; i < engaged_sampler_cnt; i++) {
|
||||
// Only have smooth data on V3, no raw data
|
||||
touch_ll_read_chan_data((int)touch_num, i + 1, TOUCH_LL_READ_SMOOTH, &raw_data[i]);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!benchmark) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read benchmark data */
|
||||
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
|
||||
for (int i = 0; i < engaged_sampler_cnt; i++) {
|
||||
touch_ll_read_chan_data((int)touch_num, i + 1, TOUCH_LL_READ_BENCHMARK, &benchmark[i]);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!smooth_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read smoothened touch sensor data */
|
||||
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
|
||||
for (int i = 0; i < engaged_sampler_cnt; i++) {
|
||||
touch_ll_read_chan_data((int)touch_num, i + 1, TOUCH_LL_READ_SMOOTH, &smooth_data[i]);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_touch_pad_reset_benchmark(int touch_num, uint32_t mask)
|
||||
{
|
||||
(void) mask; // Currently not support, reserved for future use
|
||||
/* Check Arguments */
|
||||
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Reset benchmark */
|
||||
touch_ll_reset_chan_benchmark(BIT(touch_num));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!raw_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read raw touch data */
|
||||
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
|
||||
for (int i = 0; i < engaged_sampler_cnt; i++) {
|
||||
// Only have smooth data on V3, no raw data
|
||||
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_SMOOTH, i + 1, &raw_data[i]);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!benchmark) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read benchmark data */
|
||||
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
|
||||
for (int i = 0; i < engaged_sampler_cnt; i++) {
|
||||
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_BENCHMARK, i + 1, &benchmark[i]);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!smooth_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
LP_CORE_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read smoothened touch sensor data */
|
||||
uint32_t engaged_sampler_cnt = touch_ll_sample_cfg_get_engaged_num();
|
||||
for (int i = 0; i < engaged_sampler_cnt; i++) {
|
||||
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_SMOOTH, i + 1, &smooth_data[i]);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lp_core_touch_pad_sleep_channel_reset_benchmark(uint32_t mask)
|
||||
{
|
||||
(void) mask; // Currently not support, reserved for future use
|
||||
/* Reset benchmark */
|
||||
touch_ll_sleep_reset_benchmark();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "ulp_lp_core_uart.h"
|
||||
#include "ulp_lp_core_utils.h"
|
||||
#include "soc/lp_uart_struct.h"
|
||||
#include "hal/uart_hal.h"
|
||||
|
||||
#define LP_UART_ERR_INT_FLAG (UART_INTR_PARITY_ERR | UART_INTR_FRAM_ERR)
|
||||
#define LP_UART_TX_INT_FLAG (UART_INTR_TX_DONE)
|
||||
#define LP_UART_RX_INT_FLAG (UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_OVF)
|
||||
#define LP_UART_TOUT_THRESH_DEFAULT (10U)
|
||||
#define LP_UART_FULL_THRESH_DEFAULT (10U)
|
||||
|
||||
/* LP UART HAL Context */
|
||||
uart_hal_context_t hal = {
|
||||
.dev = (uart_dev_t *)UART_LL_GET_HW(LP_UART_NUM_0),
|
||||
};
|
||||
|
||||
static esp_err_t lp_core_uart_check_timeout(uint32_t intr_mask, int32_t timeout, uint32_t timeout_start)
|
||||
{
|
||||
if (ulp_lp_core_is_timeout_elapsed(timeout_start, timeout)) {
|
||||
/* Disable and clear interrupt bits */
|
||||
uart_hal_disable_intr_mask(&hal, intr_mask);
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int lp_core_uart_tx_chars(uart_port_t lp_uart_num, const void *src, size_t size)
|
||||
{
|
||||
(void)lp_uart_num;
|
||||
uint32_t tx_len = 0;
|
||||
|
||||
/* Argument sanity check */
|
||||
if (!src) {
|
||||
/* Invalid input arguments */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Nothing to do if the length is 0 */
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write the data to the Tx FIFO */
|
||||
uart_hal_write_txfifo(&hal, src, size, &tx_len);
|
||||
|
||||
/* Return the number of bytes written */
|
||||
return tx_len;
|
||||
}
|
||||
|
||||
void lp_core_uart_tx_flush(uart_port_t lp_uart_num)
|
||||
{
|
||||
(void)lp_uart_num;
|
||||
int loop_cnt = 0;
|
||||
|
||||
if (uart_ll_is_enabled(LP_UART_NUM_0)) {
|
||||
/* Wait for the Tx FIFO to be empty */
|
||||
while (!(uart_hal_get_intraw_mask(&hal) & (LP_UART_TX_INT_FLAG | LP_UART_ERR_INT_FLAG) && uart_hal_is_tx_idle(&hal))) {
|
||||
loop_cnt++;
|
||||
if (loop_cnt > 10000) {
|
||||
/* Bail out */
|
||||
break;
|
||||
}
|
||||
}
|
||||
uart_hal_clr_intsts_mask(&hal, LP_UART_TX_INT_FLAG | LP_UART_ERR_INT_FLAG);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t lp_core_uart_write_bytes(uart_port_t lp_uart_num, const void *src, size_t size, int32_t timeout)
|
||||
{
|
||||
(void)lp_uart_num;
|
||||
|
||||
/* Argument sanity check */
|
||||
if (!src) {
|
||||
/* Invalid input arguments */
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Nothing to do if the length is 0 */
|
||||
if (size == 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Enable the Tx done interrupt */
|
||||
uint32_t intr_mask = LP_UART_TX_INT_FLAG | LP_UART_ERR_INT_FLAG;
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
|
||||
/* Transmit data */
|
||||
uint32_t tx_len;
|
||||
uint32_t bytes_sent = 0;
|
||||
int32_t remaining_bytes = size;
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t intr_status = 0;
|
||||
uint32_t timeout_start = ulp_lp_core_get_cpu_cycles();
|
||||
|
||||
while (remaining_bytes > 0) {
|
||||
/* Write to the Tx FIFO */
|
||||
tx_len = 0;
|
||||
uart_hal_write_txfifo(&hal, src + bytes_sent, remaining_bytes, &tx_len);
|
||||
|
||||
if (tx_len) {
|
||||
/* We have managed to write some data to the Tx FIFO. Check Tx interrupt status */
|
||||
while (1) {
|
||||
/* Fetch the interrupt status */
|
||||
intr_status = uart_hal_get_intraw_mask(&hal);
|
||||
if (intr_status & LP_UART_TX_INT_FLAG) {
|
||||
/* Clear interrupt status and break */
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
break;
|
||||
} else if ((intr_status & LP_UART_ERR_INT_FLAG)) {
|
||||
/* Transaction error. Abort */
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Check for transaction timeout */
|
||||
ret = lp_core_uart_check_timeout(intr_mask, timeout, timeout_start);
|
||||
if (ret == ESP_ERR_TIMEOUT) {
|
||||
/* Timeout. Clear interrupt status and break */
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the byte counters */
|
||||
bytes_sent += tx_len;
|
||||
remaining_bytes -= tx_len;
|
||||
} else {
|
||||
/* Tx FIFO does not have empty slots. Check for transaction timeout */
|
||||
ret = lp_core_uart_check_timeout(intr_mask, timeout, timeout_start);
|
||||
if (ret == ESP_ERR_TIMEOUT) {
|
||||
/* Timeout. Clear interrupt status and break */
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int lp_core_uart_read_bytes(uart_port_t lp_uart_num, void *buf, size_t size, int32_t timeout)
|
||||
{
|
||||
(void)lp_uart_num;
|
||||
|
||||
/* Argument sanity check */
|
||||
if (!buf) {
|
||||
/* Invalid input arguments */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Nothing to do if the length is 0 */
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set the Rx interrupt thresholds */
|
||||
uart_hal_set_rx_timeout(&hal, LP_UART_TOUT_THRESH_DEFAULT);
|
||||
uart_hal_set_rxfifo_full_thr(&hal, LP_UART_FULL_THRESH_DEFAULT);
|
||||
|
||||
/* Enable the Rx interrupts */
|
||||
uint32_t intr_mask = LP_UART_RX_INT_FLAG | LP_UART_ERR_INT_FLAG;
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
/* Receive data */
|
||||
int rx_len = 0;
|
||||
uint32_t bytes_rcvd = 0;
|
||||
int32_t remaining_bytes = size;
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t intr_status = 0;
|
||||
uint32_t timeout_start = ulp_lp_core_get_cpu_cycles();
|
||||
|
||||
while (remaining_bytes > 0) {
|
||||
/* Drain only as many bytes as fit in the remaining buffer space */
|
||||
int fifo_len = uart_ll_get_rxfifo_len(hal.dev);
|
||||
rx_len = (fifo_len < remaining_bytes) ? fifo_len : remaining_bytes;
|
||||
|
||||
if (rx_len > 0) {
|
||||
uart_hal_read_rxfifo(&hal, (uint8_t *)(buf + bytes_rcvd), &rx_len);
|
||||
bytes_rcvd += rx_len;
|
||||
remaining_bytes -= rx_len;
|
||||
|
||||
/* RXFIFO_FULL / RXFIFO_TOUT raw bits are sticky; acknowledge them
|
||||
* so they do not short-circuit the next iteration. */
|
||||
uart_hal_clr_intsts_mask(&hal, LP_UART_RX_INT_FLAG);
|
||||
|
||||
if (remaining_bytes <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* FIFO overflow and parity/framing errors are terminal */
|
||||
intr_status = uart_hal_get_intraw_mask(&hal);
|
||||
if (intr_status & UART_INTR_RXFIFO_OVF) {
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
uart_hal_rxfifo_rst(&hal);
|
||||
break;
|
||||
} else if (intr_status & LP_UART_ERR_INT_FLAG) {
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Progress was made; restart the timeout window so callers with
|
||||
* a finite timeout can tolerate gaps between bursts. */
|
||||
timeout_start = ulp_lp_core_get_cpu_cycles();
|
||||
} else {
|
||||
/* FIFO empty. Honour the caller's timeout. */
|
||||
ret = lp_core_uart_check_timeout(intr_mask, timeout, timeout_start);
|
||||
if (ret == ESP_ERR_TIMEOUT) {
|
||||
uart_hal_clr_intsts_mask(&hal, intr_mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_rcvd > size) {
|
||||
bytes_rcvd = size;
|
||||
}
|
||||
return (int)bytes_rcvd;
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_cpu.h"
|
||||
#include "ulp_lp_core_print.h"
|
||||
|
||||
struct source_location {
|
||||
const char *file_name;
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
};
|
||||
|
||||
struct type_descriptor {
|
||||
uint16_t type_kind;
|
||||
uint16_t type_info;
|
||||
char type_name[];
|
||||
};
|
||||
|
||||
struct type_mismatch_data {
|
||||
struct source_location loc;
|
||||
struct type_descriptor *type;
|
||||
unsigned long alignment;
|
||||
unsigned char type_check_kind;
|
||||
};
|
||||
|
||||
struct type_mismatch_data_v1 {
|
||||
struct source_location loc;
|
||||
struct type_descriptor *type;
|
||||
unsigned char log_alignment;
|
||||
unsigned char type_check_kind;
|
||||
};
|
||||
|
||||
struct overflow_data {
|
||||
struct source_location loc;
|
||||
struct type_descriptor *type;
|
||||
};
|
||||
|
||||
struct shift_out_of_bounds_data {
|
||||
struct source_location loc;
|
||||
struct type_descriptor *lhs_type;
|
||||
struct type_descriptor *rhs_type;
|
||||
};
|
||||
|
||||
struct out_of_bounds_data {
|
||||
struct source_location loc;
|
||||
struct type_descriptor *array_type;
|
||||
struct type_descriptor *index_type;
|
||||
};
|
||||
|
||||
struct unreachable_data {
|
||||
struct source_location loc;
|
||||
};
|
||||
|
||||
struct vla_bound_data {
|
||||
struct source_location loc;
|
||||
struct type_descriptor *type;
|
||||
};
|
||||
|
||||
struct invalid_value_data {
|
||||
struct source_location loc;
|
||||
struct type_descriptor *type;
|
||||
};
|
||||
|
||||
struct nonnull_arg_data {
|
||||
struct source_location loc;
|
||||
};
|
||||
|
||||
struct nonnull_return_data {
|
||||
struct source_location loc;
|
||||
struct source_location attr_loc;
|
||||
};
|
||||
|
||||
struct pointer_overflow_data {
|
||||
struct source_location loc;
|
||||
};
|
||||
|
||||
struct invalid_builtin_data {
|
||||
struct source_location loc;
|
||||
unsigned char kind;
|
||||
};
|
||||
|
||||
static void __ubsan_maybe_debugbreak(void)
|
||||
{
|
||||
}
|
||||
|
||||
__attribute__((noreturn)) static void __ubsan_default_handler(struct source_location *loc, const char *func)
|
||||
{
|
||||
#if CONFIG_ULP_PANIC_OUTPUT_ENABLE
|
||||
lp_core_printf("LP_CORE: Undefined behavior of type '%s' @\r\n"
|
||||
"%s:%d\r\n", func, loc->file_name, loc->line);
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
|
||||
void __ubsan_handle_type_mismatch(void *data_,
|
||||
void *ptr_)
|
||||
{
|
||||
struct type_mismatch_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_type_mismatch_v1(void *data_,
|
||||
void *ptr)
|
||||
{
|
||||
struct type_mismatch_data_v1 *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_add_overflow(void *data_,
|
||||
void *lhs_,
|
||||
void *rhs_)
|
||||
{
|
||||
struct overflow_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_sub_overflow(void *data_,
|
||||
void *lhs_,
|
||||
void *rhs_)
|
||||
{
|
||||
struct overflow_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_mul_overflow(void *data_,
|
||||
void *lhs_,
|
||||
void *rhs_)
|
||||
{
|
||||
struct overflow_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_negate_overflow(void *data_,
|
||||
void *old_val_)
|
||||
{
|
||||
struct overflow_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_divrem_overflow(void *data_,
|
||||
void *lhs_,
|
||||
void *rhs_)
|
||||
{
|
||||
struct overflow_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_shift_out_of_bounds(void *data_,
|
||||
void *lhs_,
|
||||
void *rhs_)
|
||||
{
|
||||
struct shift_out_of_bounds_data *data = data_;
|
||||
unsigned int rhs = (unsigned int)rhs_;
|
||||
if (rhs == 32) {
|
||||
return;
|
||||
}
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_out_of_bounds(void *data_,
|
||||
void *idx_)
|
||||
{
|
||||
struct out_of_bounds_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_missing_return(void *data_)
|
||||
{
|
||||
struct unreachable_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_vla_bound_not_positive(void *data_,
|
||||
void *bound_)
|
||||
{
|
||||
struct vla_bound_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_load_invalid_value(void *data_,
|
||||
void *val_)
|
||||
{
|
||||
struct invalid_value_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_nonnull_arg(void *data_)
|
||||
{
|
||||
struct nonnull_arg_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_nonnull_return(void *data_)
|
||||
{
|
||||
struct nonnull_return_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_builtin_unreachable(void *data_)
|
||||
{
|
||||
struct unreachable_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_pointer_overflow(void *data_,
|
||||
void *base_,
|
||||
void *result_)
|
||||
{
|
||||
struct pointer_overflow_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
|
||||
void __ubsan_handle_invalid_builtin(void *data_)
|
||||
{
|
||||
struct invalid_builtin_data *data = data_;
|
||||
__ubsan_maybe_debugbreak();
|
||||
__ubsan_default_handler(&data->loc, __func__);
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "soc/soc_caps.h"
|
||||
#include "riscv/csr.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/pmu_reg.h"
|
||||
#include "hal/misc.h"
|
||||
#include "hal/lp_core_ll.h"
|
||||
#include "hal/pmu_ll.h"
|
||||
#include "hal/uart_ll.h"
|
||||
#include "hal/rtc_io_ll.h"
|
||||
#if SOC_LP_I2S_SUPPORT_VAD
|
||||
//For VAD
|
||||
#include "hal/lp_i2s_ll.h"
|
||||
#endif
|
||||
|
||||
#if SOC_RTC_TIMER_SUPPORTED
|
||||
#include "hal/rtc_timer_ll.h"
|
||||
#endif
|
||||
|
||||
#include "esp_cpu.h"
|
||||
#include "ulp_lp_core_cpu_freq_shared.h"
|
||||
#include "ulp_lp_core_lp_uart_shared.h"
|
||||
#include "ulp_lp_core_uart.h"
|
||||
#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE
|
||||
#include "hal/lp_aon_hal.h"
|
||||
#include "rom/rtc.h"
|
||||
#endif
|
||||
|
||||
static uint32_t lp_wakeup_cause = 0;
|
||||
|
||||
#if SOC_ULP_LP_UART_SUPPORTED
|
||||
void ulp_lp_core_lp_uart_reset_wakeup_en(void)
|
||||
{
|
||||
lp_core_ll_enable_lp_uart_wakeup(false);
|
||||
lp_core_ll_enable_lp_uart_wakeup(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ulp_lp_core_update_wakeup_cause(void)
|
||||
{
|
||||
lp_wakeup_cause = 0;
|
||||
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_HP_CPU) \
|
||||
&& (pmu_ll_lp_get_interrupt_raw(&PMU) & PMU_HP_SW_TRIGGER_INT_RAW)) {
|
||||
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_HP_CPU;
|
||||
pmu_ll_lp_clear_intsts_mask(&PMU, PMU_HP_SW_TRIGGER_INT_CLR);
|
||||
}
|
||||
|
||||
#if SOC_ULP_LP_UART_SUPPORTED
|
||||
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_UART) \
|
||||
&& (uart_ll_get_intraw_mask(&LP_UART) & LP_UART_WAKEUP_INT_RAW)) {
|
||||
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_UART;
|
||||
uart_ll_clr_intsts_mask(&LP_UART, LP_UART_WAKEUP_INT_CLR);
|
||||
#if SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED
|
||||
// In these chips, the LP UART wakeup source is kept triggered, so we need to
|
||||
// reset the wakeup register and flush the UART buffer manually after waking up.
|
||||
lp_core_uart_tx_flush(LP_UART_NUM_0);
|
||||
lp_core_uart_clear_buf();
|
||||
ulp_lp_core_lp_uart_reset_wakeup_en();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_IO) \
|
||||
&& rtcio_ll_get_interrupt_status()) {
|
||||
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_IO;
|
||||
rtcio_ll_clear_interrupt_status();
|
||||
}
|
||||
|
||||
#if SOC_LP_VAD_SUPPORTED
|
||||
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_VAD)) {
|
||||
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_VAD;
|
||||
lp_i2s_ll_rx_clear_interrupt_status(&LP_I2S, LP_I2S_LL_EVENT_VAD_DONE_INT);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SOC_ETM_SUPPORTED
|
||||
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_ETM) \
|
||||
&& lp_core_ll_get_etm_wakeup_flag()) {
|
||||
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_ETM;
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
lp_core_ll_clear_etm_wakeup_status();
|
||||
#else
|
||||
lp_core_ll_clear_etm_wakeup_flag();
|
||||
#endif
|
||||
}
|
||||
#endif /* SOC_ETM_SUPPORTED */
|
||||
|
||||
#if SOC_RTC_TIMER_SUPPORTED
|
||||
if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER) \
|
||||
&& (rtc_timer_ll_get_intr_raw(&LP_TIMER, 1) & LP_TIMER_MAIN_TIMER_LP_INT_RAW)) {
|
||||
lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER;
|
||||
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 1);
|
||||
}
|
||||
#endif /* SOC_RTC_TIMER_SUPPORTED */
|
||||
|
||||
}
|
||||
|
||||
uint32_t ulp_lp_core_get_wakeup_cause()
|
||||
{
|
||||
return lp_wakeup_cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wakeup main CPU from sleep or deep sleep.
|
||||
*
|
||||
* This raises a software interrupt signal, if the
|
||||
* main CPU has configured the ULP as a wakeup source
|
||||
* calling this function will make the main CPU to
|
||||
* exit from sleep or deep sleep.
|
||||
*/
|
||||
void ulp_lp_core_wakeup_main_processor(void)
|
||||
{
|
||||
REG_SET_FIELD(PMU_HP_LP_CPU_COMM_REG, PMU_LP_TRIGGER_HP, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes the co-processor busy wait for a certain number of microseconds
|
||||
*
|
||||
* @param us Number of microseconds to busy-wait for
|
||||
*/
|
||||
void ulp_lp_core_delay_us(uint32_t us)
|
||||
{
|
||||
if (us == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t start = RV_READ_CSR(mcycle) - ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES;
|
||||
uint32_t req_delay = us * LP_CORE_CYCLES_PER_US_NUM / LP_CORE_CYCLES_PER_US_DENOM;
|
||||
uint32_t end = start + req_delay;
|
||||
|
||||
while (RV_READ_CSR(mcycle) < end) {
|
||||
/* busy wait */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes the co-processor busy wait for a certain number of cycles
|
||||
*
|
||||
* @param cycles Number of cycles to busy-wait for
|
||||
*/
|
||||
void ulp_lp_core_delay_cycles(uint32_t cycles)
|
||||
{
|
||||
if (cycles <= ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t start = RV_READ_CSR(mcycle) - ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES;
|
||||
uint32_t end = start + cycles;
|
||||
|
||||
while (RV_READ_CSR(mcycle) < end) {
|
||||
/* busy wait */
|
||||
}
|
||||
}
|
||||
|
||||
void ulp_lp_core_sleep_start_lp_core(void)
|
||||
{
|
||||
#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE
|
||||
/* LP store register to save wakeup cause for HP core to query.
|
||||
* Using a hardware register avoids symbol linking issues between
|
||||
* the independently compiled HP and LP core binaries.
|
||||
* Save PMU wakeup cause to LP store register for HP core to query */
|
||||
lp_aon_hal_store_wakeup_cause(pmu_ll_hp_get_wakeup_cause(&PMU));
|
||||
#endif
|
||||
|
||||
lp_core_ll_request_sleep();
|
||||
}
|
||||
|
||||
void ulp_lp_core_halt(void)
|
||||
{
|
||||
ulp_lp_core_sleep_start_lp_core();
|
||||
|
||||
while (1);
|
||||
}
|
||||
|
||||
void ulp_lp_core_stop_lp_core(void)
|
||||
{
|
||||
/* Disable wake-up source and put lp core to sleep */
|
||||
lp_core_ll_set_wakeup_source(0);
|
||||
ulp_lp_core_sleep_start_lp_core();
|
||||
}
|
||||
|
||||
void __attribute__((noreturn)) abort(void)
|
||||
{
|
||||
// By calling abort users expect some panic message to be printed,
|
||||
// so cause an exception like it is done in HP core's version of abort().
|
||||
// If CONFIG_ULP_PANIC_OUTPUT_ENABLE is YES then panic handler will print smth
|
||||
// If debugger is attached it will stop here and user can inspect the backtrace.
|
||||
esp_cpu_dbgr_break();
|
||||
while (1); // to make compiler happy about noreturn attr
|
||||
}
|
||||
|
||||
void __attribute__((noreturn)) ulp_lp_core_abort(void)
|
||||
{
|
||||
/* Stop the LP Core */
|
||||
ulp_lp_core_stop_lp_core();
|
||||
|
||||
while (1);
|
||||
}
|
||||
|
||||
void ulp_lp_core_sw_intr_to_hp_trigger(void)
|
||||
{
|
||||
pmu_ll_lp_trigger_sw_intr(&PMU);
|
||||
}
|
||||
|
||||
void ulp_lp_core_sw_intr_from_hp_enable(bool enable)
|
||||
{
|
||||
pmu_ll_lp_enable_sw_intr(&PMU, enable);
|
||||
}
|
||||
|
||||
void ulp_lp_core_sw_intr_from_hp_clear(void)
|
||||
{
|
||||
pmu_ll_lp_clear_sw_intr_status(&PMU);
|
||||
}
|
||||
|
||||
#if SOC_RTC_TIMER_SUPPORTED
|
||||
void ulp_lp_core_lp_timer_intr_enable(bool enable)
|
||||
{
|
||||
rtc_timer_ll_alarm_intr_enable(&LP_TIMER, 1, enable);
|
||||
}
|
||||
|
||||
void ulp_lp_core_lp_timer_intr_clear(void)
|
||||
{
|
||||
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ulp_lp_core_wait_for_intr(void)
|
||||
{
|
||||
asm volatile("wfi");
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.section .init.vector,"ax"
|
||||
|
||||
.global _vector_table
|
||||
.type _vector_table, @function
|
||||
_vector_table:
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
.rept 30
|
||||
j _panic_handler
|
||||
.endr
|
||||
j _interrupt_handler // All interrupts are routed to mtvec + 4*30, i.e. the 31st entry
|
||||
j _panic_handler
|
||||
|
||||
.option pop
|
||||
.size _vector_table, .-_vector_table
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.section .init.vector,"ax"
|
||||
|
||||
.global _vector_table
|
||||
.type _vector_table, @function
|
||||
_vector_table:
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
.rept 30
|
||||
j _panic_handler
|
||||
.endr
|
||||
j _interrupt_handler // All interrupts are routed to mtvec + 4*30, i.e. the 31st entry
|
||||
j _panic_handler
|
||||
|
||||
.option pop
|
||||
.size _vector_table, .-_vector_table
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.section .init.vector,"ax"
|
||||
|
||||
.global _vector_table
|
||||
.type _vector_table, @function
|
||||
_vector_table:
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_sw_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_lp_uart_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_lp_spi_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_trng_intr_handler
|
||||
j ulp_lp_core_lp_i2c_intr_handler
|
||||
j ulp_lp_core_lp_io_intr_handler
|
||||
j ulp_lp_core_lp_adc_intr_handler
|
||||
j ulp_lp_core_lp_touch_intr_handler
|
||||
j ulp_lp_core_tsens_intr_handler
|
||||
j ulp_lp_core_efuse_intr_handler
|
||||
j ulp_lp_core_lp_sysreg_intr_handler
|
||||
j ulp_lp_core_lp_ana_peri_intr_handler
|
||||
j ulp_lp_core_lp_pmu_intr_handler
|
||||
j ulp_lp_core_mailbox_intr_handler
|
||||
j ulp_lp_core_lp_timer_intr_handler
|
||||
j ulp_lp_core_lp_wdt_intr_handler
|
||||
j ulp_lp_core_lp_rtc_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
|
||||
.option pop
|
||||
.size _vector_table, .-_vector_table
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.section .init.vector,"ax"
|
||||
|
||||
.global _vector_table
|
||||
.type _vector_table, @function
|
||||
_vector_table:
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_sw_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_lp_uart_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_lp_spi_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
j ulp_lp_core_trng_intr_handler
|
||||
j ulp_lp_core_lp_i2c_intr_handler
|
||||
j ulp_lp_core_lp_io_intr_handler
|
||||
j ulp_lp_core_lp_adc_intr_handler
|
||||
j ulp_lp_core_lp_touch_intr_handler
|
||||
j ulp_lp_core_tsens_intr_handler
|
||||
j ulp_lp_core_efuse_intr_handler
|
||||
j ulp_lp_core_lp_sysreg_intr_handler
|
||||
j ulp_lp_core_lp_ana_peri_intr_handler
|
||||
j ulp_lp_core_lp_pmu_intr_handler
|
||||
j ulp_lp_core_mailbox_intr_handler
|
||||
j ulp_lp_core_lp_timer_intr_handler
|
||||
j ulp_lp_core_lp_wdt_intr_handler
|
||||
j ulp_lp_core_lp_pdma_intr_handler
|
||||
j _panic_handler
|
||||
j _panic_handler
|
||||
|
||||
.option pop
|
||||
.size _vector_table, .-_vector_table
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file Implementation of the mailbox port layer, using the hardware controller
|
||||
*/
|
||||
|
||||
#include "ulp_lp_core_mailbox_impl_shared.h"
|
||||
#include "hal/lp_mailbox_ll.h"
|
||||
#include "soc/lp_mailbox_struct.h"
|
||||
#include "ulp_lp_core_interrupts.h"
|
||||
|
||||
/* Implementation agnostic interrupt handler */
|
||||
static void (*s_intr_handler)(void);
|
||||
|
||||
void lp_core_mailbox_impl_init(void)
|
||||
{
|
||||
lp_mailbox_ll_enable_clock(&LP_MAILBOX);
|
||||
lp_mailbox_ll_reset_register(&LP_MAILBOX);
|
||||
}
|
||||
|
||||
void LP_CORE_ISR_ATTR ulp_lp_core_mailbox_intr_handler(void)
|
||||
{
|
||||
if (s_intr_handler) {
|
||||
s_intr_handler();
|
||||
}
|
||||
}
|
||||
|
||||
lp_core_mailbox_ctx_t lp_core_mailbox_impl_get_context(void)
|
||||
{
|
||||
return (lp_core_mailbox_ctx_t) &LP_MAILBOX;
|
||||
}
|
||||
|
||||
uint32_t lp_core_mailbox_impl_intr_raw(lp_core_mailbox_ctx_t ctx)
|
||||
{
|
||||
return lp_mailbox_ll_get_lp_intr_raw((lp_mb_dev_t*) ctx);
|
||||
}
|
||||
|
||||
uint32_t lp_core_mailbox_impl_intr_status(lp_core_mailbox_ctx_t ctx)
|
||||
{
|
||||
return lp_mailbox_ll_lp_intr_status((lp_mb_dev_t*) ctx);
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_set_intr_handler(lp_core_mailbox_ctx_t ctx, void (*handler)(void))
|
||||
{
|
||||
(void) ctx;
|
||||
s_intr_handler = handler;
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_intr_enable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
|
||||
{
|
||||
lp_mailbox_ll_lp_intr_enable_mask((lp_mb_dev_t*) ctx, msg_mask);
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_intr_disable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
|
||||
{
|
||||
lp_mailbox_ll_lp_intr_disable_mask((lp_mb_dev_t*) ctx, msg_mask);
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_intr_clear(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
|
||||
{
|
||||
lp_mailbox_ll_lp_intr_clear((lp_mb_dev_t*) ctx, msg_mask);
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_set_message(lp_core_mailbox_ctx_t ctx, int index, lp_message_t value)
|
||||
{
|
||||
lp_mailbox_ll_set_message((lp_mb_dev_t*) ctx, index, value);
|
||||
}
|
||||
|
||||
lp_message_t lp_core_mailbox_impl_get_message(lp_core_mailbox_ctx_t ctx, int index)
|
||||
{
|
||||
return lp_mailbox_ll_get_message((lp_mb_dev_t*) ctx, index);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file Implementation of the mailbox port layer, using software
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "ulp_lp_core_critical_section_shared.h"
|
||||
#include "ulp_lp_core_mailbox_impl_shared.h"
|
||||
#include "ulp_lp_core_utils.h"
|
||||
#include "ulp_lp_core_interrupts.h"
|
||||
#include "ulp_lp_core_print.h"
|
||||
|
||||
typedef struct {
|
||||
volatile lp_message_t msg[LP_MAILBOX_MSG_COUNT];
|
||||
/* Message mask to the HP core */
|
||||
volatile uint32_t to_hp_msg_raw;
|
||||
/* Message mask to the LP core */
|
||||
volatile uint32_t to_lp_msg_raw;
|
||||
/* Lock for all the "raw" values, array is guaranteed to be split between LP/HP */
|
||||
ulp_lp_core_spinlock_t lock;
|
||||
} lp_core_mailbox_impl_sw_t;
|
||||
|
||||
lp_core_mailbox_impl_sw_t g_lp_core_mailbox_impl_sw_ctx;
|
||||
|
||||
/* Implementation agnostic interrupt handler */
|
||||
static void (*s_intr_handler)(void);
|
||||
|
||||
void lp_core_mailbox_impl_init(void)
|
||||
{
|
||||
/* This structure must be initialized by the LP core */
|
||||
memset(&g_lp_core_mailbox_impl_sw_ctx, 0, sizeof(g_lp_core_mailbox_impl_sw_ctx));
|
||||
ulp_lp_core_spinlock_init(&g_lp_core_mailbox_impl_sw_ctx.lock);
|
||||
ulp_lp_core_sw_intr_from_hp_enable(false);
|
||||
}
|
||||
|
||||
void LP_CORE_ISR_ATTR ulp_lp_core_sw_intr_handler(void)
|
||||
{
|
||||
if (s_intr_handler) {
|
||||
s_intr_handler();
|
||||
}
|
||||
}
|
||||
|
||||
lp_core_mailbox_ctx_t lp_core_mailbox_impl_get_context(void)
|
||||
{
|
||||
return (lp_core_mailbox_ctx_t) &g_lp_core_mailbox_impl_sw_ctx;
|
||||
}
|
||||
|
||||
uint32_t lp_core_mailbox_impl_intr_raw(lp_core_mailbox_ctx_t ctx)
|
||||
{
|
||||
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
|
||||
return sw_ctx->to_lp_msg_raw;
|
||||
}
|
||||
|
||||
uint32_t lp_core_mailbox_impl_intr_status(lp_core_mailbox_ctx_t ctx)
|
||||
{
|
||||
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
|
||||
return sw_ctx->to_lp_msg_raw;
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_set_intr_handler(lp_core_mailbox_ctx_t ctx, void (*handler)(void))
|
||||
{
|
||||
(void) ctx;
|
||||
s_intr_handler = handler;
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_intr_enable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
|
||||
{
|
||||
/* This function is called when asynchronous received is used.
|
||||
* In the case of the software implementation, we don't really care about the mask, it should
|
||||
* be able to receive any RX message. */
|
||||
ulp_lp_core_sw_intr_from_hp_enable(true);
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_intr_disable(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
|
||||
{
|
||||
/* Similarly to the `enable` function, this is used for receiving messages.
|
||||
* Since we cannot only disable software interrupts, set the flags we defined above. */
|
||||
(void) ctx;
|
||||
(void) msg_mask;
|
||||
ulp_lp_core_sw_intr_from_hp_enable(false);
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_intr_clear(lp_core_mailbox_ctx_t ctx, uint32_t msg_mask)
|
||||
{
|
||||
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
|
||||
/* Clear the flag */
|
||||
ulp_lp_core_enter_critical(&sw_ctx->lock);
|
||||
sw_ctx->to_lp_msg_raw &= ~msg_mask;
|
||||
if (sw_ctx->to_lp_msg_raw == 0) {
|
||||
/* All interrupts were handled */
|
||||
ulp_lp_core_sw_intr_from_hp_clear();
|
||||
}
|
||||
ulp_lp_core_exit_critical(&sw_ctx->lock);
|
||||
}
|
||||
|
||||
void lp_core_mailbox_impl_set_message(lp_core_mailbox_ctx_t ctx, int index, lp_message_t value)
|
||||
{
|
||||
lp_core_mailbox_impl_sw_t* sw_ctx = (lp_core_mailbox_impl_sw_t*) ctx;
|
||||
sw_ctx->msg[index] = value;
|
||||
ulp_lp_core_enter_critical(&sw_ctx->lock);
|
||||
sw_ctx->to_hp_msg_raw |= 1 << index;
|
||||
ulp_lp_core_exit_critical(&sw_ctx->lock);
|
||||
/* Trigger a software interrupt */
|
||||
ulp_lp_core_sw_intr_to_hp_trigger();
|
||||
}
|
||||
|
||||
lp_message_t lp_core_mailbox_impl_get_message(lp_core_mailbox_ctx_t ctx, int index)
|
||||
{
|
||||
return ((lp_core_mailbox_impl_sw_t*) ctx)->msg[index];
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.section .text.vectors
|
||||
.global reset_vector
|
||||
|
||||
/* The reset vector, jumps to startup code */
|
||||
reset_vector:
|
||||
|
||||
/* _vector_table: Only 256-byte aligned addresses are allowed */
|
||||
la t0, _vector_table
|
||||
csrw mtvec, t0
|
||||
|
||||
j __start
|
||||
|
||||
__start:
|
||||
|
||||
/* setup the stack pointer */
|
||||
la sp, __stack_top
|
||||
call lp_core_startup
|
||||
loop:
|
||||
j loop
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "riscv/rvruntime-frames.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
.equ SAVE_REGS, 32
|
||||
.equ CONTEXT_SIZE, (SAVE_REGS * 4)
|
||||
/* Macro which first allocates space on the stack to save general
|
||||
* purpose registers, and then save them. GP register is excluded.
|
||||
* The default size allocated on the stack is CONTEXT_SIZE, but it
|
||||
* can be overridden. */
|
||||
.macro save_general_regs cxt_size=CONTEXT_SIZE
|
||||
addi sp, sp, -\cxt_size
|
||||
sw ra, RV_STK_RA(sp)
|
||||
sw tp, RV_STK_TP(sp)
|
||||
sw t0, RV_STK_T0(sp)
|
||||
sw t1, RV_STK_T1(sp)
|
||||
sw t2, RV_STK_T2(sp)
|
||||
sw s0, RV_STK_S0(sp)
|
||||
sw s1, RV_STK_S1(sp)
|
||||
sw a0, RV_STK_A0(sp)
|
||||
sw a1, RV_STK_A1(sp)
|
||||
sw a2, RV_STK_A2(sp)
|
||||
sw a3, RV_STK_A3(sp)
|
||||
sw a4, RV_STK_A4(sp)
|
||||
sw a5, RV_STK_A5(sp)
|
||||
sw a6, RV_STK_A6(sp)
|
||||
sw a7, RV_STK_A7(sp)
|
||||
sw s2, RV_STK_S2(sp)
|
||||
sw s3, RV_STK_S3(sp)
|
||||
sw s4, RV_STK_S4(sp)
|
||||
sw s5, RV_STK_S5(sp)
|
||||
sw s6, RV_STK_S6(sp)
|
||||
sw s7, RV_STK_S7(sp)
|
||||
sw s8, RV_STK_S8(sp)
|
||||
sw s9, RV_STK_S9(sp)
|
||||
sw s10, RV_STK_S10(sp)
|
||||
sw s11, RV_STK_S11(sp)
|
||||
sw t3, RV_STK_T3(sp)
|
||||
sw t4, RV_STK_T4(sp)
|
||||
sw t5, RV_STK_T5(sp)
|
||||
sw t6, RV_STK_T6(sp)
|
||||
.endm
|
||||
|
||||
.macro save_mepc
|
||||
csrr t0, mepc
|
||||
sw t0, RV_STK_MEPC(sp)
|
||||
.endm
|
||||
|
||||
/* Restore the general purpose registers (excluding gp) from the context on
|
||||
* the stack. The context is then deallocated. The default size is CONTEXT_SIZE
|
||||
* but it can be overridden. */
|
||||
.macro restore_general_regs cxt_size=CONTEXT_SIZE
|
||||
lw ra, RV_STK_RA(sp)
|
||||
lw tp, RV_STK_TP(sp)
|
||||
lw t0, RV_STK_T0(sp)
|
||||
lw t1, RV_STK_T1(sp)
|
||||
lw t2, RV_STK_T2(sp)
|
||||
lw s0, RV_STK_S0(sp)
|
||||
lw s1, RV_STK_S1(sp)
|
||||
lw a0, RV_STK_A0(sp)
|
||||
lw a1, RV_STK_A1(sp)
|
||||
lw a2, RV_STK_A2(sp)
|
||||
lw a3, RV_STK_A3(sp)
|
||||
lw a4, RV_STK_A4(sp)
|
||||
lw a5, RV_STK_A5(sp)
|
||||
lw a6, RV_STK_A6(sp)
|
||||
lw a7, RV_STK_A7(sp)
|
||||
lw s2, RV_STK_S2(sp)
|
||||
lw s3, RV_STK_S3(sp)
|
||||
lw s4, RV_STK_S4(sp)
|
||||
lw s5, RV_STK_S5(sp)
|
||||
lw s6, RV_STK_S6(sp)
|
||||
lw s7, RV_STK_S7(sp)
|
||||
lw s8, RV_STK_S8(sp)
|
||||
lw s9, RV_STK_S9(sp)
|
||||
lw s10, RV_STK_S10(sp)
|
||||
lw s11, RV_STK_S11(sp)
|
||||
lw t3, RV_STK_T3(sp)
|
||||
lw t4, RV_STK_T4(sp)
|
||||
lw t5, RV_STK_T5(sp)
|
||||
lw t6, RV_STK_T6(sp)
|
||||
addi sp,sp, \cxt_size
|
||||
.endm
|
||||
|
||||
.macro restore_mepc
|
||||
lw t0, RV_STK_MEPC(sp)
|
||||
csrw mepc, t0
|
||||
.endm
|
||||
|
||||
|
||||
/* _panic_handler: handle all exception */
|
||||
.section .text.handlers,"ax"
|
||||
.global _panic_handler
|
||||
.type _panic_handler, @function
|
||||
_panic_handler:
|
||||
save_general_regs RV_STK_FRMSZ
|
||||
save_mepc
|
||||
|
||||
addi t0, sp, RV_STK_FRMSZ /* restore sp with the value when trap happened */
|
||||
|
||||
/* Save CSRs */
|
||||
sw t0, RV_STK_SP(sp)
|
||||
csrr t0, mstatus
|
||||
sw t0, RV_STK_MSTATUS(sp)
|
||||
csrr t0, mcause
|
||||
sw t0, RV_STK_MCAUSE(sp)
|
||||
csrr t0, mtvec
|
||||
sw t0, RV_STK_MTVEC(sp)
|
||||
csrr t0, mhartid
|
||||
sw t0, RV_STK_MHARTID(sp)
|
||||
csrr t0, mtval
|
||||
sw t0, RV_STK_MTVAL(sp)
|
||||
|
||||
csrr a1, mcause /* exception cause */
|
||||
|
||||
mv a0, sp /* RvExcFrame *regs */
|
||||
call ulp_lp_core_panic_handler
|
||||
_end:
|
||||
j _end /* loop forever */
|
||||
|
||||
|
||||
#if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
|
||||
|
||||
/* interrupt_handler: handle all interrupt */
|
||||
.section .text.handlers,"ax"
|
||||
.global _interrupt_handler
|
||||
.type _interrupt_handler, @function
|
||||
_interrupt_handler:
|
||||
/* save registers & mepc to stack */
|
||||
save_general_regs
|
||||
save_mepc
|
||||
|
||||
call ulp_lp_core_intr_handler
|
||||
|
||||
/* restore registers & mepc from stack */
|
||||
restore_mepc
|
||||
restore_general_regs
|
||||
/* exit, this will also re-enable the interrupts */
|
||||
mret
|
||||
|
||||
#endif // SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR
|
||||
@@ -0,0 +1,21 @@
|
||||
# What a ULP FSM program is built against.
|
||||
#
|
||||
# An FSM program is assembled from the application's own sources, so this
|
||||
# component carries no sources of its own: it publishes the headers the program
|
||||
# is written against and the memory layout it is linked with.
|
||||
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
get_filename_component(ulp_dir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
|
||||
|
||||
idf_component_register(
|
||||
INCLUDE_DIRS "${ulp_dir}/ulp_common/include"
|
||||
"${ulp_dir}/ulp_fsm/include"
|
||||
"${ulp_dir}/ulp_fsm/include/${target}"
|
||||
REQUIRES soc)
|
||||
|
||||
# -D__ASSEMBLER__ keeps the SoC headers to their macro-only form.
|
||||
set(ulp_ld_flags "-D__ASSEMBLER__ -I\"${CMAKE_CURRENT_SOURCE_DIR}/ld\"")
|
||||
|
||||
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/ulp_fsm.ld.in" MEMORY
|
||||
FLAGS "${ulp_ld_flags}")
|
||||
target_link_options(${COMPONENT_LIB} INTERFACE "SHELL:-u entry")
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define ULP_BIN_MAGIC 0x00706c75
|
||||
#define HEADER_SIZE 12
|
||||
MEMORY
|
||||
{
|
||||
ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.text : AT(HEADER_SIZE)
|
||||
{
|
||||
*(.text)
|
||||
} >ram
|
||||
.data :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
*(.data)
|
||||
} >ram
|
||||
.bss :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
*(.bss)
|
||||
} >ram
|
||||
|
||||
.header : AT(0)
|
||||
{
|
||||
LONG(ULP_BIN_MAGIC)
|
||||
SHORT(LOADADDR(.text))
|
||||
SHORT(SIZEOF(.text))
|
||||
SHORT(SIZEOF(.data))
|
||||
SHORT(SIZEOF(.bss))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# The code compiled for the ULP RISC-V coprocessor.
|
||||
#
|
||||
# Sources shared with the driver stay in components/ulp and are named by path
|
||||
# from here.
|
||||
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
get_filename_component(ulp_dir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
|
||||
|
||||
# The ULP subproject in build system v1 adds these sources straight to the
|
||||
# executable, so a strong handler is not dropped in favour of a weak default
|
||||
# from another runtime object. WHOLE_ARCHIVE keeps that behaviour here.
|
||||
idf_component_register(
|
||||
SRCS "ulp_riscv_vectors.S"
|
||||
"start.S"
|
||||
"ulp_riscv_adc.c"
|
||||
"ulp_riscv_lock.c"
|
||||
"ulp_riscv_uart.c"
|
||||
"ulp_riscv_print.c"
|
||||
"ulp_riscv_i2c.c"
|
||||
"ulp_riscv_utils.c"
|
||||
"ulp_riscv_touch.c"
|
||||
"ulp_riscv_gpio.c"
|
||||
"ulp_riscv_interrupt.c"
|
||||
INCLUDE_DIRS "${ulp_dir}/ulp_common/include"
|
||||
"${ulp_dir}/ulp_riscv/include"
|
||||
"${ulp_dir}/ulp_riscv/shared/include"
|
||||
"include"
|
||||
REQUIRES esp_common
|
||||
esp_hal_ana_conv
|
||||
esp_hal_gpio
|
||||
esp_hal_i2c
|
||||
esp_hal_touch_sens
|
||||
esp_hw_support
|
||||
riscv
|
||||
soc
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
# -D__ASSEMBLER__ keeps the SoC headers to their macro-only form.
|
||||
set(ulp_ld_flags "-D__ASSEMBLER__ -I\"${CMAKE_CURRENT_SOURCE_DIR}/ld\"")
|
||||
|
||||
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/ulp_riscv.ld.in" MEMORY
|
||||
FLAGS "${ulp_ld_flags}")
|
||||
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/${target}.peripherals.ld")
|
||||
|
||||
target_link_options(${COMPONENT_LIB} INTERFACE
|
||||
-nostartfiles
|
||||
-Wl,--gc-sections
|
||||
-Wl,--no-warn-rwx-segments
|
||||
LINKER:-u,main)
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB} PUBLIC
|
||||
IS_ULP_COCPU
|
||||
ULP_RISCV_REGISTER_OPS)
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ulp_riscv_register_ops.h"
|
||||
#include "hal/adc_ll.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Start an ADC conversion and get the converted value.
|
||||
*
|
||||
* @note Will block until the conversion is completed
|
||||
*
|
||||
* @note ADC should be initialized for ULP by main CPU by calling ulp_riscv_adc_init()
|
||||
* before calling this.
|
||||
* @note When using ADC_UNIT_2, the caller must ensure that no other module (e.g., Wi-Fi or BT)
|
||||
* is accessing the ADC, as conflicts may lead to undefined behavior.
|
||||
*
|
||||
* @param adc_n ADC unit.
|
||||
* @param channel ADC channel number.
|
||||
*
|
||||
* @return Converted value, -1 if conversion failed
|
||||
*/
|
||||
int32_t ulp_riscv_adc_read_channel(adc_unit_t adc_n, int channel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/rtc_io_reg.h"
|
||||
#include "soc/sens_reg.h"
|
||||
#include "ulp_riscv_register_ops.h"
|
||||
#include "hal/gpio_types.h"
|
||||
#include "hal/rtc_io_ll.h"
|
||||
#include "ulp_riscv_interrupt.h"
|
||||
|
||||
typedef enum {
|
||||
ULP_RISCV_GPIO_INTR_DISABLE = 0, /*!< Disable RTC GPIO interrupt */
|
||||
ULP_RISCV_GPIO_INTR_POSEDGE = 1, /*!< RTC GPIO interrupt type : rising edge */
|
||||
ULP_RISCV_GPIO_INTR_NEGEDGE = 2, /*!< RTC GPIO interrupt type : falling edge */
|
||||
ULP_RISCV_GPIO_INTR_ANYEDGE = 3, /*!< RTC GPIO interrupt type : both rising and falling edge */
|
||||
ULP_RISCV_GPIO_INTR_LOW_LEVEL = 4, /*!< RTC GPIO interrupt type : input low level trigger */
|
||||
ULP_RISCV_GPIO_INTR_HIGH_LEVEL = 5, /*!< RTC GPIO interrupt type : input high level trigger */
|
||||
ULP_RISCV_GPIO_INTR_MAX
|
||||
} ulp_riscv_gpio_int_type_t;
|
||||
|
||||
typedef enum {
|
||||
RTCIO_MODE_OUTPUT = 0,
|
||||
RTCIO_MODE_OUTPUT_OD = 1,
|
||||
} rtc_io_out_mode_t;
|
||||
|
||||
static inline void ulp_riscv_gpio_init(gpio_num_t gpio_num)
|
||||
{
|
||||
#if SOC_LP_IO_CLOCK_IS_INDEPENDENT
|
||||
rtcio_ll_enable_io_clock(true);
|
||||
#endif
|
||||
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_MUX_SEL);
|
||||
REG_SET_FIELD(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_SEL, 0);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_deinit(gpio_num_t gpio_num)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_MUX_SEL);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_output_enable(gpio_num_t gpio_num)
|
||||
{
|
||||
REG_SET_FIELD(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS, BIT(gpio_num));
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_output_disable(gpio_num_t gpio_num)
|
||||
{
|
||||
REG_SET_FIELD(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC, BIT(gpio_num));
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_input_enable(gpio_num_t gpio_num)
|
||||
{
|
||||
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_IE);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_input_disable(gpio_num_t gpio_num)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_IE);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_output_level(gpio_num_t gpio_num, uint8_t level)
|
||||
{
|
||||
if (level) {
|
||||
REG_SET_FIELD(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS, BIT(gpio_num));
|
||||
} else {
|
||||
REG_SET_FIELD(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TS, BIT(gpio_num));
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t ulp_riscv_gpio_get_level(gpio_num_t gpio_num)
|
||||
{
|
||||
return (uint8_t)((REG_GET_FIELD(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT) & BIT(gpio_num)) ? 1 : 0);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_set_output_mode(gpio_num_t gpio_num, rtc_io_out_mode_t mode)
|
||||
{
|
||||
REG_SET_FIELD(RTC_GPIO_PIN0_REG + gpio_num * 4, RTC_GPIO_PIN0_PAD_DRIVER, mode);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_pullup(gpio_num_t gpio_num)
|
||||
{
|
||||
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RUE);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_pullup_disable(gpio_num_t gpio_num)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RUE);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_pulldown(gpio_num_t gpio_num)
|
||||
{
|
||||
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RDE);
|
||||
}
|
||||
|
||||
static inline void ulp_riscv_gpio_pulldown_disable(gpio_num_t gpio_num)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RDE);
|
||||
}
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
|
||||
/**
|
||||
* @brief Set RTC IO interrupt type and handler
|
||||
*
|
||||
* @param gpio_num GPIO number
|
||||
* @param intr_type Interrupt type (See rtc_io_types.h)
|
||||
* @param handler Interrupt handler
|
||||
* @param arg Interrupt handler argument
|
||||
*
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t ulp_riscv_gpio_isr_register(gpio_num_t gpio_num, ulp_riscv_gpio_int_type_t intr_type, intr_handler_t handler, void *arg);
|
||||
|
||||
/**
|
||||
* @brief Remove RTC IO interrupt handler
|
||||
*
|
||||
* @param gpio_num GPIO number
|
||||
*
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t ulp_riscv_gpio_isr_deregister(gpio_num_t gpio_num);
|
||||
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief Set the I2C slave device address
|
||||
*
|
||||
* @param slave_addr I2C slave address (7 bit)
|
||||
*/
|
||||
void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr);
|
||||
|
||||
/**
|
||||
* @brief Set the I2C slave device sub register address
|
||||
*
|
||||
* @param slave_reg_addr I2C slave register address
|
||||
*/
|
||||
void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr);
|
||||
|
||||
/**
|
||||
* @brief Read from I2C slave device
|
||||
*
|
||||
* @note The I2C slave device address must be configured at least once before invoking this API.
|
||||
*
|
||||
* @param data_rd Buffer to hold data to be read
|
||||
* @param size Size of data to be read in bytes
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Write to I2C slave device
|
||||
*
|
||||
* @note The I2C slave device address must be configured at least once before invoking this API.
|
||||
*
|
||||
* @param data_wr Buffer which holds the data to be written
|
||||
* @param size Size of data to be written in bytes
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_i2c_master_write_to_device(const uint8_t *data_wr, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "riscv/interrupt.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
|
||||
/* ULP RISC-V Interrupt sources */
|
||||
typedef enum {
|
||||
ULP_RISCV_SW_INTR_SOURCE = 0, /**< Interrupt triggered by SW */
|
||||
ULP_RISCV_RTCIO0_INTR_SOURCE, /**< Interrupt triggered by RTCIO 0 */
|
||||
ULP_RISCV_RTCIO1_INTR_SOURCE, /**< Interrupt triggered by RTCIO 1 */
|
||||
ULP_RISCV_RTCIO2_INTR_SOURCE, /**< Interrupt triggered by RTCIO 2 */
|
||||
ULP_RISCV_RTCIO3_INTR_SOURCE, /**< Interrupt triggered by RTCIO 3 */
|
||||
ULP_RISCV_RTCIO4_INTR_SOURCE, /**< Interrupt triggered by RTCIO 4 */
|
||||
ULP_RISCV_RTCIO5_INTR_SOURCE, /**< Interrupt triggered by RTCIO 5 */
|
||||
ULP_RISCV_RTCIO6_INTR_SOURCE, /**< Interrupt triggered by RTCIO 6 */
|
||||
ULP_RISCV_RTCIO7_INTR_SOURCE, /**< Interrupt triggered by RTCIO 7 */
|
||||
ULP_RISCV_RTCIO8_INTR_SOURCE, /**< Interrupt triggered by RTCIO 8 */
|
||||
ULP_RISCV_RTCIO9_INTR_SOURCE, /**< Interrupt triggered by RTCIO 9 */
|
||||
ULP_RISCV_RTCIO10_INTR_SOURCE, /**< Interrupt triggered by RTCIO 10 */
|
||||
ULP_RISCV_RTCIO11_INTR_SOURCE, /**< Interrupt triggered by RTCIO 11 */
|
||||
ULP_RISCV_RTCIO12_INTR_SOURCE, /**< Interrupt triggered by RTCIO 12 */
|
||||
ULP_RISCV_RTCIO13_INTR_SOURCE, /**< Interrupt triggered by RTCIO 13 */
|
||||
ULP_RISCV_RTCIO14_INTR_SOURCE, /**< Interrupt triggered by RTCIO 14 */
|
||||
ULP_RISCV_RTCIO15_INTR_SOURCE, /**< Interrupt triggered by RTCIO 15 */
|
||||
ULP_RISCV_RTCIO16_INTR_SOURCE, /**< Interrupt triggered by RTCIO 16 */
|
||||
ULP_RISCV_RTCIO17_INTR_SOURCE, /**< Interrupt triggered by RTCIO 17 */
|
||||
ULP_RISCV_RTCIO18_INTR_SOURCE, /**< Interrupt triggered by RTCIO 18 */
|
||||
ULP_RISCV_RTCIO19_INTR_SOURCE, /**< Interrupt triggered by RTCIO 19 */
|
||||
ULP_RISCV_RTCIO20_INTR_SOURCE, /**< Interrupt triggered by RTCIO 20 */
|
||||
ULP_RISCV_RTCIO21_INTR_SOURCE, /**< Interrupt triggered by RTCIO 21 */
|
||||
ULP_RISCV_MAX_INTR_SOURCE, /**< Total number of ULP RISC-V interrupt sources */
|
||||
} ulp_riscv_interrupt_source_t;
|
||||
|
||||
/**
|
||||
* @brief Allocate interrupt handler for a ULP RISC-V interrupt source
|
||||
*
|
||||
* @param source ULP RISC-V interrupt source
|
||||
* @param handler Interrupt handler
|
||||
* @param arg Interrupt handler argument
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_intr_alloc(ulp_riscv_interrupt_source_t source, intr_handler_t handler, void *arg);
|
||||
|
||||
/**
|
||||
* @brief Free ULP RISC-V interrupt handler
|
||||
*
|
||||
* @param source ULP RISC-V interrupt source
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_intr_free(ulp_riscv_interrupt_source_t source);
|
||||
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
* SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This header file defines custom instructions for interrupt handling on the
|
||||
* ULP RISC-V. The architecture of the processor and therefore, the interrupt
|
||||
* handling is based on the PicoRV32 CPU. Details about the operations are
|
||||
* available at https://github.com/YosysHQ/picorv32#custom-instructions-for-irq-handling
|
||||
*/
|
||||
|
||||
/* Define encoding for all general purpose RISC-V registers */
|
||||
#define regnum_zero 0
|
||||
#define regnum_ra 1
|
||||
#define regnum_sp 2
|
||||
#define regnum_gp 3
|
||||
#define regnum_tp 4
|
||||
#define regnum_t0 5
|
||||
#define regnum_t1 6
|
||||
#define regnum_t2 7
|
||||
#define regnum_s0 8
|
||||
#define regnum_s1 9
|
||||
#define regnum_a0 10
|
||||
#define regnum_a1 11
|
||||
#define regnum_a2 12
|
||||
#define regnum_a3 13
|
||||
#define regnum_a4 14
|
||||
#define regnum_a5 15
|
||||
#define regnum_a6 16
|
||||
#define regnum_a7 17
|
||||
#define regnum_s2 18
|
||||
#define regnum_s3 19
|
||||
#define regnum_s4 20
|
||||
#define regnum_s5 21
|
||||
#define regnum_s6 22
|
||||
#define regnum_s7 23
|
||||
#define regnum_s8 24
|
||||
#define regnum_s9 25
|
||||
#define regnum_s10 26
|
||||
#define regnum_s11 27
|
||||
#define regnum_t3 28
|
||||
#define regnum_t4 29
|
||||
#define regnum_t5 30
|
||||
#define regnum_t6 31
|
||||
|
||||
/* Define encoding for special interrupt handling registers, viz., q0, q1, q2 and q3 */
|
||||
#define regnum_q0 0
|
||||
#define regnum_q1 1
|
||||
#define regnum_q2 2
|
||||
#define regnum_q3 3
|
||||
|
||||
/* All custom interrupt handling instructions follow the standard R-type instruction format from RISC-V ISA
|
||||
* with the same opcode of custom0 (0001011).
|
||||
*/
|
||||
#define r_type_insn(_f7, _rs2, _rs1, _f3, _rd, _opc) \
|
||||
.word (((_f7) << 25) | ((_rs2) << 20) | ((_rs1) << 15) | ((_f3) << 12) | ((_rd) << 7) | ((_opc) << 0))
|
||||
|
||||
/**
|
||||
* Instruction: getq rd, qs
|
||||
* Description: This instruction copies the value of Qx into a general purpose register rd
|
||||
*/
|
||||
#define getq_insn(_rd, _qs) \
|
||||
r_type_insn(0b0000000, 0, regnum_ ## _qs, 0b100, regnum_ ## _rd, 0b0001011)
|
||||
|
||||
/**
|
||||
* Instruction: setq qd, rs
|
||||
* Description: This instruction copies the value of general purpose register rs to Qx
|
||||
*/
|
||||
#define setq_insn(_qd, _rs) \
|
||||
r_type_insn(0b0000001, 0, regnum_ ## _rs, 0b010, regnum_ ## _qd, 0b0001011)
|
||||
|
||||
/**
|
||||
* Instruction: retirq
|
||||
* Description: This instruction copies the value of Q0 to CPU PC, and re-enables interrupts
|
||||
*/
|
||||
#define retirq_insn() \
|
||||
r_type_insn(0b0000010, 0, 0, 0b000, 0, 0b0001011)
|
||||
|
||||
/**
|
||||
* Instruction: maskirq rd, rs
|
||||
* Description: This instruction copies the value of the register IRQ Mask to the register rd, and copies the value
|
||||
* of register rs to to IRQ mask.
|
||||
*/
|
||||
#define maskirq_insn(_rd, _rs) \
|
||||
r_type_insn(0b0000011, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ulp_riscv_lock_shared.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Locks are based on the Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Acquire the lock, preventing the main CPU from taking until released. Spins until lock is acquired.
|
||||
*
|
||||
* @note The lock is only designed for being used by a single thread on the ULP,
|
||||
* it is not safe to try to acquire it from multiple threads.
|
||||
*
|
||||
* @param lock Pointer to lock struct, shared with main CPU
|
||||
*/
|
||||
void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock);
|
||||
|
||||
/**
|
||||
* @brief Release the lock
|
||||
*
|
||||
* @param lock Pointer to lock struct, shared with main CPU
|
||||
*/
|
||||
void ulp_riscv_lock_release(ulp_riscv_lock_t *lock);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Underlying driver function for printing a char, e.g. over UART */
|
||||
typedef void (*putc_fn_t)(const void *ctx, const char c);
|
||||
|
||||
/**
|
||||
* @brief Installs a print driver that will be used for ulp_riscv_print calls
|
||||
*
|
||||
* @param putc Underlying driver function for printing a char, e.g. over UART
|
||||
* @param putc_ctx Context that will be passed when calling the putc function
|
||||
*/
|
||||
void ulp_riscv_print_install(putc_fn_t putc, void *putc_ctx);
|
||||
|
||||
/**
|
||||
* @brief Prints a null-terminated string
|
||||
*
|
||||
* @param str String to print
|
||||
*/
|
||||
void ulp_riscv_print_str(const char *str);
|
||||
|
||||
/**
|
||||
* @brief Prints a hex number. Does not print 0x, only the digits
|
||||
*
|
||||
* @param Hex number to print
|
||||
*/
|
||||
void ulp_riscv_print_hex(int h);
|
||||
|
||||
/**
|
||||
* @brief Prints a hex number with the specified number of digits. Does not print 0x, only the digits
|
||||
*
|
||||
* @param Hex number to print
|
||||
* @param number_of_digits Number of digits to print.
|
||||
*/
|
||||
void ulp_riscv_print_hex_with_number_of_digits(int h, int number_of_digits);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//Registers Operation {{
|
||||
|
||||
/*
|
||||
* When COCPU accesses the RTC register, it needs to convert the access address.
|
||||
* When COCPU accesses the RTC memory, dont need to convert the access address.
|
||||
*/
|
||||
#define WRITE_RTC_MEM(addr, val) (*((volatile int*)(addr))) = (int) (val)
|
||||
#define READ_RTC_MEM(addr) (*(volatile int*)(addr))
|
||||
|
||||
/*
|
||||
* When COCPU accesses the RTC register, it needs to convert the access address.
|
||||
* When COCPU accesses the RTC memory, dont need to convert the access address.
|
||||
*/
|
||||
#define RISCV_REG_CONV(addr) (((addr&0xffff)<<3 & 0xe000) | (addr & 0x1fff) | 0x8000)
|
||||
#define ETS_UNCACHED_ADDR(addr) (RISCV_REG_CONV(addr))
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
#define BIT(nr) (1UL << (nr))
|
||||
#else
|
||||
#define BIT(nr) (1 << (nr))
|
||||
#endif
|
||||
|
||||
//write value to register
|
||||
#define REG_WRITE(_r, _v) ({ \
|
||||
(*(volatile uint32_t *)RISCV_REG_CONV(_r)) = (_v); \
|
||||
})
|
||||
|
||||
//read value from register
|
||||
#define REG_READ(_r) ({ \
|
||||
(*(volatile uint32_t *)RISCV_REG_CONV(_r)); \
|
||||
})
|
||||
|
||||
//get bit or get bits from register
|
||||
#define REG_GET_BIT(_r, _b) ({ \
|
||||
(*(volatile uint32_t*)RISCV_REG_CONV(_r) & (_b)); \
|
||||
})
|
||||
|
||||
//set bit or set bits to register
|
||||
#define REG_SET_BIT(_r, _b) ({ \
|
||||
(*(volatile uint32_t*)RISCV_REG_CONV(_r) |= (_b)); \
|
||||
})
|
||||
|
||||
//clear bit or clear bits of register
|
||||
#define REG_CLR_BIT(_r, _b) ({ \
|
||||
(*(volatile uint32_t*)RISCV_REG_CONV(_r) &= ~(_b)); \
|
||||
})
|
||||
|
||||
//set bits of register controlled by mask
|
||||
#define REG_SET_BITS(_r, _b, _m) ({ \
|
||||
(*(volatile uint32_t*)RISCV_REG_CONV(_r) = (*(volatile uint32_t*)RISCV_REG_CONV(_r) & ~(_m)) | ((_b) & (_m))); \
|
||||
})
|
||||
|
||||
//get field from register, uses field _S & _V to determine mask
|
||||
#define REG_GET_FIELD(_r, _f) ({ \
|
||||
((REG_READ(_r) >> (_f##_S)) & (_f##_V)); \
|
||||
})
|
||||
|
||||
//set field of a register from variable, uses field _S & _V to determine mask
|
||||
#define REG_SET_FIELD(_r, _f, _v) ({ \
|
||||
(REG_WRITE((_r),((REG_READ(_r) & ~((_f##_V) << (_f##_S)))|(((_v) & (_f##_V))<<(_f##_S))))); \
|
||||
})
|
||||
|
||||
//get field value from a variable, used when _f is not left shifted by _f##_S
|
||||
#define VALUE_GET_FIELD(_r, _f) (((_r) >> (_f##_S)) & (_f))
|
||||
|
||||
//get field value from a variable, used when _f is left shifted by _f##_S
|
||||
#define VALUE_GET_FIELD2(_r, _f) (((_r) & (_f))>> (_f##_S))
|
||||
|
||||
//set field value to a variable, used when _f is not left shifted by _f##_S
|
||||
#define VALUE_SET_FIELD(_r, _f, _v) ((_r)=(((_r) & ~((_f) << (_f##_S)))|((_v)<<(_f##_S))))
|
||||
|
||||
//set field value to a variable, used when _f is left shifted by _f##_S
|
||||
#define VALUE_SET_FIELD2(_r, _f, _v) ((_r)=(((_r) & ~(_f))|((_v)<<(_f##_S))))
|
||||
|
||||
//generate a value from a field value, used when _f is not left shifted by _f##_S
|
||||
#define FIELD_TO_VALUE(_f, _v) (((_v)&(_f))<<_f##_S)
|
||||
|
||||
//generate a value from a field value, used when _f is left shifted by _f##_S
|
||||
#define FIELD_TO_VALUE2(_f, _v) (((_v)<<_f##_S) & (_f))
|
||||
|
||||
//read value from register
|
||||
#define READ_PERI_REG(addr) ({ \
|
||||
(*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))); \
|
||||
})
|
||||
|
||||
//write value to register
|
||||
#define WRITE_PERI_REG(addr, val) ({ \
|
||||
(*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))) = (uint32_t)(val); \
|
||||
})
|
||||
|
||||
//clear bits of register controlled by mask
|
||||
#define CLEAR_PERI_REG_MASK(reg, mask) ({ \
|
||||
WRITE_PERI_REG((reg), (READ_PERI_REG(reg)&(~(mask)))); \
|
||||
})
|
||||
|
||||
//set bits of register controlled by mask
|
||||
#define SET_PERI_REG_MASK(reg, mask) ({ \
|
||||
WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask))); \
|
||||
})
|
||||
|
||||
//get bits of register controlled by mask
|
||||
#define GET_PERI_REG_MASK(reg, mask) ({ \
|
||||
(READ_PERI_REG(reg) & (mask)); \
|
||||
})
|
||||
|
||||
//get bits of register controlled by highest bit and lowest bit
|
||||
// #define GET_PERI_REG_BITS(reg, hipos,lowpos) ({
|
||||
// ASSERT_IF_DPORT_REG((reg), GET_PERI_REG_BITS);
|
||||
// ((READ_PERI_REG(reg)>>(lowpos))&((1UL<<((hipos)-(lowpos)+1))-1));
|
||||
// })
|
||||
#define GET_PERI_REG_BITS(reg, bit_map, shift) ((READ_PERI_REG(reg))&((bit_map)<<(shift)))>>shift
|
||||
|
||||
//set bits of register controlled by mask and shift
|
||||
#define SET_PERI_REG_BITS(reg,bit_map,value,shift) ({ \
|
||||
(WRITE_PERI_REG((reg),(READ_PERI_REG(reg)&(~((bit_map)<<(shift))))|(((value) & bit_map)<<(shift)) )); \
|
||||
})
|
||||
|
||||
//get field of register
|
||||
#define GET_PERI_REG_BITS2(reg, mask,shift) ({ \
|
||||
((READ_PERI_REG(reg)>>(shift))&(mask)); \
|
||||
})
|
||||
//}}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "ulp_riscv_register_ops.h"
|
||||
#include "hal/touch_sens_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Read raw data of touch sensor on the ULP RISC-V core
|
||||
* @note Refer `touch_pad_read_raw_data()` for more details
|
||||
*
|
||||
* @param touch_num Touch pad index
|
||||
* @param raw_data Pointer to accept touch sensor value
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data);
|
||||
|
||||
/**
|
||||
* @brief Read benchmark of touch sensor on the ULP RISC-V core
|
||||
* @note Refer `touch_pad_read_benchmark()` for more details
|
||||
*
|
||||
* @param touch_num Touch pad index
|
||||
* @param benchmark Pointer to accept touch sensor benchmark value
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark);
|
||||
|
||||
/**
|
||||
* @brief Read the filtered (smoothened) touch sensor data on the ULP RISC-V core
|
||||
* @note Refer `touch_pad_filter_read_smooth()` for more details
|
||||
*
|
||||
* @param touch_num Touch pad index
|
||||
* @param smooth_data Pointer to accept smoothened touch sensor value
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data);
|
||||
|
||||
/**
|
||||
* @brief Force reset benchmark to raw data of touch sensor.
|
||||
* @note Refer `touch_pad_reset_benchmark()` for more details
|
||||
*
|
||||
* @param touch_num Touch pad index (TOUCH_PAD_MAX resets baseline of all channels)
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_reset_benchmark(int touch_num);
|
||||
|
||||
/**
|
||||
* @brief Read raw data of touch sensor sleep channel on the ULP RISC-V core
|
||||
* @note Refer `touch_pad_sleep_channel_read_data()` for more details
|
||||
*
|
||||
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
|
||||
* @param raw_data Pointer to accept touch sensor value
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data);
|
||||
|
||||
/**
|
||||
* @brief Read benchmark of touch sensor sleep channel on the ULP RISC-V core
|
||||
* @note Refer `touch_pad_sleep_channel_read_benchmark()` for more details
|
||||
*
|
||||
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
|
||||
* @param benchmark Pointer to accept touch sensor benchmark value
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark);
|
||||
|
||||
/**
|
||||
* @brief Read the filtered (smoothened) touch sensor sleep channel data on the ULP RISC-V core
|
||||
* @note Refer `touch_pad_sleep_channel_read_smooth()` for more details
|
||||
*
|
||||
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
|
||||
* @param smooth_data Pointer to accept smoothened touch sensor value
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data);
|
||||
|
||||
/**
|
||||
* @brief Reset benchmark of touch sensor sleep channel.
|
||||
* @note Refer `touch_pad_sleep_channel_reset_benchmark()` for more details
|
||||
*
|
||||
* @return esp_err_t ESP_OK when successful
|
||||
*/
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_reset_benchmark(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ulp_riscv_gpio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
gpio_num_t tx_pin; // TX pin number
|
||||
} ulp_riscv_uart_cfg_t; // Config for the driver
|
||||
|
||||
typedef struct {
|
||||
uint32_t bit_duration_cycles; // Number of cycles to hold the line for each bit
|
||||
gpio_num_t tx_pin; // TX pin number
|
||||
} ulp_riscv_uart_t; // Context for the driver, initialized by ulp_riscv_uart_init
|
||||
|
||||
/**
|
||||
* @brief Initialize the bit-banged UART driver
|
||||
*
|
||||
* @note Will also initialize the underlying HW, i.e. the RTC GPIO used.
|
||||
*
|
||||
* @param uart Pointer to the struct that will contain the initialized context
|
||||
* @param cfg Pointer to the config struct which will be used to initialize the driver
|
||||
*/
|
||||
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg);
|
||||
|
||||
/**
|
||||
* @brief Outputs a single byte on the tx pin
|
||||
*
|
||||
* @param uart Pointer to the initialized driver context
|
||||
* @param c Byte to output
|
||||
*/
|
||||
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "ulp_riscv_register_ops.h"
|
||||
#include "ulp_riscv_interrupt.h"
|
||||
#include "ulp_riscv_cpu_freq_shared.h"
|
||||
|
||||
/**
|
||||
* @brief Wakeup main CPU from sleep or deep sleep.
|
||||
*
|
||||
* This raises a software interrupt signal, if the
|
||||
* main CPU has configured the ULP as a wakeup source
|
||||
* calling this function will make the main CPU to
|
||||
* exit from sleep or deep sleep.
|
||||
*/
|
||||
void ulp_riscv_wakeup_main_processor(void);
|
||||
|
||||
/**
|
||||
* @brief Rescues the cpu from monitor mode
|
||||
*
|
||||
* This function cancels the low power mode
|
||||
* of the ULP-RISC-V, should be called
|
||||
* every time the co-processor starts.
|
||||
*
|
||||
* @note by convenience this function is
|
||||
* automatically called in startup code.
|
||||
*/
|
||||
void ulp_riscv_rescue_from_monitor(void);
|
||||
|
||||
/**
|
||||
* @brief Finishes the ULP program and powers down the ULP
|
||||
* until next wakeup.
|
||||
*
|
||||
* @note This function does not return. After called it will
|
||||
* fully reset the ULP.
|
||||
*
|
||||
* @note Returning from main() in the ULP program results on
|
||||
* calling this function.
|
||||
*
|
||||
* @note To stop the ULP from waking up, call ulp_riscv_timer_stop()
|
||||
* before halting.
|
||||
*
|
||||
* This function should be called after the ULP program Finishes
|
||||
* its processing, it will trigger the timer for the next wakeup,
|
||||
* put the ULP in monitor mode and triggers a reset.
|
||||
*
|
||||
*/
|
||||
void __attribute__((__noreturn__)) ulp_riscv_halt(void);
|
||||
|
||||
#define ulp_riscv_shutdown ulp_riscv_halt
|
||||
|
||||
/**
|
||||
* @brief Stop the ULP timer
|
||||
*
|
||||
* @note This will stop the ULP from waking up if halted, but will not abort any program
|
||||
* currently executing on the ULP.
|
||||
*/
|
||||
void ulp_riscv_timer_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Resumes the ULP timer
|
||||
*
|
||||
* @note This will resume an already configured timer, but does no other configuration
|
||||
*
|
||||
*/
|
||||
void ulp_riscv_timer_resume(void);
|
||||
|
||||
#define ULP_RISCV_GET_CCOUNT() ({ int __ccount; \
|
||||
asm volatile("rdcycle %0;" : "=r"(__ccount)); \
|
||||
__ccount; })
|
||||
|
||||
#define ULP_RISCV_CYCLES_PER_US ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM
|
||||
#define ULP_RISCV_CYCLES_PER_MS 1000U * ULP_RISCV_CYCLES_PER_US
|
||||
|
||||
/**
|
||||
* @brief Retrieves the current number of CPU cycles.
|
||||
*
|
||||
* @return The current CPU cycle count.
|
||||
*/
|
||||
static inline uint32_t ulp_riscv_get_cpu_cycles(void)
|
||||
{
|
||||
return ULP_RISCV_GET_CCOUNT();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether an mcycle-based timeout has elapsed.
|
||||
*
|
||||
* @note A timeout value of -1 means "wait forever".
|
||||
* Other values are interpreted as unsigned cycle counts.
|
||||
*
|
||||
* @param start_cycle_count Cycle counter value captured at timeout start.
|
||||
* @param cycles_to_wait Timeout in CPU cycles, or -1 to disable timeout.
|
||||
*
|
||||
* @return true if timeout elapsed, false otherwise.
|
||||
*/
|
||||
static inline bool ulp_riscv_is_timeout_elapsed(uint32_t start_cycle_count, int32_t cycles_to_wait)
|
||||
{
|
||||
if (cycles_to_wait == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ulp_riscv_get_cpu_cycles() - start_cycle_count) >= (uint32_t)cycles_to_wait;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes the co-processor busy-wait for a certain number of CPU cycles.
|
||||
*
|
||||
* @note This function is not accurate for delays shorter than 20 cycles because the
|
||||
* function overhead may exceed the requested delay.
|
||||
*
|
||||
* @note The maximum supported delay is 0x7FFFFFFF cycles.
|
||||
* For larger values, the behavior is undefined. Split longer delays into smaller
|
||||
* chunks if needed.
|
||||
*
|
||||
* For reference, this corresponds approximately to:
|
||||
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: 0x7FFFFFFF cycles ≈ 252.645 s
|
||||
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: 0x7FFFFFFF cycles ≈ 122.713 s
|
||||
*
|
||||
* @param cycles Number of cycles to busy-wait.
|
||||
*/
|
||||
static inline void ulp_riscv_delay_cycles(uint32_t cycles)
|
||||
{
|
||||
if (cycles <= 20U) { // estimate of cycles for this function overhead
|
||||
return;
|
||||
}
|
||||
// To improve accuracy subtract (20 + 15) cycles overhead, defined by delay calibration test
|
||||
uint32_t start = ULP_RISCV_GET_CCOUNT() - 20U - 15U;
|
||||
while ((uint32_t)(ULP_RISCV_GET_CCOUNT() - start) < cycles) {
|
||||
/* busy wait */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes the co-processor busy-wait for a certain number of microseconds.
|
||||
*
|
||||
* @note This function is not accurate for short delays because the function overhead
|
||||
* may exceed the requested delay. For very small delays the implementation uses
|
||||
* a fixed sequence of NOPs (chip-dependent thresholds).
|
||||
*
|
||||
* @note The maximum supported delay depends on the ULP-RISC-V cycle counter width and on
|
||||
* the internal cycles-per-microsecond conversion. For values above the limits below,
|
||||
* the computed delay may overflow and the result is undefined.
|
||||
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: delay_us must be <= 252645135 (about 252.6 s)
|
||||
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: delay_us must be <= 122713351 (about 122.7 s)
|
||||
*
|
||||
* @param delay_us Number of microseconds to busy wait.
|
||||
*/
|
||||
void ulp_riscv_delay_us(uint32_t delay_us);
|
||||
|
||||
/**
|
||||
* @brief Clears the GPIO wakeup interrupt bit
|
||||
*
|
||||
*/
|
||||
void ulp_riscv_gpio_wakeup_clear(void);
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
/**
|
||||
* @brief Enable ULP RISC-V SW Interrupt
|
||||
*
|
||||
* @param handler Interrupt handler
|
||||
* @param arg Interrupt handler argument
|
||||
*/
|
||||
void ulp_riscv_enable_sw_intr(intr_handler_t handler, void *arg);
|
||||
|
||||
/**
|
||||
* @brief Disable ULP RISC-V SW Interrupt
|
||||
*/
|
||||
void ulp_riscv_disable_sw_intr(void);
|
||||
|
||||
/**
|
||||
* @brief Trigger ULP RISC-V SW Interrupt
|
||||
*
|
||||
* @note The SW interrupt will only trigger if it has been enabled previously using ulp_riscv_enable_sw_intr().
|
||||
*/
|
||||
void ulp_riscv_trigger_sw_intr(void);
|
||||
|
||||
/**
|
||||
* @brief Enter a critical section by disabling all interrupts
|
||||
* This inline assembly construct uses the t0 register and is equivalent to:
|
||||
*
|
||||
* li t0, 0x80000007
|
||||
* maskirq_insn(zero, t0) // Mask all interrupt bits
|
||||
*/
|
||||
#define ULP_RISCV_ENTER_CRITICAL() \
|
||||
asm volatile ( \
|
||||
"li t0, 0x80000007\n" \
|
||||
".word 0x0602e00b" \
|
||||
); \
|
||||
|
||||
/**
|
||||
* @brief Exit a critical section by enabling all interrupts
|
||||
* This inline assembly construct is equivalent to:
|
||||
*
|
||||
* maskirq_insn(zero, zero) // Unmask all interrupt bits
|
||||
*/
|
||||
#define ULP_RISCV_EXIT_CRITICAL() asm volatile (".word 0x0600600b");
|
||||
|
||||
#else /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
|
||||
#define ULP_RISCV_ENTER_CRITICAL()
|
||||
#define ULP_RISCV_EXIT_CRITICAL()
|
||||
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
PROVIDE ( RTCCNTL = 0x8000 );
|
||||
PROVIDE ( RTCIO = 0xA400 );
|
||||
PROVIDE ( SENS = 0xC800 );
|
||||
PROVIDE ( RTC_I2C = 0x8C00 );
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
PROVIDE ( RTCCNTL = 0x8000 );
|
||||
PROVIDE ( RTCIO = 0xA400 );
|
||||
PROVIDE ( SENS = 0xC800 );
|
||||
PROVIDE ( RTC_I2C = 0xEC00 );
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
|
||||
ENTRY(reset_vector)
|
||||
|
||||
MEMORY
|
||||
{
|
||||
ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = ORIGIN(ram);
|
||||
.text :
|
||||
{
|
||||
*ulp_riscv_vectors.S.obj(.text.vectors) /* Default reset vector must link to offset 0x0 */
|
||||
*(.text)
|
||||
*(.text*)
|
||||
} >ram
|
||||
|
||||
.rodata ALIGN(4):
|
||||
{
|
||||
*(.rodata)
|
||||
*(.rodata*)
|
||||
} > ram
|
||||
|
||||
.data ALIGN(4):
|
||||
{
|
||||
*(.data)
|
||||
*(.data*)
|
||||
*(.sdata)
|
||||
*(.sdata*)
|
||||
} > ram
|
||||
|
||||
.bss ALIGN(4) :
|
||||
{
|
||||
*(.bss)
|
||||
*(.bss*)
|
||||
*(.sbss)
|
||||
*(.sbss*)
|
||||
} >ram
|
||||
|
||||
__stack_top = ORIGIN(ram) + LENGTH(ram);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "ulp_riscv_interrupt_ops.h"
|
||||
|
||||
.section .text
|
||||
.global __start
|
||||
|
||||
.type __start, %function
|
||||
__start:
|
||||
/* setup the stack pointer */
|
||||
la sp, __stack_top
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
/* Enable interrupts globally */
|
||||
maskirq_insn(zero, zero)
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
|
||||
/* Start ULP user code */
|
||||
call ulp_riscv_rescue_from_monitor
|
||||
call main
|
||||
call ulp_riscv_halt
|
||||
loop:
|
||||
j loop
|
||||
.size __start, .-__start
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "ulp_riscv_adc_ulp_core.h"
|
||||
#include "hal/adc_ll.h"
|
||||
|
||||
int32_t ulp_riscv_adc_read_channel(adc_unit_t adc_n, int channel)
|
||||
{
|
||||
uint32_t event = (adc_n == ADC_UNIT_1) ? ADC_LL_EVENT_ADC1_ONESHOT_DONE : ADC_LL_EVENT_ADC2_ONESHOT_DONE;
|
||||
adc_oneshot_ll_clear_event(event);
|
||||
adc_oneshot_ll_disable_all_unit();
|
||||
adc_oneshot_ll_enable(adc_n);
|
||||
/* Force SW control of the channel bitmap; deep-sleep entry can clear it, which would
|
||||
* otherwise make the channel selection below take no effect. */
|
||||
adc_ll_set_controller(adc_n, ADC_LL_CTRL_RTC);
|
||||
adc_oneshot_ll_set_channel(adc_n, channel);
|
||||
|
||||
adc_oneshot_ll_start(adc_n);
|
||||
while (adc_oneshot_ll_get_event(event) != true) {
|
||||
;
|
||||
}
|
||||
int32_t out_raw = adc_oneshot_ll_get_raw_result(adc_n);
|
||||
if (adc_oneshot_ll_raw_check_valid(adc_n, out_raw) == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//HW workaround: when enabling periph clock, this should be false
|
||||
adc_oneshot_ll_disable_all_unit();
|
||||
|
||||
return out_raw;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
#include "ulp_riscv_gpio.h"
|
||||
#include "include/ulp_riscv_gpio.h"
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
esp_err_t ulp_riscv_gpio_isr_register(gpio_num_t gpio_num, ulp_riscv_gpio_int_type_t intr_type, intr_handler_t handler, void *arg)
|
||||
{
|
||||
if (gpio_num < 0 || gpio_num >= GPIO_NUM_MAX) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (intr_type < 0 || intr_type >= ULP_RISCV_GPIO_INTR_MAX) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Set the interrupt type */
|
||||
REG_SET_FIELD(RTC_GPIO_PIN0_REG + 4 * gpio_num, RTC_GPIO_PIN0_INT_TYPE, intr_type);
|
||||
|
||||
/* Set the interrupt handler */
|
||||
return ulp_riscv_intr_alloc(ULP_RISCV_RTCIO0_INTR_SOURCE + gpio_num, handler, arg);
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_gpio_isr_deregister(gpio_num_t gpio_num)
|
||||
{
|
||||
return ulp_riscv_intr_free(ULP_RISCV_RTCIO0_INTR_SOURCE + gpio_num);
|
||||
}
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "esp_err.h"
|
||||
#include "ulp_riscv_i2c_ulp_core.h"
|
||||
#include "ulp_riscv_utils.h"
|
||||
#include "soc/rtc_i2c_reg.h"
|
||||
#include "soc/rtc_i2c_struct.h"
|
||||
#include "soc/rtc_io_reg.h"
|
||||
#include "soc/sens_reg.h"
|
||||
#include "hal/i2c_ll.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define I2C_CTRL_SLAVE_ADDR_MASK (0xFF << 0)
|
||||
#define I2C_CTRL_SLAVE_REG_ADDR_MASK (0xFF << 11)
|
||||
#define I2C_CTRL_MASTER_TX_DATA_MASK (0xFF << 19)
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
#define ULP_I2C_CMD_RESTART 0 /*!<I2C restart command */
|
||||
#define ULP_I2C_CMD_WRITE 1 /*!<I2C write command */
|
||||
#define ULP_I2C_CMD_READ 2 /*!<I2C read command */
|
||||
#define ULP_I2C_CMD_STOP 3 /*!<I2C stop command */
|
||||
#define ULP_I2C_CMD_END 4 /*!<I2C end command */
|
||||
#else
|
||||
#define ULP_I2C_CMD_RESTART I2C_LL_CMD_RESTART /*!<I2C restart command */
|
||||
#define ULP_I2C_CMD_WRITE I2C_LL_CMD_WRITE /*!<I2C write command */
|
||||
#define ULP_I2C_CMD_READ I2C_LL_CMD_READ /*!<I2C read command */
|
||||
#define ULP_I2C_CMD_STOP I2C_LL_CMD_STOP /*!<I2C stop command */
|
||||
#define ULP_I2C_CMD_END I2C_LL_CMD_END /*!<I2C end command */
|
||||
#endif // CONFIG_IDF_TARGET_ESP32S3
|
||||
|
||||
/* Read/Write timeout (number of iterationis) */
|
||||
#define ULP_RISCV_I2C_RW_TIMEOUT CONFIG_ULP_RISCV_I2C_RW_TIMEOUT
|
||||
|
||||
/*
|
||||
* The RTC I2C controller follows the I2C command registers to perform read/write operations.
|
||||
* The cmd registers have the following format:
|
||||
*
|
||||
* 31 30:14 13:11 10 9 8 7:0
|
||||
* |----------|----------|---------|---------|----------|------------|---------|
|
||||
* | CMD_DONE | Reserved | OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num |
|
||||
* |----------|----------|---------|---------|----------|------------|---------|
|
||||
*/
|
||||
static void ulp_riscv_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val,
|
||||
uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num)
|
||||
{
|
||||
uint32_t reg_addr = RTC_I2C_CMD0_REG + 4 * cmd_idx;
|
||||
|
||||
CLEAR_PERI_REG_MASK(reg_addr, 0xFFFFFFFF);
|
||||
|
||||
WRITE_PERI_REG(reg_addr,
|
||||
(0 << 31) | // CMD Done
|
||||
|
||||
((op_code & 0x3) << 11) | // Opcode
|
||||
|
||||
((ack_val & 0x1) << 10) | // ACK bit sent by I2C controller during READ.
|
||||
// Ignored during RSTART, STOP, END and WRITE cmds.
|
||||
|
||||
((ack_expected & 0x1) << 9) | // ACK bit expected by I2C controller during WRITE.
|
||||
// Ignored during RSTART, STOP, END and READ cmds.
|
||||
|
||||
((ack_check_en & 0x1) << 8) | // I2C controller verifies that the ACK bit sent by the slave device matches
|
||||
// the ACK expected bit during WRITE.
|
||||
// Ignored during RSTART, STOP, END and READ cmds.
|
||||
((byte_num & 0xFF) << 0)); // Byte Num
|
||||
}
|
||||
|
||||
static inline int32_t ulp_riscv_i2c_wait_for_interrupt(int32_t cycles_to_wait)
|
||||
{
|
||||
uint32_t status = 0;
|
||||
uint32_t timeout_start = ulp_riscv_get_cpu_cycles();
|
||||
|
||||
while (1) {
|
||||
status = READ_PERI_REG(RTC_I2C_INT_ST_REG);
|
||||
|
||||
/* If a NAK, Timeout, or Arbitration Loss occurs, abort immediately. */
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
if ((status & RTC_I2C_TIMEOUT_INT_ST) ||
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
if ((status & RTC_I2C_TIME_OUT_INT_ST) ||
|
||||
#endif // CONFIG_IDF_TARGET_ESP32S2
|
||||
(status & RTC_I2C_ACK_ERR_INT_ST) ||
|
||||
(status & RTC_I2C_ARBITRATION_LOST_INT_ST)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Return 0 ONLY if hardware channels are error-free and data bits are latched. */
|
||||
if ((status & RTC_I2C_TX_DATA_INT_ST) ||
|
||||
(status & RTC_I2C_RX_DATA_INT_ST)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handle CPU clock-cycle tracking */
|
||||
if (ulp_riscv_is_timeout_elapsed(timeout_start, cycles_to_wait)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK);
|
||||
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_addr, 0);
|
||||
}
|
||||
|
||||
void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_REG_ADDR_MASK);
|
||||
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_reg_addr, 11);
|
||||
}
|
||||
|
||||
/*
|
||||
* I2C transactions when master reads one byte of data from the slave device:
|
||||
*
|
||||
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
|
||||
* | Master | START | SAD + W | | SUB | | SR | SAD + R | | | NACK | STOP |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
|
||||
* | Slave | | | ACK | | ACK | | | ACK | DATA | | |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|
|
||||
*
|
||||
* I2C transactions when master reads multiple bytes of data from the slave device:
|
||||
*
|
||||
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
|
||||
* | Master | START | SAD + W | | SUB | | SR | SAD + R | | | ACK | | NACK | STOP |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
|
||||
* | Slave | | | ACK | | ACK | | | ACK | DATA | | DATA | | |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
|
||||
*/
|
||||
esp_err_t ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
uint32_t cmd_idx = 0;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (size == 0) {
|
||||
// Quietly return
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Workaround for IDF-9145
|
||||
ULP_RISCV_ENTER_CRITICAL();
|
||||
|
||||
/* By default, RTC I2C controller is hard wired to use CMD2 register onwards for read operations */
|
||||
cmd_idx = 2;
|
||||
|
||||
/* Write slave addr */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2);
|
||||
|
||||
/* Repeated START */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_RESTART, 0, 0, 0, 0);
|
||||
|
||||
/* Write slave register addr */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 1);
|
||||
|
||||
if (size > 1) {
|
||||
/* Read n - 1 bytes */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 0, 0, 1, size - 1);
|
||||
}
|
||||
|
||||
/* Read last byte + NACK */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 1, 1, 1, 1);
|
||||
|
||||
/* STOP */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
|
||||
|
||||
/* Configure the RTC I2C controller in read mode */
|
||||
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27);
|
||||
|
||||
/* Enable Rx data interrupt */
|
||||
SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA);
|
||||
|
||||
/* Start RTC I2C transmission */
|
||||
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
|
||||
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
/* Poll for RTC I2C Rx Data interrupt bit to be set */
|
||||
if (!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
|
||||
/* Read the data
|
||||
*
|
||||
* Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
|
||||
* multiple bytes of data. Therefore, we need to read one byte at a time and clear the
|
||||
* Rx interrupt to get ready for the next byte.
|
||||
*/
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA);
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA);
|
||||
#endif // CONFIG_IDF_TARGET_ESP32S2
|
||||
|
||||
/* Clear the Rx data interrupt bit */
|
||||
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR);
|
||||
} else {
|
||||
/* Error in transaction */
|
||||
CLEAR_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
|
||||
ret = ESP_ERR_INVALID_RESPONSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the RTC I2C transmission bits */
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
|
||||
|
||||
// Workaround for IDF-9145
|
||||
ULP_RISCV_EXIT_CRITICAL();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* I2C transactions when master writes one byte of data to the slave device:
|
||||
*
|
||||
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|
|
||||
* | Master | START | SAD + W | | SUB | | DATA | | STOP |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|
|
||||
* | Slave | | | ACK | | ACK | | ACK | |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|
|
||||
*
|
||||
* I2C transactions when master writes multiple bytes of data to the slave device:
|
||||
*
|
||||
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
|
||||
* | Master | START | SAD + W | | SUB | | DATA | | DATA | | STOP |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
|
||||
* | Slave | | | ACK | | ACK | | ACK | | ACK | |
|
||||
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
|
||||
*/
|
||||
esp_err_t ulp_riscv_i2c_master_write_to_device(const uint8_t *data_wr, size_t size)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
uint32_t cmd_idx = 0;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (size == 0) {
|
||||
// Quietly return
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Workaround for IDF-9145
|
||||
ULP_RISCV_ENTER_CRITICAL();
|
||||
|
||||
/* By default, RTC I2C controller is hard wired to use CMD0 and CMD1 registers for write operations */
|
||||
cmd_idx = 0;
|
||||
|
||||
/* Write slave addr + reg addr + data */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2 + size);
|
||||
|
||||
/* Stop */
|
||||
ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0);
|
||||
|
||||
/* Configure the RTC I2C controller in write mode */
|
||||
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27);
|
||||
|
||||
/* Enable Tx data interrupt */
|
||||
SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_TX_DATA_INT_ENA);
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
/* Write the data to be transmitted */
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK);
|
||||
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, data_wr[i], 19);
|
||||
|
||||
if (i == 0) {
|
||||
/* Start RTC I2C transmission. (Needn't do it for every byte) */
|
||||
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
|
||||
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
|
||||
}
|
||||
|
||||
/* Poll for RTC I2C Tx Data interrupt bit to be set */
|
||||
if (!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
|
||||
/* Clear the Tx data interrupt bit */
|
||||
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR);
|
||||
} else {
|
||||
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
|
||||
ret = ESP_ERR_INVALID_RESPONSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the RTC I2C transmission bits */
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
|
||||
|
||||
// Workaround for IDF-9145
|
||||
ULP_RISCV_EXIT_CRITICAL();
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "include/ulp_riscv_interrupt.h"
|
||||
#include "ulp_riscv_register_ops.h"
|
||||
#include "ulp_riscv_interrupt.h"
|
||||
#include "ulp_riscv_gpio.h"
|
||||
#include "soc/sens_reg.h"
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
|
||||
#define ULP_RISCV_TIMER_INT (1 << 0U) /* Internal Timer Interrupt */
|
||||
#define ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT (1 << 1U) /* EBREAK, ECALL or Illegal instruction */
|
||||
#define ULP_RISCV_BUS_ERROR_INT (1 << 2U) /* Bus Error (Unaligned Memory Access) */
|
||||
#define ULP_RISCV_PERIPHERAL_INTERRUPT (1 << 31U) /* RTC Peripheral Interrupt */
|
||||
#define ULP_RISCV_INTERNAL_INTERRUPT (ULP_RISCV_TIMER_INT | ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT | ULP_RISCV_BUS_ERROR_INT)
|
||||
|
||||
/* Interrupt handler structure */
|
||||
typedef struct {
|
||||
intr_handler_t handler;
|
||||
void *arg;
|
||||
} ulp_riscv_intr_handler_t;
|
||||
|
||||
/* Statically store all interrupt handlers */
|
||||
static ulp_riscv_intr_handler_t s_intr_handlers[ULP_RISCV_MAX_INTR_SOURCE];
|
||||
|
||||
esp_err_t ulp_riscv_intr_alloc(ulp_riscv_interrupt_source_t source, intr_handler_t handler, void *arg)
|
||||
{
|
||||
/* Check the validity of the interrupt source */
|
||||
if (source < 0 || source >= ULP_RISCV_MAX_INTR_SOURCE || handler == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Register interrupt handler */
|
||||
if (s_intr_handlers[source].handler == NULL) {
|
||||
s_intr_handlers[source].handler = handler;
|
||||
s_intr_handlers[source].arg = arg;
|
||||
} else {
|
||||
/* Error: The interrupt handler for this interrupt source has already been allocated */
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_intr_free(ulp_riscv_interrupt_source_t source)
|
||||
{
|
||||
/* Check the validity of the interrupt source */
|
||||
if (source < 0 || source >= ULP_RISCV_MAX_INTR_SOURCE) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* De-register interrupt handler */
|
||||
if (s_intr_handlers[source].handler != NULL) {
|
||||
s_intr_handlers[source].handler = NULL;
|
||||
s_intr_handlers[source].arg = NULL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* This function -
|
||||
* - Checks RTC peripheral interrupt status bit
|
||||
* - Calls interrupt handler if it is registered
|
||||
* - Clears interrupt bit
|
||||
*/
|
||||
static inline void ulp_riscv_handle_rtc_periph_intr(uint32_t status)
|
||||
{
|
||||
/* SW interrupt */
|
||||
if (status & SENS_COCPU_SW_INT_ST) {
|
||||
const ulp_riscv_intr_handler_t* entry = &s_intr_handlers[ULP_RISCV_SW_INTR_SOURCE];
|
||||
|
||||
if (entry->handler) {
|
||||
entry->handler(entry->arg);
|
||||
}
|
||||
|
||||
SET_PERI_REG_MASK(SENS_SAR_COCPU_INT_CLR_REG, SENS_COCPU_SW_INT_CLR);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function -
|
||||
* - Checks if one or more RTC IO interrupt status bits are set
|
||||
* - Calls the interrupt handler for the RTC IO if it is registered
|
||||
* - Clears all interrupt bits
|
||||
*/
|
||||
static inline void ulp_riscv_handle_rtc_io_intr(uint32_t status)
|
||||
{
|
||||
uint32_t handler_idx = 0;
|
||||
for (int i = 0; i < GPIO_NUM_MAX; i++) {
|
||||
if (status & (1U << i)) {
|
||||
handler_idx = ULP_RISCV_RTCIO0_INTR_SOURCE + i;
|
||||
ulp_riscv_intr_handler_t* entry = &s_intr_handlers[handler_idx];
|
||||
|
||||
if (entry->handler) {
|
||||
entry->handler(entry->arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
REG_SET_FIELD(RTC_GPIO_STATUS_W1TC_REG, RTC_GPIO_STATUS_INT_W1TC, status);
|
||||
}
|
||||
|
||||
/* This is the global interrupt handler for ULP RISC-V.
|
||||
* It is called from ulp_riscv_vectors.S
|
||||
*/
|
||||
void __attribute__((weak)) _ulp_riscv_interrupt_handler(uint32_t q1)
|
||||
{
|
||||
/* Call respective interrupt handlers based on the interrupt status in q1 */
|
||||
|
||||
/* Internal Interrupts */
|
||||
if (q1 & ULP_RISCV_INTERNAL_INTERRUPT) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/* External/Peripheral interrupts */
|
||||
if (q1 & ULP_RISCV_PERIPHERAL_INTERRUPT) {
|
||||
/* RTC Peripheral interrupts */
|
||||
uint32_t cocpu_int_st = READ_PERI_REG(SENS_SAR_COCPU_INT_ST_REG);
|
||||
if (cocpu_int_st) {
|
||||
ulp_riscv_handle_rtc_periph_intr(cocpu_int_st);
|
||||
}
|
||||
|
||||
/* RTC IO interrupts */
|
||||
uint32_t rtcio_int_st = REG_GET_FIELD(RTC_GPIO_STATUS_REG, RTC_GPIO_STATUS_INT);
|
||||
if (rtcio_int_st) {
|
||||
ulp_riscv_handle_rtc_io_intr(rtcio_int_st);
|
||||
}
|
||||
|
||||
/* TODO: RTC I2C interrupt */
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "ulp_riscv_lock.h"
|
||||
#include "ulp_riscv_lock_shared.h"
|
||||
|
||||
void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock)
|
||||
{
|
||||
lock->critical_section_flag_ulp = true;
|
||||
lock->turn = ULP_RISCV_LOCK_TURN_MAIN_CPU;
|
||||
|
||||
while (lock->critical_section_flag_main_cpu && (lock->turn == ULP_RISCV_LOCK_TURN_MAIN_CPU)) {
|
||||
}
|
||||
}
|
||||
|
||||
void ulp_riscv_lock_release(ulp_riscv_lock_t *lock)
|
||||
{
|
||||
lock->critical_section_flag_ulp = false;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "ulp_riscv_print.h"
|
||||
#include "ulp_riscv_utils.h"
|
||||
|
||||
typedef struct {
|
||||
putc_fn_t putc_fn; // Putc function of the underlying driver, e.g. UART
|
||||
void *putc_ctx; // Context passed to the putc function
|
||||
} ulp_riscv_print_ctx_t;
|
||||
|
||||
static ulp_riscv_print_ctx_t s_print_ctx;
|
||||
|
||||
void ulp_riscv_print_install(putc_fn_t putc, void * putc_ctx)
|
||||
{
|
||||
s_print_ctx.putc_ctx = putc_ctx;
|
||||
s_print_ctx.putc_fn = putc;
|
||||
}
|
||||
|
||||
void ulp_riscv_print_str(const char *str)
|
||||
{
|
||||
if (!s_print_ctx.putc_fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Perform the bit-banged UART operation in a critical section */
|
||||
ULP_RISCV_ENTER_CRITICAL();
|
||||
|
||||
for (int i = 0; str[i] != 0; i++) {
|
||||
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, str[i]);
|
||||
}
|
||||
|
||||
ULP_RISCV_EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void ulp_riscv_print_hex(int h)
|
||||
{
|
||||
int x;
|
||||
int c;
|
||||
|
||||
if (!s_print_ctx.putc_fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Perform the bit-banged UART operation in a critical section */
|
||||
ULP_RISCV_ENTER_CRITICAL();
|
||||
|
||||
// Does not print '0x', only the digits (8 digits to print)
|
||||
for (x = 0; x < 8; x++) {
|
||||
c = (h >> 28) & 0xf; // extract the leftmost byte
|
||||
if (c < 10) {
|
||||
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, '0' + c);
|
||||
} else {
|
||||
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, 'a' + c - 10);
|
||||
}
|
||||
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
|
||||
}
|
||||
|
||||
ULP_RISCV_EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void ulp_riscv_print_hex_with_number_of_digits(int h, int number_of_digits)
|
||||
{
|
||||
int x;
|
||||
int c;
|
||||
|
||||
if (!s_print_ctx.putc_fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (number_of_digits < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (number_of_digits >= 8) {
|
||||
ulp_riscv_print_hex(h);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Perform the bit-banged UART operation in a critical section */
|
||||
ULP_RISCV_ENTER_CRITICAL();
|
||||
|
||||
// Does not print '0x', only the digits specified by the number_of_digits argument
|
||||
for (x = 0; x < number_of_digits; x++) {
|
||||
c = (h >> ((number_of_digits - 1) * 4)) & 0xf; // extract the leftmost byte
|
||||
if (c < 10) {
|
||||
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, '0' + c);
|
||||
} else {
|
||||
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, 'a' + c - 10);
|
||||
}
|
||||
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
|
||||
}
|
||||
|
||||
ULP_RISCV_EXIT_CRITICAL();
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "ulp_riscv_touch_ulp_core.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/touch_sensor_ll.h"
|
||||
|
||||
/* Check Touch Channel correctness */
|
||||
#define ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(channel) \
|
||||
{ \
|
||||
if (channel >= SOC_TOUCH_MAX_CHAN_ID || \
|
||||
channel < SOC_TOUCH_MIN_CHAN_ID) { \
|
||||
return ESP_ERR_INVALID_ARG; \
|
||||
} \
|
||||
} \
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!raw_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read raw touch data */
|
||||
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_RAW, raw_data);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!benchmark) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read benchmark data */
|
||||
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_BENCHMARK, benchmark);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!smooth_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read smoothened touch sensor data */
|
||||
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_SMOOTH, smooth_data);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_reset_benchmark(int touch_num)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (touch_num > SOC_TOUCH_MAX_CHAN_ID || touch_num < 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Reset benchmark */
|
||||
touch_ll_reset_chan_benchmark(BIT(touch_num));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!raw_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read raw touch data */
|
||||
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_RAW, raw_data);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!benchmark) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read benchmark data */
|
||||
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_BENCHMARK, benchmark);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data)
|
||||
{
|
||||
/* Check Arguments */
|
||||
if (!smooth_data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
|
||||
|
||||
/* Read smoothened touch sensor data */
|
||||
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_SMOOTH, smooth_data);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_riscv_touch_pad_sleep_channel_reset_benchmark(void)
|
||||
{
|
||||
/* Reset benchmark */
|
||||
touch_ll_sleep_reset_benchmark();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "ulp_riscv.h"
|
||||
#include "ulp_riscv_utils.h"
|
||||
#include "ulp_riscv_gpio.h"
|
||||
#include "ulp_riscv_uart_ulp_core.h"
|
||||
|
||||
/* We calculate the bit duration at compile time to speed up and avoid pulling in soft-float libs */
|
||||
#define BIT_DURATION_CYCLES ( (ULP_RISCV_CYCLES_PER_US_NUM * 1000000) / (ULP_RISCV_CYCLES_PER_US_DENOM * CONFIG_ULP_RISCV_UART_BAUDRATE) )
|
||||
|
||||
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg)
|
||||
{
|
||||
uart->tx_pin = cfg->tx_pin;
|
||||
/* 1 bit duration with length given in clock cycles */
|
||||
uart->bit_duration_cycles = BIT_DURATION_CYCLES;
|
||||
|
||||
/* Setup GPIO used for uart TX */
|
||||
ulp_riscv_gpio_init(cfg->tx_pin);
|
||||
ulp_riscv_gpio_output_enable(cfg->tx_pin);
|
||||
ulp_riscv_gpio_set_output_mode(cfg->tx_pin, RTCIO_MODE_OUTPUT_OD);
|
||||
ulp_riscv_gpio_pullup(cfg->tx_pin);
|
||||
ulp_riscv_gpio_pulldown_disable(cfg->tx_pin);
|
||||
ulp_riscv_gpio_output_level(cfg->tx_pin, 1);
|
||||
|
||||
}
|
||||
|
||||
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c)
|
||||
{
|
||||
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
/* Offset the delay to account for cycles spent setting the bit */
|
||||
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 100);
|
||||
if ((1 << i) & c) {
|
||||
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
|
||||
} else {
|
||||
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 20);
|
||||
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
|
||||
ulp_riscv_delay_cycles(uart->bit_duration_cycles);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "ulp_riscv_utils.h"
|
||||
#include "ulp_riscv_register_ops.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "soc/soc_ulp.h"
|
||||
#include "soc/sens_reg.h"
|
||||
#include "ulp_riscv_cpu_freq_shared.h"
|
||||
|
||||
void ulp_riscv_rescue_from_monitor(void)
|
||||
{
|
||||
/* Rescue RISCV from monitor state. */
|
||||
CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN);
|
||||
}
|
||||
|
||||
void ulp_riscv_wakeup_main_processor(void)
|
||||
{
|
||||
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SW_CPU_INT);
|
||||
}
|
||||
|
||||
void ulp_riscv_halt(void)
|
||||
{
|
||||
/* Setting the delay time after RISCV recv `DONE` signal, Ensure that action `RESET` can be executed in time. */
|
||||
REG_SET_FIELD(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_2_CLK_DIS, 0x3F);
|
||||
|
||||
/* Suspends the ulp operation and reset the ULP core. Must be the final operation before going to halt. */
|
||||
SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN);
|
||||
|
||||
while (1);
|
||||
}
|
||||
|
||||
void ulp_riscv_timer_stop(void)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
|
||||
}
|
||||
|
||||
void ulp_riscv_timer_resume(void)
|
||||
{
|
||||
SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
|
||||
}
|
||||
|
||||
void ulp_riscv_gpio_wakeup_clear(void)
|
||||
{
|
||||
SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_GPIO_WAKEUP_CLR);
|
||||
}
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
|
||||
void ulp_riscv_enable_sw_intr(intr_handler_t handler, void *arg)
|
||||
{
|
||||
/* Enable ULP RISC-V SW interrupt */
|
||||
SET_PERI_REG_MASK(SENS_SAR_COCPU_INT_ENA_REG, SENS_COCPU_SW_INT_ENA);
|
||||
|
||||
/* Register interrupt handler */
|
||||
if (handler) {
|
||||
ulp_riscv_intr_alloc(ULP_RISCV_SW_INTR_SOURCE, handler, arg);
|
||||
}
|
||||
}
|
||||
|
||||
void ulp_riscv_disable_sw_intr(void)
|
||||
{
|
||||
/* Disable ULP RISC-V SW interrupt */
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_COCPU_INT_ENA_REG, SENS_COCPU_SW_INT_ENA);
|
||||
|
||||
/* De-register interrupt handler */
|
||||
ulp_riscv_intr_free(ULP_RISCV_SW_INTR_SOURCE);
|
||||
}
|
||||
|
||||
void ulp_riscv_trigger_sw_intr(void)
|
||||
{
|
||||
SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SW_INT_TRIGGER);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
|
||||
void ulp_riscv_delay_us(uint32_t delay_us)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
/*
|
||||
* For very small delays, entering the generic cycle-count loop adds too much fixed overhead.
|
||||
* Use a short calibrated NOP path instead to improve small-delay accuracy on ESP32-S3.
|
||||
*/
|
||||
if (delay_us <= 5) {
|
||||
goto fast_return;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
/*
|
||||
* Same principle as S3, but with a different threshold due to target-specific timing/overhead.
|
||||
*/
|
||||
if (delay_us <= 10) {
|
||||
goto fast_return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Generic delay path:
|
||||
* - Convert requested microseconds to CPU cycles using ratio macros.
|
||||
* - Pre-subtract measured function overhead (55 cycles), so observed delay is closer to request.
|
||||
*/
|
||||
uint32_t start = ulp_riscv_get_cpu_cycles() - 55U;
|
||||
uint32_t req_delay = delay_us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM;
|
||||
|
||||
/*
|
||||
* Busy-wait until elapsed cycles reach req_delay.
|
||||
* uint32_t subtraction intentionally relies on wrap-around-safe arithmetic for cycle counter rollover.
|
||||
*/
|
||||
while ((uint32_t)(ulp_riscv_get_cpu_cycles() - start) < req_delay) {
|
||||
/* busy wait */
|
||||
}
|
||||
return;
|
||||
|
||||
fast_return:
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
/*
|
||||
* Fast path for tiny delays:
|
||||
* Use discrete NOP counts calibrated for this target.
|
||||
* Note: (delay_us == 0 || delay_us <= 2) is intentionally kept as-is to avoid behavior changes.
|
||||
*/
|
||||
if (delay_us == 0 || delay_us <= 2) {
|
||||
return;
|
||||
} else if (delay_us <= 3) {
|
||||
asm volatile("nop\n");
|
||||
} else if (delay_us <= 4) {
|
||||
asm volatile("nop\n nop\n");
|
||||
} else {
|
||||
asm volatile("nop\n nop\n nop\n nop\n nop\n");
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
/*
|
||||
* ESP32-S2 calibrated NOP mapping for very short delays.
|
||||
*/
|
||||
if (delay_us == 0) {
|
||||
return;
|
||||
} else if (delay_us <= 6) {
|
||||
asm volatile("nop\n");
|
||||
} else if (delay_us <= 8) {
|
||||
asm volatile("nop\n nop\n");
|
||||
} else {
|
||||
asm volatile("nop\n nop\n nop\n nop\n nop\n nop\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "ulp_riscv_interrupt_ops.h"
|
||||
|
||||
.equ SAVE_REGS, 17
|
||||
.equ CONTEXT_SIZE, (SAVE_REGS * 4)
|
||||
|
||||
/* Macro which first allocates space on the stack to save general
|
||||
* purpose registers, and then save them. GP register is excluded.
|
||||
* The default size allocated on the stack is CONTEXT_SIZE, but it
|
||||
* can be overridden.
|
||||
*
|
||||
* Note: We don't save the callee-saved s0-s11 registers to save space
|
||||
*/
|
||||
.macro save_general_regs cxt_size=CONTEXT_SIZE
|
||||
addi sp, sp, -\cxt_size
|
||||
sw ra, 0(sp)
|
||||
sw tp, 4(sp)
|
||||
sw t0, 8(sp)
|
||||
sw t1, 12(sp)
|
||||
sw t2, 16(sp)
|
||||
sw a0, 20(sp)
|
||||
sw a1, 24(sp)
|
||||
sw a2, 28(sp)
|
||||
sw a3, 32(sp)
|
||||
sw a4, 36(sp)
|
||||
sw a5, 40(sp)
|
||||
sw a6, 44(sp)
|
||||
sw a7, 48(sp)
|
||||
sw t3, 52(sp)
|
||||
sw t4, 56(sp)
|
||||
sw t5, 60(sp)
|
||||
sw t6, 64(sp)
|
||||
.endm
|
||||
|
||||
/* Restore the general purpose registers (excluding gp) from the context on
|
||||
* the stack. The context is then deallocated. The default size is CONTEXT_SIZE
|
||||
* but it can be overridden. */
|
||||
.macro restore_general_regs cxt_size=CONTEXT_SIZE
|
||||
lw ra, 0(sp)
|
||||
lw tp, 4(sp)
|
||||
lw t0, 8(sp)
|
||||
lw t1, 12(sp)
|
||||
lw t2, 16(sp)
|
||||
lw a0, 20(sp)
|
||||
lw a1, 24(sp)
|
||||
lw a2, 28(sp)
|
||||
lw a3, 32(sp)
|
||||
lw a4, 36(sp)
|
||||
lw a5, 40(sp)
|
||||
lw a6, 44(sp)
|
||||
lw a7, 48(sp)
|
||||
lw t3, 52(sp)
|
||||
lw t4, 56(sp)
|
||||
lw t5, 60(sp)
|
||||
lw t6, 64(sp)
|
||||
addi sp,sp, \cxt_size
|
||||
.endm
|
||||
|
||||
.section .text.vectors
|
||||
.global irq_vector
|
||||
.global reset_vector
|
||||
|
||||
/* The reset vector, jumps to startup code */
|
||||
reset_vector:
|
||||
j __start
|
||||
|
||||
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
|
||||
/* Interrupt handler */
|
||||
.balign 0x10
|
||||
irq_vector:
|
||||
/* Save the general gurpose register context before handling the interrupt */
|
||||
save_general_regs
|
||||
|
||||
/* Fetch the interrupt status from the custom q1 register into a0 */
|
||||
getq_insn(a0, q1)
|
||||
|
||||
/* Call the global C interrupt handler. The interrupt status is passed as the argument in a0.
|
||||
* We do not re-enable interrupts before calling the C handler as ULP RISC-V does not
|
||||
* support nested interrupts.
|
||||
*/
|
||||
jal _ulp_riscv_interrupt_handler
|
||||
|
||||
/* Restore the register context after returning from the C interrupt handler */
|
||||
restore_general_regs
|
||||
|
||||
/* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */
|
||||
retirq_insn()
|
||||
|
||||
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
|
||||
@@ -6,6 +6,11 @@
|
||||
# ulp_project_default, the ULP analogs of idf_project_init,
|
||||
# idf_build_executable, idf_build_binary and idf_project_default.
|
||||
|
||||
# The components a ULP program is built against live inside this subproject, so
|
||||
# an application build does not discover them. Added before components are
|
||||
# searched for.
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/components")
|
||||
|
||||
include(${IDF_PATH}/tools/cmakev2/idf.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/IDFULPProjectCommon.cmake)
|
||||
|
||||
@@ -17,6 +22,21 @@ if(__ULP_BUILDV2)
|
||||
idf_build_set_property(NON_OS_BUILD 1)
|
||||
endif()
|
||||
|
||||
# The component a ULP program of the detected architecture is built against.
|
||||
function(__ulp_arch_component out_var)
|
||||
if(BUILD_RISCV)
|
||||
set(component ulp_riscv)
|
||||
elseif(BUILD_LP_CORE)
|
||||
set(component lp_core)
|
||||
elseif(BUILD_FSM)
|
||||
set(component ulp_fsm)
|
||||
else()
|
||||
idf_die("Unable to determine the ULP type. Set ULP_TYPE to riscv, lp_core or fsm.")
|
||||
endif()
|
||||
|
||||
set(${out_var} "${component}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Reset the compile/link state inherited from the parent app build and select
|
||||
# ULP-specific build behavior. Idempotent within a child configure.
|
||||
function(__ulp_prepare_build)
|
||||
|
||||
Reference in New Issue
Block a user