diff --git a/components/driver/gpio/include/driver/lp_io.h b/components/driver/gpio/include/driver/lp_io.h new file mode 100644 index 0000000000..01673d8bae --- /dev/null +++ b/components/driver/gpio/include/driver/lp_io.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "soc/soc_caps.h" +#include "esp_err.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if SOC_LP_GPIO_MATRIX_SUPPORTED +/** + * @brief Connect a RTC(LP) GPIO input with a peripheral signal, which tagged as input attribute + * + * @note There's no limitation on the number of signals that a RTC(LP) GPIO can connect with + * + * @param gpio_num GPIO number, especially, `LP_GPIO_MATRIX_CONST_ZERO_INPUT` means connect logic 0 to signal + * `LP_GPIO_MATRIX_CONST_ONE_INPUT` means connect logic 1 to signal + * @param signal_idx LP peripheral signal index (tagged as input attribute) + * @param inv Whether the RTC(LP) GPIO input to be inverted or not + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t lp_gpio_connect_in_signal(gpio_num_t gpio_num, uint32_t signal_idx, bool inv); + +/** + * @brief Connect a peripheral signal which tagged as output attribute with a RTC(LP) GPIO + * + * @note There's no limitation on the number of RTC(LP) GPIOs that a signal can connect with + * + * @param gpio_num GPIO number + * @param signal_idx LP peripheral signal index (tagged as input attribute), especially, `SIG_LP_GPIO_OUT_IDX` means disconnect RTC(LP) GPIO and other peripherals. Only the RTC GPIO driver can control the output level + * @param out_inv Whether to signal to be inverted or not + * @param out_en_inv Whether the output enable control is inverted or not + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t lp_gpio_connect_out_signal(gpio_num_t gpio_num, uint32_t signal_idx, bool out_inv, bool out_en_inv); +#endif // SOC_LP_GPIO_MATRIX_SUPPORTED + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/gpio/include/driver/rtc_io.h b/components/driver/gpio/include/driver/rtc_io.h index 36d2b4100e..3b17696f01 100644 --- a/components/driver/gpio/include/driver/rtc_io.h +++ b/components/driver/gpio/include/driver/rtc_io.h @@ -198,6 +198,18 @@ esp_err_t rtc_gpio_set_drive_capability(gpio_num_t gpio_num, gpio_drive_cap_t st */ esp_err_t rtc_gpio_get_drive_capability(gpio_num_t gpio_num, gpio_drive_cap_t* strength); +/** + * @brief Select a RTC IOMUX function for the RTC IO + * + * @param gpio_num GPIO number + * @param func Function to assign to the pin + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t rtc_gpio_iomux_func_sel(gpio_num_t gpio_num, int func); + #endif // SOC_RTCIO_INPUT_OUTPUT_SUPPORTED #if SOC_RTCIO_HOLD_SUPPORTED diff --git a/components/driver/gpio/rtc_io.c b/components/driver/gpio/rtc_io.c index a3b04e49f2..298ef37217 100644 --- a/components/driver/gpio/rtc_io.c +++ b/components/driver/gpio/rtc_io.c @@ -145,6 +145,17 @@ esp_err_t rtc_gpio_pulldown_dis(gpio_num_t gpio_num) return ESP_OK; } +#ifdef CONFIG_SOC_SERIES_ESP32C6 +esp_err_t rtc_gpio_iomux_func_sel(gpio_num_t gpio_num, int func) +{ + ESP_RETURN_ON_FALSE(rtc_gpio_is_valid_gpio(gpio_num), ESP_ERR_INVALID_ARG, RTCIO_TAG, "RTCIO number error"); + RTCIO_ENTER_CRITICAL(); + rtcio_hal_iomux_func_sel(rtc_io_number_get(gpio_num), func); + RTCIO_EXIT_CRITICAL(); + + return ESP_OK; +} +#endif #endif // SOC_RTCIO_INPUT_OUTPUT_SUPPORTED #if SOC_RTCIO_HOLD_SUPPORTED diff --git a/components/esp_hw_support/include/esp_private/periph_ctrl.h b/components/esp_hw_support/include/esp_private/periph_ctrl.h index a20364bd51..68406afac1 100644 --- a/components/esp_hw_support/include/esp_private/periph_ctrl.h +++ b/components/esp_hw_support/include/esp_private/periph_ctrl.h @@ -11,6 +11,55 @@ extern "C" { #endif +/** + * @defgroup Reset and Clock Control APIs + * @{ + */ + +/** + * @brief Acquire the RCC lock for a peripheral module + * + * @note User code protected by this macro should be as short as possible, because it's a critical section + * @note This macro will increase the reference lock of that peripheral. + * You can get the value before the increment from the `rc_name` local variable + */ +#define PERIPH_RCC_ACQUIRE_ATOMIC(rc_periph, rc_name) \ + for (uint8_t rc_name, _rc_cnt = 1, __DECLARE_RCC_RC_ATOMIC_ENV; \ + _rc_cnt ? (rc_name = periph_rcc_acquire_enter(rc_periph), 1) : 0; \ + periph_rcc_acquire_exit(rc_periph, rc_name), _rc_cnt--) + +/** + * @brief Release the RCC lock for a peripheral module + * + * @note User code protected by this macro should be as short as possible, because it's a critical section + * @note This macro will decrease the reference lock of that peripheral. + * You can get the value after the decrease from the `rc_name` local variable + */ +#define PERIPH_RCC_RELEASE_ATOMIC(rc_periph, rc_name) \ + for (uint8_t rc_name, _rc_cnt = 1, __DECLARE_RCC_RC_ATOMIC_ENV; \ + _rc_cnt ? (rc_name = periph_rcc_release_enter(rc_periph), 1) : 0; \ + periph_rcc_release_exit(rc_periph, rc_name), _rc_cnt--) + +/** + * @brief A simplified version of `PERIPH_RCC_ACQUIRE/RELEASE_ATOMIC`, without a reference count + * + * @note User code protected by this macro should be as short as possible, because it's a critical section + */ +#define PERIPH_RCC_ATOMIC() \ + for (int _rc_cnt = 1, __DECLARE_RCC_ATOMIC_ENV; \ + _rc_cnt ? (periph_rcc_enter(), 1) : 0; \ + periph_rcc_exit(), _rc_cnt--) + +/** @cond */ +// The following functions are not intended to be used directly by the developers +uint8_t periph_rcc_acquire_enter(periph_module_t periph); +void periph_rcc_acquire_exit(periph_module_t periph, uint8_t ref_count); +uint8_t periph_rcc_release_enter(periph_module_t periph); +void periph_rcc_release_exit(periph_module_t periph, uint8_t ref_count); +void periph_rcc_enter(void); +void periph_rcc_exit(void); +/** @endcond */ + /** * @brief Enable peripheral module by un-gating the clock and de-asserting the reset signal. * diff --git a/components/esp_hw_support/include/esp_private/uart_share_hw_ctrl.h b/components/esp_hw_support/include/esp_private/uart_share_hw_ctrl.h new file mode 100644 index 0000000000..e132593b1e --- /dev/null +++ b/components/esp_hw_support/include/esp_private/uart_share_hw_ctrl.h @@ -0,0 +1,36 @@ + +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "soc/soc_caps.h" +#include "esp_private/periph_ctrl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if SOC_PERIPH_CLK_CTRL_SHARED +#define HP_UART_SRC_CLK_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define HP_UART_SRC_CLK_ATOMIC() +#endif + +#if SOC_RCC_IS_INDEPENDENT +#define HP_UART_BUS_CLK_ATOMIC() +#else +#define HP_UART_BUS_CLK_ATOMIC() PERIPH_RCC_ATOMIC() +#endif + +#if (SOC_UART_LP_NUM >= 1) +#define LP_UART_SRC_CLK_ATOMIC() PERIPH_RCC_ATOMIC() +#define LP_UART_BUS_CLK_ATOMIC() PERIPH_RCC_ATOMIC() +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/periph_ctrl.c b/components/esp_hw_support/periph_ctrl.c index ef76e860fd..068e1f5355 100644 --- a/components/esp_hw_support/periph_ctrl.c +++ b/components/esp_hw_support/periph_ctrl.c @@ -21,6 +21,40 @@ static int periph_spinlock; static uint8_t ref_counts[PERIPH_MODULE_MAX] = {0}; +IRAM_ATTR void periph_rcc_enter(void) +{ + ENTER_CRITICAL_SECTION(); +} + +IRAM_ATTR void periph_rcc_exit(void) +{ + LEAVE_CRITICAL_SECTION(); +} + +IRAM_ATTR uint8_t periph_rcc_acquire_enter(periph_module_t periph) +{ + periph_rcc_enter(); + return ref_counts[periph]; +} + +IRAM_ATTR void periph_rcc_acquire_exit(periph_module_t periph, uint8_t ref_count) +{ + ref_counts[periph] = ++ref_count; + periph_rcc_exit(); +} + +IRAM_ATTR uint8_t periph_rcc_release_enter(periph_module_t periph) +{ + periph_rcc_enter(); + return ref_counts[periph] - 1; +} + +IRAM_ATTR void periph_rcc_release_exit(periph_module_t periph, uint8_t ref_count) +{ + ref_counts[periph] = ref_count; + periph_rcc_exit(); +} + void periph_module_enable(periph_module_t periph) { assert(periph < PERIPH_MODULE_MAX); diff --git a/components/esp_hw_support/port/esp32c6/rtc_clk.c b/components/esp_hw_support/port/esp32c6/rtc_clk.c index 7358fcdeba..6124ac70be 100644 --- a/components/esp_hw_support/port/esp32c6/rtc_clk.c +++ b/components/esp_hw_support/port/esp32c6/rtc_clk.c @@ -364,7 +364,9 @@ rtc_xtal_freq_t rtc_clk_xtal_freq_get(void) { uint32_t xtal_freq_mhz = clk_ll_xtal_load_freq_mhz(); if (xtal_freq_mhz == 0) { +#if !defined(CONFIG_SOC_ESP32C6_LPCORE) ESP_HW_LOGW(TAG, "invalid RTC_XTAL_FREQ_REG value, assume 40MHz"); +#endif clk_ll_xtal_store_freq_mhz(RTC_XTAL_FREQ_40M); return RTC_XTAL_FREQ_40M; } diff --git a/components/hal/esp32c6/clk_tree_hal.c b/components/hal/esp32c6/clk_tree_hal.c index ca6ccd1e20..1ee2b88b36 100644 --- a/components/hal/esp32c6/clk_tree_hal.c +++ b/components/hal/esp32c6/clk_tree_hal.c @@ -10,7 +10,9 @@ #include "hal/assert.h" #include "hal/log.h" +#if !defined(CONFIG_SOC_ESP32C6_LPCORE) static const char *CLK_HAL_TAG = "clk_hal"; +#endif uint32_t clk_hal_soc_root_get_freq_mhz(soc_cpu_clk_src_t cpu_clk_src) { @@ -69,7 +71,9 @@ uint32_t clk_hal_xtal_get_freq_mhz(void) { uint32_t freq = clk_ll_xtal_load_freq_mhz(); if (freq == 0) { +#if !defined(CONFIG_SOC_ESP32C6_LPCORE) HAL_LOGW(CLK_HAL_TAG, "invalid RTC_XTAL_FREQ_REG value, assume 40MHz"); +#endif return (uint32_t)RTC_XTAL_FREQ_40M; } return freq; diff --git a/components/hal/esp32c6/include/hal/lp_core_ll.h b/components/hal/esp32c6/include/hal/lp_core_ll.h new file mode 100644 index 0000000000..8f3446d389 --- /dev/null +++ b/components/hal/esp32c6/include/hal/lp_core_ll.h @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/******************************************************************************* + * NOTICE + * The hal is not public api, don't use it in application code. + ******************************************************************************/ + +#pragma once + +#include +#include +#include "soc/lpperi_struct.h" +#include "soc/pmu_struct.h" +#include "soc/lp_aon_struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#define LP_CORE_LL_WAKEUP_SOURCE_HP_CPU BIT(0) // Started by HP core (1 single wakeup) +#define LP_CORE_LL_WAKEUP_SOURCE_LP_UART BIT(1) // Enable wake-up by a certain number of LP UART RX pulses +#define LP_CORE_LL_WAKEUP_SOURCE_LP_IO BIT(2) // Enable wake-up by LP IO interrupt +#define LP_CORE_LL_WAKEUP_SOURCE_ETM BIT(3) // Enable wake-up by ETM event +#define LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER BIT(4) // Enable wake-up by LP timer + +/** + * @brief Enable the bus clock for LP-core + * + * @param enable Enable if true, disable if false + */ +static inline void lp_core_ll_enable_bus_clock(bool enable) +{ + /* ESP32C6 does not have clk/rst periph control for LP-core */ + (void)enable; +} + +/** + * @brief Reset the lp_core module + * + */ +static inline void lp_core_ll_reset_register(void) +{ + /* ESP32C6 does not have clk/rst periph control for LP-core */ +} + +/** + * @brief Enable fast access of LP memory + * + * @note When fast access is activated, LP-core cannot access LP mem during deep sleep + * + * @param enable Enable if true, disable if false + */ +static inline void lp_core_ll_fast_lp_mem_enable(bool enable) +{ + LP_AON.lpbus.fast_mem_mux_sel = enable; + LP_AON.lpbus.fast_mem_mux_sel_update = 1; +} + +/** + * @brief Trigger a LP_CORE_LL_WAKEUP_SOURCE_HP_CPU wake-up on the LP-core + * + */ +static inline void lp_core_ll_hp_wake_lp(void) +{ + PMU.hp_lp_cpu_comm.hp_trigger_lp = 1; +} + +/** + * @brief Enable the debug module of LP-core, allowing JTAG to connect + * + * @param enable Enable if true, disable if false + */ +static inline void lp_core_ll_debug_module_enable(bool enable) +{ + LPPERI.cpu.lpcore_dbgm_unavaliable = !enable; +} + +/** + * @brief Enable CPU reset at sleep + * + * @param enable Enable if true, disable if false + */ +static inline void lp_core_ll_rst_at_sleep_enable(bool enable) +{ + PMU.lp_ext.pwr0.slp_reset_en = enable; +} + +/** + * @brief Stall LP-core at sleep requests + * + * @param enable Enable if true, disable if false + */ +static inline void lp_core_ll_stall_at_sleep_request(bool enable) +{ + PMU.lp_ext.pwr0.slp_stall_en = enable; +} + +/** + * @brief Set wake-up sources for the LP-core + * + * @param flags Wake-up sources + */ +static inline void lp_core_ll_set_wakeup_source(uint32_t flags) +{ + PMU.lp_ext.pwr1.wakeup_en = flags; +} + +/** + * @brief Get wake-up sources for the LP-core + */ +static inline uint32_t lp_core_ll_get_wakeup_source(void) +{ + return PMU.lp_ext.pwr1.wakeup_en; +} + +/** + * @brief Request PMU to put LP core to sleep + */ +static inline void lp_core_ll_request_sleep(void) +{ + PMU.lp_ext.pwr1.sleep_req = 1; +} + +/** + * @brief Get which interrupts have triggered on the LP core + * + * @return uint8_t bit mask of triggered LP interrupt sources + */ +static inline uint8_t lp_core_ll_get_triggered_interrupt_srcs(void) +{ + return LPPERI.interrupt_source.lp_interrupt_source; +} + +/** + * @brief Get the flag that marks whether LP CPU is awakened by ETM + * + * @return Return true if lpcore is woken up by soc_etm + */ +static inline bool lp_core_ll_get_etm_wakeup_flag(void) +{ + return LP_AON.lpcore.lpcore_etm_wakeup_flag; +} + +/** + * @brief Clear the flag that marks whether LP CPU is awakened by soc_etm + */ +static inline void lp_core_ll_clear_etm_wakeup_flag(void) +{ + LP_AON.lpcore.lpcore_etm_wakeup_flag_clr = 0x01; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/esp32c6/include/hal/pmu_ll.h b/components/hal/esp32c6/include/hal/pmu_ll.h index c79a4809b0..c203ab021f 100644 --- a/components/hal/esp32c6/include/hal/pmu_ll.h +++ b/components/hal/esp32c6/include/hal/pmu_ll.h @@ -542,6 +542,16 @@ FORCE_INLINE_ATTR void pmu_ll_lp_clear_intsts_mask(pmu_dev_t *hw, uint32_t mask) hw->lp_ext.int_clr.val = mask; } +FORCE_INLINE_ATTR void pmu_ll_lp_clear_sw_intr_status(pmu_dev_t *hw) +{ + hw->lp_ext.int_clr.sw_trigger = 1; +} + +FORCE_INLINE_ATTR void pmu_ll_lp_enable_sw_intr(pmu_dev_t *hw, bool enable) +{ + hw->lp_ext.int_ena.sw_trigger = enable; +} + FORCE_INLINE_ATTR void pmu_ll_lp_set_min_sleep_cycle(pmu_dev_t *hw, uint32_t slow_clk_cycle) { HAL_FORCE_MODIFY_U32_REG_FIELD(hw->wakeup.cntl3, lp_min_slp_val, slow_clk_cycle); diff --git a/components/hal/esp32c6/include/hal/rtc_io_ll.h b/components/hal/esp32c6/include/hal/rtc_io_ll.h index 159e0fcdd1..8df50ce5cc 100644 --- a/components/hal/esp32c6/include/hal/rtc_io_ll.h +++ b/components/hal/esp32c6/include/hal/rtc_io_ll.h @@ -41,11 +41,31 @@ typedef enum { RTCIO_WAKEUP_HIGH_LEVEL = 0x5, /*!< GPIO interrupt type : input high level trigger */ } rtcio_ll_wake_type_t; +typedef enum { + RTCIO_INTR_DISABLE = 0, /*!< Disable GPIO interrupt */ + RTCIO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */ + RTCIO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */ + RTCIO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */ + RTCIO_INTR_LOW_LEVEL = 4, /*!< GPIO interrupt type : input low level trigger */ + RTCIO_INTR_HIGH_LEVEL = 5, /*!< GPIO interrupt type : input high level trigger */ +} rtcio_ll_intr_type_t; + typedef enum { RTCIO_OUTPUT_NORMAL = 0, /*!< RTCIO output mode is normal. */ RTCIO_OUTPUT_OD = 0x1, /*!< RTCIO output mode is open-drain. */ } rtcio_ll_out_mode_t; +/** + * @brief Select a RTC IOMUX function for the RTC IO + * + * @param rtcio_num The index of rtcio. 0 ~ MAX(rtcio). + * @param func Function to assign to the pin + */ +static inline void rtcio_ll_iomux_func_sel(int rtcio_num, int func) +{ + LP_IO.gpio[rtcio_num].mcu_sel = func; +} + /** * @brief Select the rtcio function. * @@ -288,6 +308,24 @@ static inline void rtcio_ll_wakeup_disable(int rtcio_num) LP_IO.pin[rtcio_num].int_type = RTCIO_WAKEUP_DISABLE; } +/** + * Enable interrupt function and set interrupt type + * + * @param rtcio_num The index of rtcio. 0 ~ MAX(rtcio). + * @param type Interrupt type on high level or low level. + */ + +static inline void rtcio_ll_intr_enable(int rtcio_num, rtcio_ll_intr_type_t type) +{ + LP_IO.pin[rtcio_num].int_type = type; + + /* Work around for HW issue, + need to also enable this clk, so that LP_IO.status.status_interrupt can get updated, + and trigger the interrupt on the LP Core + */ + LP_IO.date.clk_en = 1; +} + /** * Enable rtc io output in deep sleep. * diff --git a/components/hal/esp32c6/include/hal/uart_ll.h b/components/hal/esp32c6/include/hal/uart_ll.h index bfa2559b72..466898a05b 100644 --- a/components/hal/esp32c6/include/hal/uart_ll.h +++ b/components/hal/esp32c6/include/hal/uart_ll.h @@ -7,16 +7,19 @@ // The LL layer for UART register operations. // Note that most of the register operations in this layer are non-atomic operations. - #pragma once -#include #include "esp_attr.h" #include "hal/misc.h" #include "hal/uart_types.h" -#include "soc/uart_periph.h" +#include "soc/uart_reg.h" #include "soc/uart_struct.h" +#include "soc/lp_uart_reg.h" #include "soc/pcr_struct.h" +#include "soc/pcr_reg.h" +#include "soc/lp_clkrst_struct.h" +#include "soc/lpperi_struct.h" +#include "hal/assert.h" #ifdef __cplusplus extern "C" { @@ -24,8 +27,11 @@ extern "C" { // The default fifo depth #define UART_LL_FIFO_DEF_LEN (SOC_UART_FIFO_LEN) +#define LP_UART_LL_FIFO_DEF_LEN (SOC_LP_UART_FIFO_LEN) // Get UART hardware instance with giving uart num -#define UART_LL_GET_HW(num) (((num) == 0) ? (&UART0) : (&UART1)) +#define UART_LL_GET_HW(num) (((num) == UART_NUM_0) ? (&UART0) : (((num) == UART_NUM_1) ? (&UART1) : (&LP_UART))) + +#define UART_LL_REG_FIELD_BIT_SHIFT(hw) (((hw) == &LP_UART) ? 3 : 0) #define UART_LL_MIN_WAKEUP_THRESH (3) #define UART_LL_INTR_MASK (0x7ffff) //All interrupt mask @@ -92,17 +98,208 @@ FORCE_INLINE_ATTR void uart_ll_update(uart_dev_t *hw) while (hw->reg_update.reg_update); } +/****************************************** LP_UART Specific ********************************************/ +/** + * @brief Get the LP_UART source clock. + * + * @param hw Beginning address of the peripheral registers. + * @param source_clk Current LP_UART clock source, one in soc_periph_lp_uart_clk_src_t. + */ +FORCE_INLINE_ATTR void lp_uart_ll_get_sclk(uart_dev_t *hw, uart_sclk_t *source_clk) +{ + (void)hw; + switch (LP_CLKRST.lpperi.lp_uart_clk_sel) { + default: + case 0: + *source_clk = (uart_sclk_t)LP_UART_SCLK_LP_FAST; + break; + case 1: + *source_clk = (uart_sclk_t)LP_UART_SCLK_XTAL_D2; + break; + } +} + /** - * @brief Configure the UART core reset. + * @brief Set LP UART source clock + * + * @param hw Address offset of the LP UART peripheral registers + * @param src_clk Source clock for the LP UART peripheral + */ +FORCE_INLINE_ATTR void lp_uart_ll_set_source_clk(uart_dev_t *hw, soc_periph_lp_uart_clk_src_t src_clk) +{ + (void)hw; + switch (src_clk) { + case LP_UART_SCLK_LP_FAST: + LP_CLKRST.lpperi.lp_uart_clk_sel = 0; + break; + case LP_UART_SCLK_XTAL_D2: + LP_CLKRST.lpperi.lp_uart_clk_sel = 1; + break; + default: + // Invalid LP_UART clock source + HAL_ASSERT(false); + } +} + +/// LP_CLKRST.lpperi is a shared register, so this function must be used in an atomic way +// #define lp_uart_ll_set_source_clk(...) (void)__DECLARE_RCC_ATOMIC_ENV; lp_uart_ll_set_source_clk(__VA_ARGS__) + +/** + * @brief Configure the lp uart baud-rate. * * @param hw Beginning address of the peripheral registers. - * @param core_rst_en True to enable the core reset, otherwise set it false. + * @param baud The baud rate to be set. + * @param sclk_freq Frequency of the clock source of UART, in Hz. * - * @return None. + * @return None + */ +FORCE_INLINE_ATTR void lp_uart_ll_set_baudrate(uart_dev_t *hw, uint32_t baud, uint32_t sclk_freq) +{ + // Constants + const uint32_t MAX_DIV_BITS = 12; // UART divider integer part bits + const uint32_t MAX_DIV = (1U << MAX_DIV_BITS) - 1; // Maximum divider value + + // Ensure baud rate and sclk_freq are valid + if (baud == 0 || sclk_freq == 0) abort(); + + // Calculate sclk divider + uint32_t sclk_div = (sclk_freq + MAX_DIV * baud - 1) / (MAX_DIV * baud); + if (sclk_div == 0) abort(); // Handle invalid sclk_div + + // Calculate clk_div with integer and fractional parts + uint32_t clk_div = (sclk_freq * 16) / (baud * sclk_div); + uint32_t clk_div_int = clk_div >> 4; // Integer part + uint32_t clk_div_frac = clk_div & 0xF; // Fractional part + + // Configure hardware registers + hw->clkdiv_sync.clkdiv_int = clk_div_int; + hw->clkdiv_sync.clkdiv_frag = clk_div_frac; + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clk_conf, sclk_div_num, sclk_div - 1); + + // Update UART hardware + uart_ll_update(hw); +} + +/** + * @brief Enable bus clock for the LP UART module + * + * @param hw_id LP UART instance ID + * @param enable True to enable, False to disable + */ +FORCE_INLINE_ATTR void lp_uart_ll_enable_bus_clock(int hw_id, bool enable) +{ + (void)hw_id; + LPPERI.clk_en.lp_uart_ck_en = enable; +} + +/// LPPERI.clk_en is a shared register, so this function must be used in an atomic way +// #define lp_uart_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; _lp_uart_ll_enable_bus_clock(__VA_ARGS__) + +/** + * @brief Enable the UART clock. + * + * @param hw_id LP UART instance ID + */ +FORCE_INLINE_ATTR void lp_uart_ll_sclk_enable(int hw_id) +{ + (void)hw_id; + LP_UART.clk_conf.tx_sclk_en = 1; + LP_UART.clk_conf.rx_sclk_en = 1; +} + +/** + * @brief Disable the UART clock. + * + * @param hw_id LP UART instance ID + */ +FORCE_INLINE_ATTR void lp_uart_ll_sclk_disable(int hw_id) +{ + (void)hw_id; + LP_UART.clk_conf.tx_sclk_en = 0; + LP_UART.clk_conf.rx_sclk_en = 0; +} + +/** + * @brief Reset LP UART module + * + * @param hw_id LP UART instance ID + */ +static inline void lp_uart_ll_reset_register(int hw_id) +{ + (void)hw_id; + LPPERI.reset_en.lp_uart_reset_en = 1; + LPPERI.reset_en.lp_uart_reset_en = 0; +} + +/// LPPERI.reset_en is a shared register, so this function must be used in an atomic way +#define lp_uart_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; lp_uart_ll_reset_register(__VA_ARGS__) + +/*************************************** General LL functions ******************************************/ + +/** + * @brief Check if UART is enabled or disabled. + * + * @param uart_num UART port number, the max port number is (UART_NUM_MAX -1). + * + * @return true: enabled; false: disabled */ -FORCE_INLINE_ATTR void uart_ll_set_reset_core(uart_dev_t *hw, bool core_rst_en) +FORCE_INLINE_ATTR bool uart_ll_is_enabled(uint32_t uart_num) { - UART_LL_PCR_REG_SET(hw, conf, rst_en, core_rst_en); + switch (uart_num) { + case 0: + return PCR.uart0_conf.uart0_clk_en && !PCR.uart0_conf.uart0_rst_en; + case 1: + return PCR.uart1_conf.uart1_clk_en && !PCR.uart1_conf.uart1_rst_en; + case 2: // LP_UART + return LPPERI.clk_en.lp_uart_ck_en && !LPPERI.reset_en.lp_uart_reset_en; + default: + // Unknown uart port number + HAL_ASSERT(false); + return false; + } +} + +/** + * @brief Enable the bus clock for uart + * @param uart_num UART port number, the max port number is (UART_NUM_MAX -1). + * @param enable true to enable, false to disable + */ +static inline void uart_ll_enable_bus_clock(uart_port_t uart_num, bool enable) +{ + switch (uart_num) { + case 0: + PCR.uart0_conf.uart0_clk_en = enable; + break; + case 1: + PCR.uart1_conf.uart1_clk_en = enable; + break; + default: + // LP_UART + abort(); + break; + } +} + +/** + * @brief Reset UART module + * @param uart_num UART port number, the max port number is (UART_NUM_MAX -1). + */ +static inline void uart_ll_reset_register(uart_port_t uart_num) +{ + switch (uart_num) { + case 0: + PCR.uart0_conf.uart0_rst_en = 1; + PCR.uart0_conf.uart0_rst_en = 0; + break; + case 1: + PCR.uart1_conf.uart1_rst_en = 1; + PCR.uart1_conf.uart1_rst_en = 0; + break; + default: + // LP_UART + abort(); + break; + } } /** @@ -114,7 +311,13 @@ FORCE_INLINE_ATTR void uart_ll_set_reset_core(uart_dev_t *hw, bool core_rst_en) */ FORCE_INLINE_ATTR void uart_ll_sclk_enable(uart_dev_t *hw) { - UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_en, 1); + if ((hw) != &LP_UART) { + UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_en, 1); + } else { + // LP_UART clk_en shares the same register with other LP peripherals + // Needs to be protected with a lock, therefore, it has its unique LL function + abort(); + } } /** @@ -126,7 +329,13 @@ FORCE_INLINE_ATTR void uart_ll_sclk_enable(uart_dev_t *hw) */ FORCE_INLINE_ATTR void uart_ll_sclk_disable(uart_dev_t *hw) { - UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_en, 0); + if ((hw) != &LP_UART) { + UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_en, 0); + } else { + // LP_UART clk_en shares the same register with other LP peripherals + // Needs to be protected with a lock, therefore, it has its unique LL function + abort(); + } } /** @@ -138,19 +347,29 @@ FORCE_INLINE_ATTR void uart_ll_sclk_disable(uart_dev_t *hw) * * @return None. */ -FORCE_INLINE_ATTR void uart_ll_set_sclk(uart_dev_t *hw, uart_sclk_t source_clk) +FORCE_INLINE_ATTR void uart_ll_set_sclk(uart_dev_t *hw, soc_module_clk_t source_clk) { - switch (source_clk) { - default: + if ((hw) != &LP_UART) { + uint32_t sel_value = 0; + switch (source_clk) { case UART_SCLK_PLL_F80M: - UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_sel, 1); + sel_value = 1; break; case UART_SCLK_RTC: - UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_sel, 2); + sel_value = 2; break; case UART_SCLK_XTAL: - UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_sel, 3); + sel_value = 3; break; + default: + // Invalid HP_UART clock source + abort(); + } + UART_LL_PCR_REG_SET(hw, sclk_conf, sclk_sel, sel_value); + } else { + // LP_UART clk_sel shares the same register with other LP peripherals + // Needs to be protected with a lock, therefore, it has its unique LL function + abort(); } } @@ -164,17 +383,21 @@ FORCE_INLINE_ATTR void uart_ll_set_sclk(uart_dev_t *hw, uart_sclk_t source_clk) */ FORCE_INLINE_ATTR void uart_ll_get_sclk(uart_dev_t *hw, uart_sclk_t *source_clk) { - switch (UART_LL_PCR_REG_GET(hw, sclk_conf, sclk_sel)) { + if ((hw) != &LP_UART) { + switch (UART_LL_PCR_REG_GET(hw, sclk_conf, sclk_sel)) { default: case 1: - *source_clk = UART_SCLK_PLL_F80M; + *source_clk = (uart_sclk_t)UART_SCLK_PLL_F80M; break; case 2: - *source_clk = UART_SCLK_RTC; + *source_clk = (uart_sclk_t)UART_SCLK_RTC; break; case 3: - *source_clk = UART_SCLK_XTAL; + *source_clk = (uart_sclk_t)UART_SCLK_XTAL; break; + } + } else { + lp_uart_ll_get_sclk(hw, source_clk); } } @@ -200,7 +423,11 @@ FORCE_INLINE_ATTR void uart_ll_set_baudrate(uart_dev_t *hw, uint32_t baud, uint3 // an integer part and a fractional part. hw->clkdiv_sync.clkdiv_int = clk_div >> 4; hw->clkdiv_sync.clkdiv_frag = clk_div & 0xf; - UART_LL_PCR_REG_U32_SET(hw, sclk_conf, sclk_div_num, sclk_div - 1); + if ((hw) == &LP_UART) { + abort(); + } else { + UART_LL_PCR_REG_U32_SET(hw, sclk_conf, sclk_div_num, sclk_div - 1); + } #undef DIV_UP uart_ll_update(hw); } @@ -217,7 +444,13 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_baudrate(uart_dev_t *hw, uint32_t sclk_fr { typeof(hw->clkdiv_sync) div_reg; div_reg.val = hw->clkdiv_sync.val; - return ((sclk_freq << 4)) / (((div_reg.clkdiv_int << 4) | div_reg.clkdiv_frag) * (UART_LL_PCR_REG_U32_GET(hw, sclk_conf, sclk_div_num) + 1)); + int sclk_div; + if ((hw) == &LP_UART) { + sclk_div = HAL_FORCE_READ_U32_REG_FIELD(hw->clk_conf, sclk_div_num) + 1; + } else { + sclk_div = UART_LL_PCR_REG_U32_GET(hw, sclk_conf, sclk_div_num) + 1; + } + return ((sclk_freq << 4)) / (((div_reg.clkdiv_int << 4) | div_reg.clkdiv_frag) * sclk_div); } /** @@ -230,7 +463,7 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_baudrate(uart_dev_t *hw, uint32_t sclk_fr */ FORCE_INLINE_ATTR void uart_ll_ena_intr_mask(uart_dev_t *hw, uint32_t mask) { - hw->int_ena.val |= mask; + hw->int_ena.val = hw->int_ena.val | mask; } /** @@ -243,7 +476,7 @@ FORCE_INLINE_ATTR void uart_ll_ena_intr_mask(uart_dev_t *hw, uint32_t mask) */ FORCE_INLINE_ATTR void uart_ll_disable_intr_mask(uart_dev_t *hw, uint32_t mask) { - hw->int_ena.val &= (~mask); + hw->int_ena.val = hw->int_ena.val & (~mask); } /** @@ -322,8 +555,11 @@ FORCE_INLINE_ATTR void uart_ll_read_rxfifo(uart_dev_t *hw, uint8_t *buf, uint32_ */ FORCE_INLINE_ATTR void uart_ll_write_txfifo(uart_dev_t *hw, const uint8_t *buf, uint32_t wr_len) { + // Write to the FIFO should make sure only involve write operation, any read operation would cause data lost. + // Non-32-bit access would lead to a read-modify-write operation to the register, which is undesired. + // Therefore, use 32-bit access to avoid any potential problem. for (int i = 0; i < (int)wr_len; i++) { - hw->fifo.rxfifo_rd_byte = buf[i]; + hw->fifo.val = (int)buf[i]; } } @@ -366,7 +602,7 @@ FORCE_INLINE_ATTR void uart_ll_txfifo_rst(uart_dev_t *hw) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_rxfifo_len(uart_dev_t *hw) { - return HAL_FORCE_READ_U32_REG_FIELD(hw->status, rxfifo_cnt); + return HAL_FORCE_READ_U32_REG_FIELD(hw->status, rxfifo_cnt) >> UART_LL_REG_FIELD_BIT_SHIFT(hw); } /** @@ -378,7 +614,9 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_rxfifo_len(uart_dev_t *hw) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_txfifo_len(uart_dev_t *hw) { - return UART_LL_FIFO_DEF_LEN - HAL_FORCE_READ_U32_REG_FIELD(hw->status, txfifo_cnt); + uint32_t total_fifo_len = ((hw) == &LP_UART) ? LP_UART_LL_FIFO_DEF_LEN : UART_LL_FIFO_DEF_LEN; + uint32_t txfifo_len = HAL_FORCE_READ_U32_REG_FIELD(hw->status, txfifo_cnt) >> UART_LL_REG_FIELD_BIT_SHIFT(hw); + return (total_fifo_len - txfifo_len); } /** @@ -447,13 +685,13 @@ FORCE_INLINE_ATTR void uart_ll_get_parity(uart_dev_t *hw, uart_parity_t *parity_ * it will produce rxfifo_full_int_raw interrupt. * * @param hw Beginning address of the peripheral registers. - * @param full_thrhd The full threshold value of the rxfifo. `full_thrhd` should be less than `UART_LL_FIFO_DEF_LEN`. + * @param full_thrhd The full threshold value of the rxfifo. `full_thrhd` should be less than `(LP_)UART_LL_FIFO_DEF_LEN`. * * @return None. */ FORCE_INLINE_ATTR void uart_ll_set_rxfifo_full_thr(uart_dev_t *hw, uint16_t full_thrhd) { - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->conf1, rxfifo_full_thrhd, full_thrhd); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->conf1, rxfifo_full_thrhd, full_thrhd << UART_LL_REG_FIELD_BIT_SHIFT(hw)); } /** @@ -467,7 +705,7 @@ FORCE_INLINE_ATTR void uart_ll_set_rxfifo_full_thr(uart_dev_t *hw, uint16_t full */ FORCE_INLINE_ATTR void uart_ll_set_txfifo_empty_thr(uart_dev_t *hw, uint16_t empty_thrhd) { - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->conf1, txfifo_empty_thrhd, empty_thrhd); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->conf1, txfifo_empty_thrhd, empty_thrhd << UART_LL_REG_FIELD_BIT_SHIFT(hw)); } /** @@ -500,7 +738,7 @@ FORCE_INLINE_ATTR void uart_ll_set_tx_idle_num(uart_dev_t *hw, uint32_t idle_num } /** - * @brief Configure the transmiter to send break chars. + * @brief Configure the transmitter to send break chars. * * @param hw Beginning address of the peripheral registers. * @param break_num The number of the break chars need to be send. @@ -531,7 +769,7 @@ FORCE_INLINE_ATTR void uart_ll_set_hw_flow_ctrl(uart_dev_t *hw, uart_hw_flowcont { //only when UART_HW_FLOWCTRL_RTS is set , will the rx_thresh value be set. if (flow_ctrl & UART_HW_FLOWCTRL_RTS) { - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->hwfc_conf_sync, rx_flow_thrhd, rx_thrs); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->hwfc_conf_sync, rx_flow_thrhd, rx_thrs << UART_LL_REG_FIELD_BIT_SHIFT(hw)); hw->hwfc_conf_sync.rx_flow_en = 1; } else { hw->hwfc_conf_sync.rx_flow_en = 0; @@ -567,7 +805,7 @@ FORCE_INLINE_ATTR void uart_ll_get_hw_flow_ctrl(uart_dev_t *hw, uart_hw_flowcont * @brief Configure the software flow control. * * @param hw Beginning address of the peripheral registers. - * @param flow_ctrl The UART sofware flow control settings. + * @param flow_ctrl The UART software flow control settings. * @param sw_flow_ctrl_en Set true to enable software flow control, otherwise set it false. * * @return None. @@ -577,10 +815,10 @@ FORCE_INLINE_ATTR void uart_ll_set_sw_flow_ctrl(uart_dev_t *hw, uart_sw_flowctrl if (sw_flow_ctrl_en) { hw->swfc_conf0_sync.xonoff_del = 1; hw->swfc_conf0_sync.sw_flow_con_en = 1; - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->swfc_conf1, xon_threshold, flow_ctrl->xon_thrd); - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->swfc_conf1, xoff_threshold, flow_ctrl->xoff_thrd); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->swfc_conf1, xon_threshold, (flow_ctrl->xon_thrd) << UART_LL_REG_FIELD_BIT_SHIFT(hw)); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->swfc_conf1, xoff_threshold, (flow_ctrl->xoff_thrd) << UART_LL_REG_FIELD_BIT_SHIFT(hw)); HAL_FORCE_MODIFY_U32_REG_FIELD(hw->swfc_conf0_sync, xon_char, flow_ctrl->xon_char); - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->swfc_conf0_sync, xoff_char, flow_ctrl->xoff_char); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->swfc_conf0_sync, xon_char, flow_ctrl->xoff_char); } else { hw->swfc_conf0_sync.sw_flow_con_en = 0; hw->swfc_conf0_sync.xonoff_del = 0; @@ -667,6 +905,17 @@ FORCE_INLINE_ATTR void uart_ll_set_wakeup_thrd(uart_dev_t *hw, uint32_t wakeup_t hw->sleep_conf2.active_threshold = wakeup_thrd - UART_LL_MIN_WAKEUP_THRESH; } +/** + * @brief Enable/disable the UART pad clock in sleep_state + * + * @param hw Beginning address of the peripheral registers. + * @param enable enable or disable + */ +FORCE_INLINE_ATTR void uart_ll_enable_pad_sleep_clock(uart_dev_t *hw, bool enable) +{ + (void)hw; (void)enable; +} + /** * @brief Configure the UART work in normal mode. * @@ -676,6 +925,10 @@ FORCE_INLINE_ATTR void uart_ll_set_wakeup_thrd(uart_dev_t *hw, uint32_t wakeup_t */ FORCE_INLINE_ATTR void uart_ll_set_mode_normal(uart_dev_t *hw) { + // This function is only for HP_UART use + // LP_UART can only work in normal mode + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + hw->rs485_conf_sync.rs485_en = 0; hw->rs485_conf_sync.rs485tx_rx_en = 0; hw->rs485_conf_sync.rs485rxby_tx_en = 0; @@ -692,6 +945,10 @@ FORCE_INLINE_ATTR void uart_ll_set_mode_normal(uart_dev_t *hw) */ FORCE_INLINE_ATTR void uart_ll_set_mode_rs485_app_ctrl(uart_dev_t *hw) { + // This function is only for HP_UART use + // LP_UART can only work in normal mode + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + // Application software control, remove echo hw->rs485_conf_sync.rs485rxby_tx_en = 1; hw->conf0_sync.irda_en = 0; @@ -712,6 +969,10 @@ FORCE_INLINE_ATTR void uart_ll_set_mode_rs485_app_ctrl(uart_dev_t *hw) */ FORCE_INLINE_ATTR void uart_ll_set_mode_rs485_half_duplex(uart_dev_t *hw) { + // This function is only for HP_UART use + // LP_UART can only work in normal mode + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + // Enable receiver, sw_rts = 1 generates low level on RTS pin hw->conf0_sync.sw_rts = 1; // Half duplex mode @@ -747,6 +1008,10 @@ FORCE_INLINE_ATTR bool uart_ll_is_mode_rs485_half_duplex(uart_dev_t *hw) */ FORCE_INLINE_ATTR void uart_ll_set_mode_collision_detect(uart_dev_t *hw) { + // This function is only for HP_UART use + // LP_UART can only work in normal mode + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + hw->conf0_sync.irda_en = 0; // Enable full-duplex mode hw->rs485_conf_sync.rs485tx_rx_en = 1; @@ -768,6 +1033,10 @@ FORCE_INLINE_ATTR void uart_ll_set_mode_collision_detect(uart_dev_t *hw) */ FORCE_INLINE_ATTR void uart_ll_set_mode_irda(uart_dev_t *hw) { + // This function is only for HP_UART use + // LP_UART can only work in normal mode + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + hw->rs485_conf_sync.rs485_en = 0; hw->rs485_conf_sync.rs485tx_rx_en = 0; hw->rs485_conf_sync.rs485rxby_tx_en = 0; @@ -792,15 +1061,19 @@ FORCE_INLINE_ATTR void uart_ll_set_mode(uart_dev_t *hw, uart_mode_t mode) uart_ll_set_mode_normal(hw); break; case UART_MODE_RS485_COLLISION_DETECT: + // Only HP_UART support this mode uart_ll_set_mode_collision_detect(hw); break; case UART_MODE_RS485_APP_CTRL: + // Only HP_UART support this mode uart_ll_set_mode_rs485_app_ctrl(hw); break; case UART_MODE_RS485_HALF_DUPLEX: + // Only HP_UART support this mode uart_ll_set_mode_rs485_half_duplex(hw); break; case UART_MODE_IRDA: + // Only HP_UART support this mode uart_ll_set_mode_irda(hw); break; } @@ -855,7 +1128,7 @@ FORCE_INLINE_ATTR void uart_ll_get_data_bit_num(uart_dev_t *hw, uart_word_length */ FORCE_INLINE_ATTR bool uart_ll_is_tx_idle(uart_dev_t *hw) { - return ((HAL_FORCE_READ_U32_REG_FIELD(hw->status, txfifo_cnt) == 0) && (hw->fsm_status.st_utx_out == 0)); + return (((HAL_FORCE_READ_U32_REG_FIELD(hw->status, txfifo_cnt) >> UART_LL_REG_FIELD_BIT_SHIFT(hw)) == 0) && (hw->fsm_status.st_utx_out == 0)); } /** @@ -886,7 +1159,7 @@ FORCE_INLINE_ATTR bool uart_ll_is_hw_cts_en(uart_dev_t *hw) * @brief Configure TX signal loop back to RX module, just for the testing purposes * * @param hw Beginning address of the peripheral registers. - * @param loop_back_en Set ture to enable the loop back function, else set it false. + * @param loop_back_en Set true to enable the loop back function, else set it false. * * @return None */ @@ -900,7 +1173,7 @@ FORCE_INLINE_ATTR void uart_ll_xon_force_on(uart_dev_t *hw, bool always_on) { hw->swfc_conf0_sync.force_xon = 1; uart_ll_update(hw); - if(!always_on) { + if (!always_on) { hw->swfc_conf0_sync.force_xon = 0; uart_ll_update(hw); } @@ -917,6 +1190,9 @@ FORCE_INLINE_ATTR void uart_ll_xon_force_on(uart_dev_t *hw, bool always_on) */ FORCE_INLINE_ATTR void uart_ll_inverse_signal(uart_dev_t *hw, uint32_t inv_mask) { + // LP_UART does not support UART_SIGNAL_IRDA_TX_INV and UART_SIGNAL_IRDA_RX_INV + // lp_uart_dev_t has no these fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + typeof(hw->conf0_sync) conf0_reg; conf0_reg.val = hw->conf0_sync.val; conf0_reg.irda_tx_inv = (inv_mask & UART_SIGNAL_IRDA_TX_INV) ? 1 : 0; @@ -946,7 +1222,7 @@ FORCE_INLINE_ATTR void uart_ll_inverse_signal(uart_dev_t *hw, uint32_t inv_mask) FORCE_INLINE_ATTR void uart_ll_set_rx_tout(uart_dev_t *hw, uint16_t tout_thrd) { uint16_t tout_val = tout_thrd; - if(tout_thrd > 0) { + if (tout_thrd > 0) { hw->tout_conf_sync.rx_tout_thrhd = tout_val; hw->tout_conf_sync.rx_tout_en = 1; } else { @@ -965,7 +1241,7 @@ FORCE_INLINE_ATTR void uart_ll_set_rx_tout(uart_dev_t *hw, uint16_t tout_thrd) FORCE_INLINE_ATTR uint16_t uart_ll_get_rx_tout_thr(uart_dev_t *hw) { uint16_t tout_thrd = 0; - if(hw->tout_conf_sync.rx_tout_en > 0) { + if (hw->tout_conf_sync.rx_tout_en > 0) { tout_thrd = hw->tout_conf_sync.rx_tout_thrhd; } return tout_thrd; @@ -980,7 +1256,7 @@ FORCE_INLINE_ATTR uint16_t uart_ll_get_rx_tout_thr(uart_dev_t *hw) */ FORCE_INLINE_ATTR uint16_t uart_ll_max_tout_thrd(uart_dev_t *hw) { - return UART_RX_TOUT_THRHD_V; + return ((hw) == &LP_UART) ? LP_UART_RX_TOUT_THRHD_V : UART_RX_TOUT_THRHD_V; } /** @@ -991,6 +1267,9 @@ FORCE_INLINE_ATTR uint16_t uart_ll_max_tout_thrd(uart_dev_t *hw) */ FORCE_INLINE_ATTR void uart_ll_set_autobaud_en(uart_dev_t *hw, bool enable) { + // LP_UART does not support autobaud + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + hw->conf0_sync.autobaud_en = enable ? 1 : 0; uart_ll_update(hw); } @@ -1002,6 +1281,9 @@ FORCE_INLINE_ATTR void uart_ll_set_autobaud_en(uart_dev_t *hw, bool enable) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_rxd_edge_cnt(uart_dev_t *hw) { + // LP_UART does not support this feature + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + return hw->rxd_cnt.rxd_edge_cnt; } @@ -1012,6 +1294,9 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_rxd_edge_cnt(uart_dev_t *hw) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_pos_pulse_cnt(uart_dev_t *hw) { + // LP_UART does not support this feature + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + return hw->pospulse.posedge_min_cnt; } @@ -1022,6 +1307,9 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_pos_pulse_cnt(uart_dev_t *hw) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_neg_pulse_cnt(uart_dev_t *hw) { + // LP_UART does not support this feature + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + return hw->negpulse.negedge_min_cnt; } @@ -1032,6 +1320,9 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_neg_pulse_cnt(uart_dev_t *hw) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_high_pulse_cnt(uart_dev_t *hw) { + // LP_UART does not support this feature + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + return hw->highpulse.highpulse_min_cnt; } @@ -1042,6 +1333,9 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_high_pulse_cnt(uart_dev_t *hw) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_low_pulse_cnt(uart_dev_t *hw) { + // LP_UART does not support this feature + // lp_uart_dev_t has no following fields (reserved), but no harm since we map the LP_UART instance to the uart_dev_t struct + return hw->lowpulse.lowpulse_min_cnt; } @@ -1054,9 +1348,11 @@ FORCE_INLINE_ATTR uint32_t uart_ll_get_low_pulse_cnt(uart_dev_t *hw) */ FORCE_INLINE_ATTR void uart_ll_force_xoff(uart_port_t uart_num) { - REG_CLR_BIT(UART_SWFC_CONF0_SYNC_REG(uart_num), UART_FORCE_XON); - REG_SET_BIT(UART_SWFC_CONF0_SYNC_REG(uart_num), UART_SW_FLOW_CON_EN | UART_FORCE_XOFF); - uart_ll_update(UART_LL_GET_HW(uart_num)); + uart_dev_t *hw = UART_LL_GET_HW(uart_num); + hw->swfc_conf0_sync.force_xon = 0; + hw->swfc_conf0_sync.sw_flow_con_en = 1; + hw->swfc_conf0_sync.force_xoff = 1; + uart_ll_update(hw); } /** @@ -1068,10 +1364,12 @@ FORCE_INLINE_ATTR void uart_ll_force_xoff(uart_port_t uart_num) */ FORCE_INLINE_ATTR void uart_ll_force_xon(uart_port_t uart_num) { - REG_CLR_BIT(UART_SWFC_CONF0_SYNC_REG(uart_num), UART_FORCE_XOFF); - REG_SET_BIT(UART_SWFC_CONF0_SYNC_REG(uart_num), UART_FORCE_XON); - REG_CLR_BIT(UART_SWFC_CONF0_SYNC_REG(uart_num), UART_SW_FLOW_CON_EN | UART_FORCE_XON); - uart_ll_update(UART_LL_GET_HW(uart_num)); + uart_dev_t *hw = UART_LL_GET_HW(uart_num); + hw->swfc_conf0_sync.force_xoff = 0; + hw->swfc_conf0_sync.force_xon = 1; + hw->swfc_conf0_sync.sw_flow_con_en = 0; + hw->swfc_conf0_sync.force_xon = 0; + uart_ll_update(hw); } /** @@ -1083,7 +1381,8 @@ FORCE_INLINE_ATTR void uart_ll_force_xon(uart_port_t uart_num) */ FORCE_INLINE_ATTR uint32_t uart_ll_get_fsm_status(uart_port_t uart_num) { - return REG_GET_FIELD(UART_FSM_STATUS_REG(uart_num), UART_ST_UTX_OUT); + uart_dev_t *hw = UART_LL_GET_HW(uart_num); + return hw->fsm_status.st_utx_out; } /** diff --git a/components/hal/include/hal/rtc_io_hal.h b/components/hal/include/hal/rtc_io_hal.h index a2422480aa..077d502173 100644 --- a/components/hal/include/hal/rtc_io_hal.h +++ b/components/hal/include/hal/rtc_io_hal.h @@ -166,6 +166,16 @@ void rtcio_hal_set_direction_in_sleep(int rtcio_num, rtc_gpio_mode_t mode); */ #define rtcio_hal_pulldown_disable(rtcio_num) rtcio_ll_pulldown_disable(rtcio_num) +/** + * Select a RTC IOMUX function for the RTC IO + * + * @param rtcio_num The index of rtcio. 0 ~ SOC_RTCIO_PIN_COUNT. + * @param func Function to assign to the pin + */ +#ifdef CONFIG_SOC_SERIES_ESP32C6 +#define rtcio_hal_iomux_func_sel(rtcio_num, func) rtcio_ll_iomux_func_sel(rtcio_num, func) +#endif + #endif // SOC_RTCIO_INPUT_OUTPUT_SUPPORTED #if SOC_RTCIO_HOLD_SUPPORTED diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index e0df0233ba..ae4e949c8d 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -1,14 +1,9 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -// The long term plan is to have a single soc_caps.h for each peripheral. -// During the refactoring and multichip support development process, we -// seperate these information into periph_caps.h for each peripheral and -// include them here. - /* * These defines are parsed and imported as kconfig variables via the script * `tools/gen_soc_caps_kconfig/gen_soc_caps_kconfig.py` @@ -16,11 +11,8 @@ * If this file is changed the script will automatically run the script * and generate the kconfig variables as part of the pre-commit hooks. * - * It can also be ran manually with `./tools/gen_soc_caps_kconfig/gen_soc_caps_kconfig.py 'components/soc/esp32c6/include/soc/'` - * - * For more information see `tools/gen_soc_caps_kconfig/README.md` - * -*/ + * It can also be run manually. For more information, see `${IDF_PATH}/tools/gen_soc_caps_kconfig/README.md` + */ #pragma once @@ -29,6 +21,7 @@ #define SOC_DEDICATED_GPIO_SUPPORTED 1 #define SOC_UART_SUPPORTED 1 #define SOC_GDMA_SUPPORTED 1 +#define SOC_AHB_GDMA_SUPPORTED 1 #define SOC_GPTIMER_SUPPORTED 1 #define SOC_PCNT_SUPPORTED 1 #define SOC_MCPWM_SUPPORTED 1 @@ -40,11 +33,13 @@ #define SOC_ASYNC_MEMCPY_SUPPORTED 1 #define SOC_USB_SERIAL_JTAG_SUPPORTED 1 #define SOC_TEMP_SENSOR_SUPPORTED 1 +#define SOC_PHY_SUPPORTED 1 #define SOC_WIFI_SUPPORTED 1 #define SOC_SUPPORTS_SECURE_DL_MODE 1 #define SOC_ULP_SUPPORTED 1 #define SOC_LP_CORE_SUPPORTED 1 #define SOC_EFUSE_KEY_PURPOSE_FIELD 1 +#define SOC_EFUSE_SUPPORTED 1 #define SOC_RTC_FAST_MEM_SUPPORTED 1 #define SOC_RTC_MEM_SUPPORTED 1 #define SOC_I2S_SUPPORTED 1 @@ -66,11 +61,23 @@ #define SOC_SDIO_SLAVE_SUPPORTED 1 #define SOC_BOD_SUPPORTED 1 #define SOC_APM_SUPPORTED 1 +#define SOC_APM_CTRL_FILTER_SUPPORTED 1 #define SOC_PMU_SUPPORTED 1 #define SOC_PAU_SUPPORTED 1 #define SOC_LP_TIMER_SUPPORTED 1 #define SOC_LP_AON_SUPPORTED 1 +#define SOC_LP_PERIPHERALS_SUPPORTED 1 #define SOC_LP_I2C_SUPPORTED 1 +#define SOC_ULP_LP_UART_SUPPORTED 1 +#define SOC_CLK_TREE_SUPPORTED 1 +#define SOC_ASSIST_DEBUG_SUPPORTED 1 +#define SOC_WDT_SUPPORTED 1 +#define SOC_SPI_FLASH_SUPPORTED 1 +#define SOC_RNG_SUPPORTED 1 +#define SOC_LIGHT_SLEEP_SUPPORTED 1 +#define SOC_DEEP_SLEEP_SUPPORTED 1 +#define SOC_MODEM_CLOCK_SUPPORTED 1 +#define SOC_PM_SUPPORTED 1 /*-------------------------- XTAL CAPS ---------------------------------------*/ #define SOC_XTAL_SUPPORT_40M 1 @@ -121,6 +128,9 @@ /*!< Interrupt */ #define SOC_ADC_TEMPERATURE_SHARE_INTR (1) +/*!< ADC power control is shared by PWDET */ +#define SOC_ADC_SHARED_POWER 1 + // ESP32C6-TODO: Copy from esp32c6, need check /*-------------------------- APB BACKUP DMA CAPS -------------------------------*/ #define SOC_APB_BACKUP_DMA (0) @@ -137,6 +147,7 @@ #define SOC_CPU_INTR_NUM 32 #define SOC_CPU_HAS_FLEXIBLE_INTC 1 #define SOC_INT_PLIC_SUPPORTED 1 //riscv platform-level interrupt controller +#define SOC_CPU_HAS_CSR_PC 1 #define SOC_CPU_BREAKPOINTS_NUM 4 #define SOC_CPU_WATCHPOINTS_NUM 4 @@ -158,9 +169,11 @@ #define SOC_DS_KEY_CHECK_MAX_WAIT_US (1100) /*-------------------------- GDMA CAPS -------------------------------------*/ -#define SOC_GDMA_GROUPS (1U) // Number of GDMA groups -#define SOC_GDMA_PAIRS_PER_GROUP (3) // Number of GDMA pairs in each group -#define SOC_GDMA_SUPPORT_ETM (1) // Support ETM submodule +#define SOC_AHB_GDMA_VERSION 1U +#define SOC_GDMA_NUM_GROUPS_MAX 1U +#define SOC_GDMA_PAIRS_PER_GROUP 3 +#define SOC_GDMA_SUPPORT_ETM 1 // Support ETM submodule +#define SOC_GDMA_SUPPORT_SLEEP_RETENTION 1 /*-------------------------- ETM CAPS --------------------------------------*/ #define SOC_ETM_GROUPS 1U // Number of ETM groups @@ -181,6 +194,8 @@ #define SOC_GPIO_SUPPORT_RTC_INDEPENDENT (1) // GPIO0~7 on ESP32C6 can support chip deep sleep wakeup #define SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP (1) +// LP IO peripherals have independent clock gating to manage +#define SOC_LP_IO_CLOCK_IS_INDEPENDENT (1) #define SOC_GPIO_VALID_GPIO_MASK ((1U< SPI0/SPI1, host_id = 1 -> SPI2, #define SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(host_id) ({(void)host_id; 1;}) +#define SOC_SPI_SCT_SUPPORTED 1 +#define SOC_SPI_SCT_SUPPORTED_PERIPH(PERIPH_NUM) ((PERIPH_NUM==1) ? 1 : 0) //Support Segmented-Configure-Transfer +#define SOC_SPI_SCT_REG_NUM 14 +#define SOC_SPI_SCT_BUFFER_NUM_MAX (1 + SOC_SPI_SCT_REG_NUM) //1-word-bitmap + 14-word-regs +#define SOC_SPI_SCT_CONF_BITLEN_MAX 0x3FFFA //18 bits wide reg + #define SOC_MEMSPI_IS_INDEPENDENT 1 #define SOC_SPI_MAX_PRE_DIVIDER 16 @@ -404,9 +443,11 @@ #define SOC_TIMER_GROUP_SUPPORT_RC_FAST (1) #define SOC_TIMER_GROUP_TOTAL_TIMERS (2) #define SOC_TIMER_SUPPORT_ETM (1) +#define SOC_TIMER_SUPPORT_SLEEP_RETENTION (1) /*--------------------------- WATCHDOG CAPS ---------------------------------------*/ #define SOC_MWDT_SUPPORT_XTAL (1) +#define SOC_MWDT_SUPPORT_SLEEP_RETENTION (1) /*-------------------------- TWAI CAPS ---------------------------------------*/ #define SOC_TWAI_CONTROLLER_NUM 2 @@ -422,7 +463,7 @@ #define SOC_EFUSE_DIS_DIRECT_BOOT 1 #define SOC_EFUSE_SOFT_DIS_JTAG 1 #define SOC_EFUSE_DIS_ICACHE 1 -#define SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK 1 // AES-XTS key purpose not supported for this block +#define SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK 1 // XTS-AES key purpose not supported for this block /*-------------------------- Secure Boot CAPS----------------------------*/ #define SOC_SECURE_BOOT_V2_RSA 1 @@ -432,7 +473,7 @@ #define SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY 1 /*-------------------------- Flash Encryption CAPS----------------------------*/ -#define SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX (32) +#define SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX (64) #define SOC_FLASH_ENCRYPTION_XTS_AES 1 #define SOC_FLASH_ENCRYPTION_XTS_AES_128 1 @@ -440,16 +481,19 @@ #define SOC_CRYPTO_DPA_PROTECTION_SUPPORTED 1 /*-------------------------- UART CAPS ---------------------------------------*/ -// ESP32-C6 has 2 UARTs +// ESP32-C6 has 3 UARTs (2 HP UART, and 1 LP UART) #define SOC_UART_NUM (3) #define SOC_UART_HP_NUM (2) #define SOC_UART_LP_NUM (1U) #define SOC_UART_FIFO_LEN (128) /*!< The UART hardware FIFO length */ +#define SOC_LP_UART_FIFO_LEN (16) /*!< The LP UART hardware FIFO length */ #define SOC_UART_BITRATE_MAX (5000000) /*!< Max bit rate supported by UART */ #define SOC_UART_SUPPORT_PLL_F80M_CLK (1) /*!< Support PLL_F80M as the clock source */ #define SOC_UART_SUPPORT_RTC_CLK (1) /*!< Support RTC clock as the clock source */ #define SOC_UART_SUPPORT_XTAL_CLK (1) /*!< Support XTAL clock as the clock source */ #define SOC_UART_SUPPORT_WAKEUP_INT (1) /*!< Support UART wakeup interrupt */ +#define SOC_UART_HAS_LP_UART (1) /*!< Support LP UART */ +#define SOC_UART_SUPPORT_SLEEP_RETENTION (1) /*!< Support back up registers before sleep */ // UART has an extra TX_WAIT_SEND state when the FIFO is not empty and XOFF is enabled #define SOC_UART_SUPPORT_FSM_TX_WAIT_SEND (1) @@ -475,7 +519,7 @@ #define SOC_PM_SUPPORT_BEACON_WAKEUP (1) #define SOC_PM_SUPPORT_BT_WAKEUP (1) #define SOC_PM_SUPPORT_EXT1_WAKEUP (1) -#define SOC_PM_SUPPORT_EXT1_WAKEUP_MODE_PER_PIN (1) /*!:-specs=picolibc.specs>) + target_compile_options(${ulp_app_name} PRIVATE $<$:-specs=picolibcpp.specs>) + endif() + + if(CONFIG_ULP_COPROC_TYPE_LP_CORE) + set(ULP_BASE_ADDR "0x0") + else() + set(ULP_BASE_ADDR "0x50000000") + endif() + + set(ULP_MAP_GEN ${PYTHON} ${IDF_PATH}/components/ulp/esp32ulp_mapgen.py) + + # Dump the list of global symbols in a convenient format + add_custom_command(OUTPUT ${ULP_APP_NAME}.sym + COMMAND ${CMAKE_NM} -f posix -g $ > ${ulp_app_name}.sym + DEPENDS ${ulp_app_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + # Dump the binary for inclusion into the project + add_custom_command(OUTPUT ${ulp_app_name}.bin + COMMAND ${CMAKE_OBJCOPY} -O binary $ ${ulp_app_name}.bin + DEPENDS ${ulp_app_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + add_custom_command(OUTPUT ${ulp_app_name}.ld ${ulp_app_name}.h + COMMAND ${ULP_MAP_GEN} -s ${ulp_app_name}.sym -o ${ulp_app_name} --base ${ULP_BASE_ADDR} + DEPENDS ${ulp_app_name}.sym + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + # Building the component separately from the project should result in + # ULP files being built. + add_custom_target(build + DEPENDS ${ulp_app_name} ${ulp_app_name}.bin ${ulp_app_name}.sym + ${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.ld + ${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.h + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +endfunction() diff --git a/components/ulp/cmake/toolchain-esp32-ulp.cmake b/components/ulp/cmake/toolchain-esp32-ulp.cmake new file mode 100644 index 0000000000..599e939add --- /dev/null +++ b/components/ulp/cmake/toolchain-esp32-ulp.cmake @@ -0,0 +1,17 @@ +# CMake toolchain file for ULP +set(CMAKE_SYSTEM_NAME Generic) + +# Compiler is only used for preprocessing +set(CMAKE_C_COMPILER "xtensa-esp32-elf-gcc") +set(CMAKE_CXX_COMPILER "xtensa-esp32-elf-g++") + +set(CMAKE_ASM_COMPILER "esp32ulp-elf-as") +set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy") +set(CMAKE_LINKER "esp32ulp-elf-ld") + + +set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + --mcpu=esp32 -o -c ") +set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32ulp -nostdlib" CACHE STRING "ULP Linker Base Flags") +set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} \ + -o ") diff --git a/components/ulp/cmake/toolchain-esp32s2-ulp.cmake b/components/ulp/cmake/toolchain-esp32s2-ulp.cmake new file mode 100644 index 0000000000..7eedc02982 --- /dev/null +++ b/components/ulp/cmake/toolchain-esp32s2-ulp.cmake @@ -0,0 +1,16 @@ +# CMake toolchain file for ULP +set(CMAKE_SYSTEM_NAME Generic) + +# Compiler is only used for preprocessing +set(CMAKE_C_COMPILER "xtensa-esp32s2-elf-gcc") +set(CMAKE_CXX_COMPILER "xtensa-esp32s2-elf-g++") + +set(CMAKE_ASM_COMPILER "esp32ulp-elf-as") +set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy") +set(CMAKE_LINKER "esp32ulp-elf-ld") + +set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + --mcpu=esp32s2 -o -c ") +set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32s2ulp -nostdlib" CACHE STRING "ULP Linker Base Flags") +set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} \ + -o ") diff --git a/components/ulp/cmake/toolchain-esp32s3-ulp.cmake b/components/ulp/cmake/toolchain-esp32s3-ulp.cmake new file mode 100644 index 0000000000..61e0a85c31 --- /dev/null +++ b/components/ulp/cmake/toolchain-esp32s3-ulp.cmake @@ -0,0 +1,16 @@ +# CMake toolchain file for ULP +set(CMAKE_SYSTEM_NAME Generic) + +# Compiler is only used for preprocessing +set(CMAKE_C_COMPILER "xtensa-esp32s3-elf-gcc") +set(CMAKE_CXX_COMPILER "xtensa-esp32s3-elf-g++") + +set(CMAKE_ASM_COMPILER "esp32ulp-elf-as") +set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy") +set(CMAKE_LINKER "esp32ulp-elf-ld") + +set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \ + --mcpu=esp32s3 -o -c ") +set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32s3ulp -nostdlib" CACHE STRING "ULP Linker Base Flags") +set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} \ + -o ") diff --git a/components/ulp/cmake/toolchain-lp-core-riscv.cmake b/components/ulp/cmake/toolchain-lp-core-riscv.cmake new file mode 100644 index 0000000000..eb3a0623be --- /dev/null +++ b/components/ulp/cmake/toolchain-lp-core-riscv.cmake @@ -0,0 +1,15 @@ +# CMake toolchain file for ULP LP core +set(CMAKE_SYSTEM_NAME Generic) + +set(CMAKE_C_COMPILER "riscv32-esp-elf-gcc") +set(CMAKE_CXX_COMPILER "riscv32-esp-elf-g++") +set(CMAKE_ASM_COMPILER "riscv32-esp-elf-gcc") + +set(CMAKE_C_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" + CACHE STRING "C Compiler Base Flags") +set(CMAKE_CXX_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" + CACHE STRING "C++ Compiler Base Flags") +set(CMAKE_ASM_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei -x assembler-with-cpp" + CACHE STRING "Assembler Base Flags") +set(CMAKE_EXE_LINKER_FLAGS "-march=rv32imac_zicsr_zifencei --specs=nano.specs --specs=nosys.specs" + CACHE STRING "Linker Base Flags") diff --git a/components/ulp/cmake/toolchain-ulp-riscv.cmake b/components/ulp/cmake/toolchain-ulp-riscv.cmake new file mode 100644 index 0000000000..be94b1f329 --- /dev/null +++ b/components/ulp/cmake/toolchain-ulp-riscv.cmake @@ -0,0 +1,15 @@ +# CMake toolchain file for ULP-RISC-V +set(CMAKE_SYSTEM_NAME Generic) + +set(CMAKE_C_COMPILER "riscv32-esp-elf-gcc") +set(CMAKE_CXX_COMPILER "riscv32-esp-elf-g++") +set(CMAKE_ASM_COMPILER "riscv32-esp-elf-gcc") + +set(CMAKE_C_FLAGS "-Os -march=rv32imc_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" + CACHE STRING "C Compiler Base Flags") +set(CMAKE_CXX_FLAGS "-Os -march=rv32imc_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections" + CACHE STRING "C++ Compiler Base Flags") +set(CMAKE_ASM_FLAGS "-march=rv32imc -x assembler-with-cpp" + CACHE STRING "Assembler Base Flags") +set(CMAKE_EXE_LINKER_FLAGS "-march=rv32imc_zicsr_zifencei --specs=nano.specs --specs=nosys.specs" + CACHE STRING "Linker Base Flags") diff --git a/components/ulp/component_ulp_common.cmake b/components/ulp/component_ulp_common.cmake new file mode 100644 index 0000000000..35271642bf --- /dev/null +++ b/components/ulp/component_ulp_common.cmake @@ -0,0 +1,14 @@ +spaces2list(ULP_S_SOURCES) +spaces2list(ULP_EXP_DEP_SRCS) + +foreach(ulp_s_source ${ULP_S_SOURCES}) + get_filename_component(ulp_src ${ulp_s_source} ABSOLUTE BASE_DIR ${COMPONENT_DIR}) + list(APPEND ulp_srcs ${ulp_src}) +endforeach() + +foreach(ulp_exp_dep_src ${ULP_EXP_DEP_SRCS}) + get_filename_component(ulp_dep_src ${ulp_exp_dep_src} ABSOLUTE BASE_DIR ${COMPONENT_DIR}) + list(APPEND ulp_dep_srcs ${ulp_dep_src}) +endforeach() + +ulp_embed_binary(${ULP_APP_NAME} "${ulp_srcs}" "${ulp_dep_srcs}") diff --git a/components/ulp/esp32ulp_mapgen.py b/components/ulp/esp32ulp_mapgen.py new file mode 100755 index 0000000000..90b8535aae --- /dev/null +++ b/components/ulp/esp32ulp_mapgen.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# esp32ulp_mapgen utility converts a symbol list provided by nm into an export script +# for the linker and a header file. +# +# SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import argparse +import os +import textwrap +import typing + +UTIL = os.path.basename(__file__) + + +def gen_ld_h_from_sym(f_sym: typing.TextIO, f_ld: typing.TextIO, f_h: typing.TextIO, base_addr: int) -> None: + f_ld.write(textwrap.dedent( + f""" + /* ULP variable definitions for the linker. + * This file is generated automatically by {UTIL} utility. + */ + """ # noqa: E222 + )) + f_h.write(textwrap.dedent( + f""" + /* ULP variable definitions for the compiler. + * This file is generated automatically by {UTIL} utility. + */ + #include + + #pragma once + #ifdef __cplusplus + extern "C" {{ + #endif + """ # noqa: E222 + )) + + for line in f_sym: + # NM "posix" format output has the following structure: + # symbol_name symbol_type addr_hex [size_hex] + parts = line.split() + name = parts[0] + addr = int(parts[2], 16) + base_addr + f_h.write('extern uint32_t ulp_{0};\n'.format(name)) + f_ld.write('PROVIDE ( ulp_{0} = 0x{1:08x} );\n'.format(name, addr)) + + f_h.write(textwrap.dedent( + """ + #ifdef __cplusplus + } + #endif + """ + )) + + +def main() -> None: + description = ('This application generates .h and .ld files for symbols defined in input file. ' + 'The input symbols file can be generated using nm utility like this: ' + 'nm -g -f posix > ') + + parser = argparse.ArgumentParser(description=description) + parser.add_argument('-s', '--symfile', required=True, help='symbols file name', metavar='SYMFILE', type=argparse.FileType('r')) + parser.add_argument('-o', '--outputfile', required=True, help='destination .h and .ld files name prefix', metavar='OUTFILE') + parser.add_argument('--base-addr', required=True, help='base address of the ULP memory, to be added to each symbol') + + args = parser.parse_args() + + with open(args.outputfile + '.h', 'w') as f_h, open(args.outputfile + '.ld', 'w') as f_ld: + gen_ld_h_from_sym(args.symfile, f_ld, f_h, int(args.base_addr, 0)) + + +if __name__ == '__main__': + main() diff --git a/components/ulp/ld/esp32s2.peripherals.ld b/components/ulp/ld/esp32s2.peripherals.ld new file mode 100644 index 0000000000..291467eb1a --- /dev/null +++ b/components/ulp/ld/esp32s2.peripherals.ld @@ -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 ); diff --git a/components/ulp/ld/esp32s3.peripherals.ld b/components/ulp/ld/esp32s3.peripherals.ld new file mode 100644 index 0000000000..5d0753e011 --- /dev/null +++ b/components/ulp/ld/esp32s3.peripherals.ld @@ -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 ); diff --git a/components/ulp/ld/lp_core_riscv.ld b/components/ulp/ld/lp_core_riscv.ld new file mode 100644 index 0000000000..0f2da81c3e --- /dev/null +++ b/components/ulp/ld/lp_core_riscv.ld @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "sdkconfig.h" +#include "soc/soc.h" +#include "ld.common" + +#if CONFIG_ESP_ROM_HAS_LP_ROM +/* With LP-ROM memory layout is different due to LP ROM stack/data */ +#define ULP_MEM_START_ADDRESS SOC_RTC_DRAM_LOW + RESERVE_RTC_MEM +#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) + +ENTRY(reset_vector) + +MEMORY +{ + /*first 128byte for exception/interrupt vectors*/ + vector_table(RX) : ORIGIN = ULP_MEM_START_ADDRESS , LENGTH = 0x80 + ram(RWX) : ORIGIN = ULP_MEM_START_ADDRESS + 0x80, LENGTH = ALIGNED_COPROC_MEM - 0x80 - CONFIG_ULP_SHARED_MEM + shared_mem_ram(RW) : ORIGIN = ULP_MEM_START_ADDRESS + ALIGNED_COPROC_MEM - CONFIG_ULP_SHARED_MEM, LENGTH = CONFIG_ULP_SHARED_MEM +} + +SECTIONS +{ + .vector.text : + { + /*exception/interrupt vectors*/ + __mtvec_base = .; + KEEP (*(.init.vector .init.vector.*)) + } > vector_table + + . = ORIGIN(ram); + + .text ALIGN(4): + { + *(.text.vectors) /* Default reset vector must link to offset 0x80 */ + *(.text) + *(.text*) + } >ram + + .rodata ALIGN(4): + { + *(.rodata) + *(.rodata*) + } > ram + + .data ALIGN(4): + { + *(.data) + *(.data*) + *(.sdata) + *(.sdata*) + } > ram + + .bss ALIGN(4) : + { + *(.bss) + *(.bss*) + *(.sbss) + *(.sbss*) + PROVIDE(end = .); + } >ram + + __stack_top = ORIGIN(ram) + LENGTH(ram); + + . = ORIGIN(shared_mem_ram); + .shared_mem (ALIGN(4)) : + { + KEEP(*(.shared_mem)) + } > shared_mem_ram +} diff --git a/components/ulp/ld/ulp_fsm.ld b/components/ulp/ld/ulp_fsm.ld new file mode 100644 index 0000000000..8e321d2e8c --- /dev/null +++ b/components/ulp/ld/ulp_fsm.ld @@ -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)) + } +} diff --git a/components/ulp/ld/ulp_riscv.ld b/components/ulp/ld/ulp_riscv.ld new file mode 100644 index 0000000000..eedc22f004 --- /dev/null +++ b/components/ulp/ld/ulp_riscv.ld @@ -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); +} diff --git a/components/ulp/lp_core/include/lp_core_etm.h b/components/ulp/lp_core/include/lp_core_etm.h new file mode 100644 index 0000000000..5550906adb --- /dev/null +++ b/components/ulp/lp_core/include/lp_core_etm.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "esp_etm.h" +#include "hal/lp_core_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LP-Core ETM event configuration + */ +typedef struct { + lp_core_etm_event_type_t event_type; /*!< LP-Core ETM event type */ +} lp_core_etm_event_config_t; + +/** + * @brief LP-Core ETM task configuration + */ +typedef struct { + lp_core_etm_task_type_t task_type; /*!< LP-Core ETM task type */ +} lp_core_etm_task_config_t; + +/** + * @brief Create a ETM event for LP-Core + * + * @note The created ETM event object can be deleted later by calling `esp_etm_del_event` + * + * @param[in] config LP-Core ETM event configuration + * @param[out] out_event Returned ETM event handle + * @return + * - ESP_OK: Get ETM event successfully + * - ESP_ERR_INVALID_ARG: Get ETM event failed because of invalid argument + * - ESP_FAIL: Get ETM event failed because of other error + */ +esp_err_t lp_core_new_etm_event(const lp_core_etm_event_config_t *config, esp_etm_event_handle_t *out_event); + +/** + * @brief Create a ETM task for LP-Core + * + * @note The created ETM task object can be deleted later by calling `esp_etm_del_task` + * + * @param[in] config LP-Core ETM task configuration + * @param[out] out_task Returned ETM task handle + * @return + * - ESP_OK: Get ETM task successfully + * - ESP_ERR_INVALID_ARG: Get ETM task failed because of invalid argument + * - ESP_FAIL: Get ETM task failed because of other error + */ +esp_err_t lp_core_new_etm_task(const lp_core_etm_task_config_t *config, esp_etm_task_handle_t *out_task); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/include/lp_core_i2c.h b/components/ulp/lp_core/include/lp_core_i2c.h new file mode 100644 index 0000000000..90c34d4d8d --- /dev/null +++ b/components/ulp/lp_core/include/lp_core_i2c.h @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "hal/i2c_types.h" +#include "hal/gpio_types.h" +#include "esp_err.h" + +/** + * @brief LP Core I2C pin config parameters + */ +typedef struct { + gpio_num_t sda_io_num; /*!< GPIO pin for SDA signal. Only GPIO#6 can be used as the SDA pin. */ + gpio_num_t scl_io_num; /*!< GPIO pin for SCL signal. Only GPIO#7 can be used as the SCL pin. */ + bool sda_pullup_en; /*!< SDA line enable internal pullup. Can be configured if external pullup is not used. */ + bool scl_pullup_en; /*!< SCL line enable internal pullup. Can be configured if external pullup is not used. */ +} lp_core_i2c_pin_cfg_t; + +/** + * @brief LP Core I2C timing config parameters + */ +typedef struct { + uint32_t clk_speed_hz; /*!< LP I2C clock speed for master mode */ +} lp_core_i2c_timing_cfg_t; + +/** + * @brief LP Core I2C config parameters + */ +typedef struct { + lp_core_i2c_pin_cfg_t i2c_pin_cfg; /*!< LP I2C pin configuration */ + lp_core_i2c_timing_cfg_t i2c_timing_cfg; /*!< LP I2C timing configuration */ + soc_periph_lp_i2c_clk_src_t i2c_src_clk; /*!< LP I2C source clock type */ +} lp_core_i2c_cfg_t; + +/* Default LP I2C GPIO settings */ +#define LP_I2C_DEFAULT_GPIO_CONFIG() \ + .i2c_pin_cfg.sda_io_num = GPIO_NUM_6, \ + .i2c_pin_cfg.scl_io_num = GPIO_NUM_7, \ + .i2c_pin_cfg.sda_pullup_en = true, \ + .i2c_pin_cfg.scl_pullup_en = true, \ + +/* LP I2C fast mode config. Max SCL freq of 400 KHz. */ +#define LP_I2C_FAST_MODE_CONFIG() \ + .i2c_timing_cfg.clk_speed_hz = 400000, \ + +/* LP I2C standard mode config. Max SCL freq of 100 KHz. */ +#define LP_I2C_STANDARD_MODE_CONFIG() \ + .i2c_timing_cfg.clk_speed_hz = 100000, \ + +#define LP_I2C_DEFAULT_SRC_CLK() \ + .i2c_src_clk = LP_I2C_SCLK_LP_FAST, \ + +/* Default LP I2C GPIO settings and timing parametes */ +#define LP_CORE_I2C_DEFAULT_CONFIG() \ + { \ + LP_I2C_DEFAULT_GPIO_CONFIG() \ + LP_I2C_FAST_MODE_CONFIG() \ + LP_I2C_DEFAULT_SRC_CLK() \ + } + +/** + * @brief Initialize and configure the LP I2C for use by the LP core + * Currently LP I2C can only be used in master mode + * + * @param lp_i2c_num LP I2C port number + * @param cfg Configuration parameters + * @return esp_err_t ESP_OK when successful + * + * @note The internal pull-up resistors for SDA and SCL pins, if enabled, will + * provide a weak pull-up value of about 30-50 kOhm. Users are adviced to enable + * external pull-ups for better performance at higher SCL frequencies. + */ +esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/include/lp_core_spi.h b/components/ulp/lp_core/include/lp_core_spi.h new file mode 100644 index 0000000000..f2f0ea9f03 --- /dev/null +++ b/components/ulp/lp_core/include/lp_core_spi.h @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LP SPI peripheral + * Since we have just one LP SPI peripheral, we can define it as a uint32_t type for now, instead of an enum. + */ +typedef uint32_t lp_spi_host_t; + +/** + * @brief LP SPI device configuration flags + */ +#define LP_SPI_DEVICE_TXBIT_LSBFIRST (1<<0) /*!< Transmit command/address/data LSB first instead of the default MSB first */ +#define LP_SPI_DEVICE_RXBIT_LSBFIRST (1<<1) /*!< Receive data LSB first instead of the default MSB first */ +#define LP_SPI_DEVICE_BIT_LSBFIRST (LP_SPI_DEVICE_TXBIT_LSBFIRST|LP_SPI_DEVICE_RXBIT_LSBFIRST) /*!< Transmit and receive LSB first */ +#define LP_SPI_DEVICE_3WIRE (1<<2) /*!< Use MOSI (=spid) for both sending and receiving data */ +#define LP_SPI_DEVICE_CS_ACTIVE_HIGH (1<<3) /*!< Make CS line active-high during a transanction instead of the default active-low state. Only available in SPI master mode. */ +#define LP_SPI_DEVICE_HALF_DUPLEX (1<<4) /*!< Transmit data before receiving it, instead of simultaneously. Only available in SPI master mode. */ + +/** + * @brief LP SPI bus configuration parameters + */ +typedef struct { + int mosi_io_num; /*!< GPIO pin for Master out, Slave In signal, a.k.a, SPI_D. */ + int miso_io_num; /*!< GPIO pin for Master in, Slave Out signal, a.k.a, SPI_Q. */ + int sclk_io_num; /*!< GPIO pin for LP SPI Clock signal. */ +} lp_spi_bus_config_t; + +/** + * @brief LP SPI device configuration parameters + */ +typedef struct { + int cs_io_num; /*!< GPIO pin for the device Chip Select (CS) signal. */ + int clock_speed_hz; /*!< SPI clock speed in Hz. */ + int spi_mode; /*!< SPI mode, representing a pair of Clock Polarity (CPOL) and Clock Phase (CPHA) configuration: + - SPI Mode 0: (0, 0) + - SPI Mode 1: (0, 1) + - SPI Mode 2: (1, 0) + - SPI Mode 3: (1, 1) + */ + int duty_cycle; /*!< Duty cycle of positive SPI clock, in 1/256th increments (128 = 50% duty cycle). Setting this to 0 (=not setting it) is equivalent to setting this to 128. */ + int flags; /*!< Bitwise OR of LP_SPI_DEVICE_* flags */ + int cs_ena_pretrans; /*!< Amount of SPI bit-cycles the CS should be active for, before the transmission (0-16). This only works on half-duplex transactions. */ + int cs_ena_posttrans; /*!< Amount of SPI bit-cycles the CS should stay active for, after the transmission (0-16). This only works on half-duplex transactions. */ +} lp_spi_device_config_t; + +/** + * @brief LP SPI slave configuration parameters + */ +typedef struct { + int cs_io_num; /*!< GPIO pin for the device Chip Select (CS) signal. */ + int spi_mode; /*!< SPI mode, representing a pair of Clock Polarity (CPOL) and Clock Phase (CPHA) configuration: + - SPI Mode 0: (0, 0) + - SPI Mode 1: (0, 1) + - SPI Mode 2: (1, 0) + - SPI Mode 3: (1, 1) + */ + int flags; /*!< Bitwise OR of LP_SPI_DEVICE_* flags */ +} lp_spi_slave_config_t; + +/** + * @brief Initialize the LP SPI bus for use by the LP core + * + * @param host_id LP SPI host number + * @param bus_config LP SPI bus configuration parameters + * + * @return esp_err_t ESP_OK when successful + * ESP_ERR_INVALID_ARG if the configuration is invalid + */ +esp_err_t lp_core_lp_spi_bus_initialize(lp_spi_host_t host_id, const lp_spi_bus_config_t *bus_config); + +/** + * @brief Initialize the LP SPI controller in master mode and add an SPI device to the LP SPI bus. + * + * @param host_id LP SPI host number + * @param dev_config LP SPI device configuration parameters + * + * @return esp_err_t ESP_OK when successful + * ESP_ERR_INVALID_ARG if the configuration is invalid + * ESP_FAIL if the device could not be added + */ +esp_err_t lp_core_lp_spi_bus_add_device(lp_spi_host_t host_id, const lp_spi_device_config_t *dev_config); + +/** + * @brief Initialize the LP SPI controller in slave mode + * + * @param host_id LP SPI host number + * @param slave_config LP SPI slave configuration parameters + * + * @return esp_err_t ESP_OK when successful + * ESP_FAIL if the SPI controller could not be initialized in slave mode + */ +esp_err_t lp_core_lp_spi_slave_initialize(lp_spi_host_t host_id, const lp_spi_slave_config_t *slave_config); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/include/lp_core_uart.h b/components/ulp/lp_core/include/lp_core_uart.h new file mode 100644 index 0000000000..bd91f5fbcf --- /dev/null +++ b/components/ulp/lp_core/include/lp_core_uart.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "esp_err.h" +#include "hal/uart_types.h" +#include "hal/gpio_types.h" + +/** + * Default LP_IO Mux pins for LP UART + */ +#if CONFIG_IDF_TARGET_ESP32P4 +#define LP_UART_DEFAULT_TX_GPIO_NUM GPIO_NUM_14 +#define LP_UART_DEFAULT_RX_GPIO_NUM GPIO_NUM_15 +#define LP_UART_DEFAULT_RTS_GPIO_NUM (-1) +#define LP_UART_DEFAULT_CTS_GPIO_NUM (-1) +#elif (CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C5) +#define LP_UART_DEFAULT_TX_GPIO_NUM GPIO_NUM_5 +#define LP_UART_DEFAULT_RX_GPIO_NUM GPIO_NUM_4 +#define LP_UART_DEFAULT_RTS_GPIO_NUM GPIO_NUM_2 +#define LP_UART_DEFAULT_CTS_GPIO_NUM GPIO_NUM_3 +#else +#error "LP IO Mux pins undefined for LP UART" +#endif /* CONFIG_IDF_TARGET_ESP32P4 */ + +/** + * @brief LP UART IO pins configuration + */ +typedef struct { + gpio_num_t tx_io_num; /*!< GPIO pin for UART Tx signal. Only GPIO#5 can be used as the UART Tx pin */ + gpio_num_t rx_io_num; /*!< GPIO pin for UART Rx signal. Only GPIO#4 can be used as the UART Rx pin */ + gpio_num_t rts_io_num; /*!< GPIO pin for UART RTS signal. Only GPIO#2 can be used as the UART RTS pin */ + gpio_num_t cts_io_num; /*!< GPIO pin for UART CTS signal. Only GPIO#3 can be used as the UART CTS pin */ +} lp_core_uart_pin_cfg_t; + +/** + * @brief LP UART protocol configuration + */ +typedef struct { + int baud_rate; /*!< LP UART baud rate */ + uart_word_length_t data_bits; /*!< LP UART byte size */ + uart_parity_t parity; /*!< LP UART parity mode */ + uart_stop_bits_t stop_bits; /*!< LP UART stop bits */ + uart_hw_flowcontrol_t flow_ctrl; /*!< LP UART HW flow control mode (cts/rts) */ + uint8_t rx_flow_ctrl_thresh; /*!< LP UART HW RTS threshold */ +} lp_core_uart_proto_cfg_t; + +/** + * @brief LP UART configuration parameters + */ +typedef struct { + lp_core_uart_pin_cfg_t uart_pin_cfg; /*!< LP UART pin configuration */ + lp_core_uart_proto_cfg_t uart_proto_cfg; /*!< LP UART protocol configuration */ + lp_uart_sclk_t lp_uart_source_clk; /*!< LP UART source clock selection */ +} lp_core_uart_cfg_t; + +/* Default LP UART GPIO settings */ +#define LP_UART_DEFAULT_GPIO_CONFIG() \ + .uart_pin_cfg.tx_io_num = LP_UART_DEFAULT_TX_GPIO_NUM, \ + .uart_pin_cfg.rx_io_num = LP_UART_DEFAULT_RX_GPIO_NUM, \ + .uart_pin_cfg.rts_io_num = LP_UART_DEFAULT_RTS_GPIO_NUM, \ + .uart_pin_cfg.cts_io_num = LP_UART_DEFAULT_CTS_GPIO_NUM, \ + +/* Default LP UART protocol config */ +#define LP_UART_DEFAULT_PROTO_CONFIG() \ + .uart_proto_cfg.baud_rate = 115200, \ + .uart_proto_cfg.data_bits = UART_DATA_8_BITS, \ + .uart_proto_cfg.parity = UART_PARITY_DISABLE, \ + .uart_proto_cfg.stop_bits = UART_STOP_BITS_1, \ + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \ + .uart_proto_cfg.rx_flow_ctrl_thresh = 0, \ + +/* Default LP UART source clock config */ +#define LP_UART_DEFAULT_CLOCK_CONFIG() \ + .lp_uart_source_clk = LP_UART_SCLK_DEFAULT, \ + +/* Default LP UART GPIO settings and protocol parameters */ +#define LP_CORE_UART_DEFAULT_CONFIG() \ + { \ + LP_UART_DEFAULT_GPIO_CONFIG() \ + LP_UART_DEFAULT_PROTO_CONFIG() \ + LP_UART_DEFAULT_CLOCK_CONFIG() \ + } + +/** + * @brief Initialize and configure the LP UART to be used from the LP core + * + * @note The LP UART initialization must be called from the main core (HP CPU) + * + * @param cfg Configuration parameters + * @return esp_err_t ESP_OK when successful + */ +esp_err_t lp_core_uart_init(const lp_core_uart_cfg_t *cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/include/ulp_lp_core.h b/components/ulp/lp_core/include/ulp_lp_core.h new file mode 100644 index 0000000000..6a0373aefa --- /dev/null +++ b/components/ulp/lp_core/include/ulp_lp_core.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include +#include +#include +#include "esp_err.h" +#include "ulp_common.h" +#include "esp_rom_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU BIT(0) // Started by HP core (1 single wakeup) +#define ULP_LP_CORE_WAKEUP_SOURCE_LP_UART BIT(1) // Enable wake-up by a certain number of LP UART RX pulses +#define ULP_LP_CORE_WAKEUP_SOURCE_LP_IO BIT(2) // Enable wake-up by LP IO interrupt +#define ULP_LP_CORE_WAKEUP_SOURCE_ETM BIT(3) // Enable wake-up by ETM event +#define ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER BIT(4) // Enable wake-up by LP timer +#define ULP_LP_CORE_WAKEUP_SOURCE_LP_VAD BIT(5) // Enable wake-up by LP VAD + +/** + * @brief ULP LP core init parameters + * + */ +typedef struct { + uint32_t wakeup_source; /*!< Wakeup source flags */ + uint32_t lp_timer_sleep_duration_us; /*!< Sleep duration when ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER is specified. Measurement unit: us */ +#if ESP_ROM_HAS_LP_ROM + bool skip_lp_rom_boot; /*!< Skips the LP rom code and boots directly into the app code placed in LP RAM, + this gives faster boot time for time sensitive use-cases at the cost of skipping + setup e.g. of UART */ +#endif //ESP_ROM_HAS_LP_ROM +} ulp_lp_core_cfg_t; + +/** + * @brief Configure the ULP + * and run the program loaded into RTC memory + * + * @return ESP_OK on success + */ +esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t* cfg); + +/** + * @brief Load the program binary into RTC memory + * + * @param program_binary pointer to program binary + * @param program_size_bytes size of the program binary + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_SIZE if program_size_bytes is more than KiB + */ +esp_err_t ulp_lp_core_load_binary(const uint8_t* program_binary, size_t program_size_bytes); + +/** + * @brief Puts the ulp to sleep and disables all wakeup sources. + * To restart the ULP call ulp_lp_core_run() with the desired + * wake up trigger. + */ +void ulp_lp_core_stop(void); + +/** + * @brief Trigger a SW interrupt to the LP CPU from the PMU + * + * @note This is the same SW trigger that is used to wake up the LP CPU + * + */ +void ulp_lp_core_sw_intr_trigger(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/lp_core.c b/components/ulp/lp_core/lp_core.c new file mode 100644 index 0000000000..881e6df823 --- /dev/null +++ b/components/ulp/lp_core/lp_core.c @@ -0,0 +1,189 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_rom_caps.h" +#include "soc/soc_caps.h" +#include "esp_log.h" +#include "esp_assert.h" +#include "esp_cpu.h" +#include "soc/pmu_reg.h" +#include "hal/misc.h" +#include "esp_private/periph_ctrl.h" +#include "ulp_common.h" +#include "ulp_lp_core.h" +#include "ulp_lp_core_memory_shared.h" +#include "ulp_lp_core_lp_timer_shared.h" +#include "hal/lp_core_ll.h" +#include + +#define ULP_COPROC_RESERVE_MEM DT_REG_SIZE(DT_NODELABEL(sramlp)) + +#if CONFIG_IDF_TARGET_ESP32P4 +#include "esp32p4/rom/rtc.h" +#endif + +#if CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32C5 +#define LP_CORE_RCC_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define LP_CORE_RCC_ATOMIC() +#endif + +#if ESP_ROM_HAS_LP_ROM +extern uint32_t _rtc_ulp_memory_start; +#endif //ESP_ROM_HAS_LP_ROM + +const static char* TAG = "ulp-lp-core"; + +#define WAKEUP_SOURCE_MAX_NUMBER 6 + +#define RESET_HANDLER_ADDR (intptr_t)(&_rtc_ulp_memory_start + 0x80 / 4) // Placed after the 0x80 byte long vector table + +/* Maps the flags defined in ulp_lp_core.h e.g. ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU to their actual HW values */ +static uint32_t wakeup_src_sw_to_hw_flag_lookup[WAKEUP_SOURCE_MAX_NUMBER] = { + LP_CORE_LL_WAKEUP_SOURCE_HP_CPU, + LP_CORE_LL_WAKEUP_SOURCE_LP_UART, + LP_CORE_LL_WAKEUP_SOURCE_LP_IO, + LP_CORE_LL_WAKEUP_SOURCE_ETM, + LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER, +#if SOC_LP_VAD_SUPPORTED + LP_CORE_LL_WAKEUP_SOURCE_LP_VAD, +#endif +}; + +/* Convert the wake-up sources defined in ulp_lp_core.h to the actual HW wake-up source values */ +static uint32_t lp_core_get_wakeup_source_hw_flags(uint32_t flags) +{ + uint32_t hw_flags = 0; + for (int i = 0; i < WAKEUP_SOURCE_MAX_NUMBER; i++) { + if (flags & (1 << i)) { + hw_flags |= wakeup_src_sw_to_hw_flag_lookup[i]; + } + } + return hw_flags; +} + +esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t* cfg) +{ + if (!cfg->wakeup_source) { + ESP_LOGE(TAG, "No valid wakeup source specified"); + return ESP_ERR_INVALID_ARG; + } + +#if ESP_ROM_HAS_LP_ROM + /* If we have a LP ROM we boot from it, before jumping to the app code */ + intptr_t boot_addr; + if (cfg->skip_lp_rom_boot) { + boot_addr = RESET_HANDLER_ADDR; + } else { + boot_addr = SOC_LP_ROM_LOW; + /* Disable UART init in ROM, it defaults to XTAL clk src + * which is not powered on during deep sleep + * This will cause the ROM code to get stuck during UART output + * if used + */ + REG_SET_BIT(LP_UART_INIT_CTRL_REG, 1 << 0); + } + + lp_core_ll_set_boot_address(boot_addr); + lp_core_ll_set_app_boot_address(RESET_HANDLER_ADDR); + +#endif //ESP_ROM_HAS_LP_ROM + + LP_CORE_RCC_ATOMIC() { + lp_core_ll_reset_register(); + lp_core_ll_enable_bus_clock(true); + } + +#if CONFIG_IDF_TARGET_ESP32C6 + /* Disable fast LP mem access to allow LP core to access LP memory during sleep */ + lp_core_ll_fast_lp_mem_enable(false); +#endif //CONFIG_IDF_TARGET_ESP32C6 + + /* Enable stall at sleep request*/ + lp_core_ll_stall_at_sleep_request(true); + + /* Enable reset CPU when going to sleep */ + /* Avoid resetting chip in sleep mode when debugger is attached, + otherwise configured HW breakpoints and dcsr.ebreak* bits will be missed */ + lp_core_ll_rst_at_sleep_enable(!(CONFIG_ULP_NORESET_UNDER_DEBUG && esp_cpu_dbgr_is_attached())); + + /* Set wake-up sources */ + lp_core_ll_set_wakeup_source(lp_core_get_wakeup_source_hw_flags(cfg->wakeup_source)); + + /* Enable JTAG debugging */ + lp_core_ll_debug_module_enable(true); + + if (cfg->wakeup_source & ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU) { + lp_core_ll_hp_wake_lp(); + } + +#if SOC_LP_TIMER_SUPPORTED + ulp_lp_core_memory_shared_cfg_t* shared_mem = ulp_lp_core_memory_shared_cfg_get(); + + if (cfg->wakeup_source & ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER) { + if (!cfg->lp_timer_sleep_duration_us) { + ESP_LOGI(TAG, "LP timer specified as wakeup source, but no sleep duration set. ULP will only wake-up once unless it calls ulp_lp_core_lp_timer_set_wakeup_time()"); + } + shared_mem->sleep_duration_us = cfg->lp_timer_sleep_duration_us; + shared_mem->sleep_duration_ticks = ulp_lp_core_lp_timer_calculate_sleep_ticks(cfg->lp_timer_sleep_duration_us); + + /* Set first wakeup alarm */ + ulp_lp_core_lp_timer_set_wakeup_time(cfg->lp_timer_sleep_duration_us); + } +#endif + + if (cfg->wakeup_source & (ULP_LP_CORE_WAKEUP_SOURCE_LP_UART)) { + ESP_LOGE(TAG, "Wake-up source not yet supported"); + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +esp_err_t ulp_lp_core_load_binary(const uint8_t* program_binary, size_t program_size_bytes) +{ + if (program_binary == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (program_size_bytes > ULP_COPROC_RESERVE_MEM) { + return ESP_ERR_INVALID_SIZE; + } + + /* Turn off LP CPU before loading binary */ + ulp_lp_core_stop(); +#if ESP_ROM_HAS_LP_ROM + uint32_t* base = (uint32_t*)&_rtc_ulp_memory_start; +#else + uint32_t* base = RTC_SLOW_MEM; +#endif + + //Start by clearing memory reserved with zeros, this will also will initialize the bss: + memset(base, 0, ULP_COPROC_RESERVE_MEM); + memcpy(base, program_binary, program_size_bytes); + + return ESP_OK; +} + +void ulp_lp_core_stop(void) +{ + if (esp_cpu_dbgr_is_attached()) { + /* upon SW reset debugger puts LP core into the infinite loop at reset vector, + so configure it to stall when going to sleep */ + lp_core_ll_stall_at_sleep_request(true); + /* Avoid resetting chip in sleep mode when debugger is attached, + otherwise configured HW breakpoints and dcsr.ebreak* bits will be missed */ + lp_core_ll_rst_at_sleep_enable(!CONFIG_ULP_NORESET_UNDER_DEBUG); + lp_core_ll_debug_module_enable(true); + } + /* Disable wake-up source and put lp core to sleep */ + lp_core_ll_set_wakeup_source(0); + lp_core_ll_request_sleep(); +} + +void ulp_lp_core_sw_intr_trigger(void) +{ + lp_core_ll_hp_wake_lp(); +} diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h new file mode 100644 index 0000000000..9719a29576 --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h @@ -0,0 +1,203 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 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 + +typedef enum { + LP_IO_NUM_0 = 0, /*!< GPIO0, input and output */ + LP_IO_NUM_1 = 1, /*!< GPIO1, input and output */ + LP_IO_NUM_2 = 2, /*!< GPIO2, input and output */ + LP_IO_NUM_3 = 3, /*!< GPIO3, input and output */ + LP_IO_NUM_4 = 4, /*!< GPIO4, input and output */ + LP_IO_NUM_5 = 5, /*!< GPIO5, input and output */ + LP_IO_NUM_6 = 6, /*!< GPIO6, input and output */ + LP_IO_NUM_7 = 7, /*!< GPIO7, input and output */ +#if SOC_RTCIO_PIN_COUNT > 8 + LP_IO_NUM_8 = 8, /*!< GPIO8, input and output */ + LP_IO_NUM_9 = 9, /*!< GPIO9, input and output */ + LP_IO_NUM_10 = 10, /*!< GPIO10, input and output */ + LP_IO_NUM_11 = 11, /*!< GPIO11, input and output */ + LP_IO_NUM_12 = 12, /*!< GPIO12, input and output */ + LP_IO_NUM_13 = 13, /*!< GPIO13, input and output */ + LP_IO_NUM_14 = 14, /*!< GPIO14, input and output */ + LP_IO_NUM_15 = 15, /*!< GPIO15, input and output */ +#endif +} lp_io_num_t; + +typedef enum { + LP_IO_INTR_DISABLE = 0, /*!< Disable GPIO interrupt */ + LP_IO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */ + LP_IO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */ + LP_IO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */ + LP_IO_INTR_LOW_LEVEL = 4, /*!< GPIO interrupt type : input low level trigger */ + LP_IO_INTR_HIGH_LEVEL = 5, /*!< GPIO interrupt type : input high level trigger */ +} lp_io_intr_type_t; + +/** + * @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); +} + +/** + * @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, lp_io_intr_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(); +} + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h new file mode 100644 index 0000000000..a98f5ebb1a --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "hal/i2c_types.h" +#include "esp_err.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 ticks_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 ticks_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 ticks_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 ticks_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 ticks_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 ticks_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); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h new file mode 100644 index 0000000000..8d20d1f43c --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h @@ -0,0 +1,60 @@ +/* + * 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)) +#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_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 diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h new file mode 100644 index 0000000000..86c1eae3ce --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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 diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h new file mode 100644 index 0000000000..95b97e1320 --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2024 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; /*!< Total data length to transmit in bytes */ + uint32_t rx_length; /*!< Total data length to receive in bytes */ + const void *tx_buffer; /*!< Pointer to the transmit buffer. Must be set for master mode transactions. Can be NULL for slave mode transactions. */ + void *rx_buffer; /*!< Pointer to the receive buffer. Must be set for slave mode transactions. Can be NULL for master mode transactions. */ + 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 transaction in master mode to transmit device to an SPI device and optionally receive data + * from the device. + * + * @param trans_desc LP SPI transaction configuration descriptor + * @param ticks_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_master_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait); + +/** + * @brief Initiate an LP SPI transaction in slave mode to receive data from an SPI master and optionally transmit data + * back to the master. + * + * @param trans_desc LP SPI transaction configuration descriptor + * @param ticks_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_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h new file mode 100644 index 0000000000..5b125e5c0f --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "esp_err.h" +#include "hal/uart_types.h" + +/** + * @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 diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h new file mode 100644 index 0000000000..10fe3b525f --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * @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. + */ +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 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); + +/** + * @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); + +/** + * @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 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_enable(bool enable); + +/** + * @brief Clear the interrupt status for the SW triggered interrupt from the PMU + * + */ +void ulp_lp_core_sw_intr_clear(void); + +/** + * @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 diff --git a/components/ulp/lp_core/lp_core/lp_core_i2c.c b/components/ulp/lp_core/lp_core/lp_core_i2c.c new file mode 100644 index 0000000000..cc2d0df7be --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_i2c.c @@ -0,0 +1,484 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "soc/soc_caps.h" +#include "ulp_lp_core_i2c.h" +#include "ulp_lp_core_utils.h" +#include "soc/lp_i2c_reg.h" +#include "soc/i2c_struct.h" +#include "hal/i2c_ll.h" + +#if SOC_LP_I2C_SUPPORTED + +#define LP_I2C_FIFO_LEN SOC_LP_I2C_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 + +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +/* 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 ticks_to_wait) +{ + uint32_t intr_status = 0; + uint32_t to = 0; + + while (1) { + i2c_ll_get_intr_mask(dev, &intr_status); + if (intr_status & intr_mask) { + if (intr_status & LP_I2C_NACK_INT_ST) { + /* 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 & LP_I2C_TRANS_COMPLETE_INT_ST_M) { + /* Transaction complete. + * Disable and clear interrupt bits and break + */ + i2c_ll_disable_intr_mask(dev, intr_mask); + 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 (ticks_to_wait > -1) { + /* If the ticks_to_wait value is not -1, keep track of ticks and + * break from the loop once the timeout is reached. + */ + ulp_lp_core_delay_cycles(1); + to++; + if (to >= ticks_to_wait) { + /* Disable and clear interrupt bits */ + i2c_ll_disable_intr_mask(dev, intr_mask); + 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 ticks_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 = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S); + i2c_ll_enable_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, ticks_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 ticks_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 = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S); + if (s_ack_check_en) { + /* Enable LP_I2C_NACK_INT to check for ACK errors */ + intr_mask |= (1 << LP_I2C_NACK_INT_ST_S); + } + i2c_ll_enable_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, ticks_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 ticks_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 = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S); + if (s_ack_check_en) { + /* Enable LP_I2C_NACK_INT to check for ACK errors */ + intr_mask |= (1 << LP_I2C_NACK_INT_ST_S); + } + i2c_ll_enable_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, ticks_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, ticks_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_I2C_SUPPORTED */ diff --git a/components/ulp/lp_core/lp_core/lp_core_interrupt.c b/components/ulp/lp_core/lp_core/lp_core_interrupt.c new file mode 100644 index 0000000000..66bcb86b01 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_interrupt.c @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#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 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_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 diff --git a/components/ulp/lp_core/lp_core/lp_core_panic.c b/components/ulp/lp_core/lp_core/lp_core_panic.c new file mode 100644 index 0000000000..f85f6f3088 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_panic.c @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "sdkconfig.h" + +#include +#include +#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, + NULL, + "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 diff --git a/components/ulp/lp_core/lp_core/lp_core_print.c b/components/ulp/lp_core/lp_core/lp_core_print.c new file mode 100644 index 0000000000..64d07b783f --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_print.c @@ -0,0 +1,337 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "sdkconfig.h" +#include "ulp_lp_core_uart.h" +#include "hal/uart_hal.h" +#include "esp_rom_uart.h" + +#define LP_UART_PORT_NUM LP_UART_NUM_0 +#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 +void __attribute__((alias("lp_uart_send_char"))) lp_core_print_char(char c); +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 = ""; + } + 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'); +} diff --git a/components/ulp/lp_core/lp_core/lp_core_spi.c b/components/ulp/lp_core/lp_core/lp_core_spi.c new file mode 100644 index 0000000000..9ff1f8a273 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_spi.c @@ -0,0 +1,262 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_LP_SPI_SUPPORTED + +#include +#include +#include "esp_err.h" +#include "ulp_lp_core_spi.h" +#include "soc/lp_spi_struct.h" + +/* Use the register structure to access LP_SPI module registers */ +lp_spi_dev_t *lp_spi_dev = &LP_SPI; + +static inline esp_err_t lp_core_spi_wait_for_interrupt(int32_t ticks_to_wait) +{ + uint32_t to = 0; + while (!lp_spi_dev->spi_dma_int_raw.reg_trans_done_int_raw) { + if (ticks_to_wait > -1) { + /* If the ticks_to_wait value is not -1, keep track of ticks and + * break from the loop once the timeout is reached. + */ + to++; + if (to >= ticks_to_wait) { + /* Clear interrupt bits */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + 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 ticks_to_wait) +{ + esp_err_t ret = ESP_OK; + + /* Argument sanity check + * Note: The Tx buffer is mandatory for this API. + */ + if (trans_desc == NULL || trans_desc->tx_buffer == NULL || trans_desc->tx_length == 0) { + return ESP_ERR_INVALID_ARG; + } + + /* Reset the Tx and Rx FIFOs */ + lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 1; + lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 0; + lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 1; + lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 0; + + /* Clear any previous interrupts. + * Note: LP SPI does not have any DMA access but the interrupt bit lives in the DMA interrupt register. + */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + + /* Make sure that we do not have any ongoing transactions */ + if (lp_spi_dev->spi_cmd.reg_usr) { + return ESP_ERR_INVALID_STATE; + } + + /* Configure dummy bits */ + lp_spi_dev->spi_user.reg_usr_dummy = trans_desc->dummy_bits ? 1 : 0; + if (trans_desc->dummy_bits) { + lp_spi_dev->spi_user1.reg_usr_dummy_cyclelen = trans_desc->dummy_bits - 1; + } + + /* Configure the command and command bit length */ + lp_spi_dev->spi_user.reg_usr_command = trans_desc->command_bits ? 1 : 0; + if (trans_desc->command_bits) { + lp_spi_dev->spi_user2.reg_usr_command_bitlen = trans_desc->command_bits - 1; + lp_spi_dev->spi_user2.reg_usr_command_value = lp_spi_dev->spi_ctrl.reg_wr_bit_order ? trans_desc->command : __builtin_bswap32(trans_desc->command << (32 - trans_desc->command_bits)); + } + + /* Configure the address and address bit length */ + lp_spi_dev->spi_user.reg_usr_addr = trans_desc->address_bits ? 1 : 0; + if (trans_desc->address_bits) { + lp_spi_dev->spi_user1.reg_usr_addr_bitlen = trans_desc->address_bits - 1; + lp_spi_dev->spi_addr.reg_usr_addr_value = lp_spi_dev->spi_ctrl.reg_wr_bit_order ? __builtin_bswap32(trans_desc->address) : trans_desc->address << (32 - trans_desc->address_bits); + } + + /* Set data lines */ + lp_spi_dev->spi_user.reg_usr_mosi = 1; + lp_spi_dev->spi_user.reg_usr_miso = trans_desc->rx_buffer ? 1 : 0; + + /* Configure the transaction bit length */ + int tx_bitlen = trans_desc->tx_length * 8; + lp_spi_dev->spi_ms_dlen.reg_ms_data_bitlen = tx_bitlen - 1; + + /* Prepare the data to be transmitted */ + uint32_t tx_idx = 0; + uint32_t rx_idx = 0; + + /* The TRM suggests that the data is sent from and received in the LP_SPI_W0_REG ~ LP_SPI_W15_REG registers. + * The following rules apply: + * 1. The first 64 bytes are sent from/received in LP_SPI_W0_REG ~ LP_SPI_W15_REG + * 2. Bytes 64 - 255 are repeatedly sent from or received in LP_SPI_W15_REG[31:24] + * 3. Subsequent blocks of 256 bytes of data continue to follow the above rules + * + * This driver, however, avoids using the LP_SPI_W15_REG altogether. In other words, + * this driver sends or receives data in chunks of 60 bytes (LP_SPI_W0_REG ~ LP_SPI_W14_REG) + * and does not handle the repeated use of the high-byte of LP_SPI_W15_REG. This design approach + * has been chosen to simplify the data handling logic. + */ + uint8_t max_data_reg_num = (SOC_LP_SPI_MAXIMUM_BUFFER_SIZE / 4) - 1; // 15 + uint8_t max_data_chunk_size = max_data_reg_num * 4; // 60 + while (tx_idx < trans_desc->tx_length) { + /* Store 4 bytes of data in the data buffer registers serially. */ + lp_spi_dev->data_buf[(tx_idx / 4) & max_data_reg_num].reg_buf = *(uint32_t *)(trans_desc->tx_buffer + tx_idx); + tx_idx += 4; + + /* Begin transmission of the data if we have pushed all the data or if we have reached the maximum data chunk size */ + if ((tx_idx >= trans_desc->tx_length) || (tx_idx % max_data_chunk_size) == 0) { + /* Apply the configuration */ + lp_spi_dev->spi_cmd.reg_update = 1; + while (lp_spi_dev->spi_cmd.reg_update) { + ; + } + + /* Start the transaction */ + lp_spi_dev->spi_cmd.reg_usr = 1; + + /* Wait for the transaction to complete */ + ret = lp_core_spi_wait_for_interrupt(ticks_to_wait); + if (ret != ESP_OK) { + return ret; + } + + /* Clear the transaction done interrupt */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + + /* Fetch the received data if an Rx buffer is provided */ + if (trans_desc->rx_buffer != NULL) { + while (rx_idx < tx_idx) { + *(uint32_t *)(trans_desc->rx_buffer + rx_idx) = lp_spi_dev->data_buf[(rx_idx / 4) & max_data_reg_num].reg_buf; + rx_idx += 4; + // This loop would exit even if we haven't received all the data. + } + } + } + } + + return ret; +} + +esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait) +{ + esp_err_t ret = ESP_OK; + + /* Argument sanity check + * Note: The Rx buffer is mandatory for this API. + */ + if (trans_desc == NULL || trans_desc->rx_buffer == NULL || trans_desc->rx_length == 0) { + return ESP_ERR_INVALID_ARG; + } + + /* Reset the Tx and Rx FIFOs */ + lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 1; + lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 0; + lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 1; + lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 0; + + /* Clear any previous interrupts. + * Note: LP SPI does not have any DMA access but the interrupt bit lives in the DMA interrupt register. + */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + + /* Set data lines */ + lp_spi_dev->spi_user.reg_usr_mosi = 1; + lp_spi_dev->spi_user.reg_usr_miso = 1; + + /* Configure the transaction bit length */ + int rx_bitlen = trans_desc->rx_length * 8; + lp_spi_dev->spi_ms_dlen.reg_ms_data_bitlen = rx_bitlen - 1; + + /* Prepare the data to be received */ + uint32_t rx_idx = 0; + uint32_t rcvd_bitlen = 0; + uint32_t rcvd_length_in_bytes = 0; + + /* The LP SPI slave receives data in the LP_SPI_W0_REG ~ LP_SPI_W15_REG registers. + * The following rules apply: + * 1. The first 64 bytes are received in LP_SPI_W0_REG ~ LP_SPI_W15_REG + * 2. The next 64 bytes are overwritten in LP_SPI_W0_REG ~ LP_SPI_W15_REG + * + * Since the peripheral has no protection against overwriting the data, we restrict the + * driver to receive up to 64 bytes of data at a time. + */ + uint32_t length_in_bytes = trans_desc->rx_length; + if (trans_desc->rx_length > SOC_LP_SPI_MAXIMUM_BUFFER_SIZE) { + /* Truncate the length to the maximum buffer size */ + length_in_bytes = SOC_LP_SPI_MAXIMUM_BUFFER_SIZE; + } + + while (rx_idx < length_in_bytes) { + /* Wait for the transmission to complete */ + ret = lp_core_spi_wait_for_interrupt(ticks_to_wait); + if (ret != ESP_OK) { + return ret; + } + + /* Fetch the received bit length */ + rcvd_bitlen = lp_spi_dev->spi_slave1.reg_slv_data_bitlen > (trans_desc->rx_length * 8) ? (trans_desc->rx_length * 8) : lp_spi_dev->spi_slave1.reg_slv_data_bitlen; + rcvd_length_in_bytes = (rcvd_bitlen + 7) / 8; + + /* Read the received data */ + while (rx_idx < rcvd_length_in_bytes) { + *(uint32_t *)(trans_desc->rx_buffer + rx_idx) = lp_spi_dev->data_buf[(rx_idx / 4)].reg_buf; + rx_idx += 4; + } + + /* Clear the transaction done interrupt */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + } + + /* Prepare data for transmission if a Tx buffer is provided */ + if (trans_desc->tx_buffer != NULL) { + uint32_t tx_idx = 0; + uint32_t length_in_bytes = trans_desc->tx_length; + if (length_in_bytes > SOC_LP_SPI_MAXIMUM_BUFFER_SIZE) { + /* Truncate the length to the maximum buffer size */ + length_in_bytes = SOC_LP_SPI_MAXIMUM_BUFFER_SIZE; + } + + while (tx_idx < length_in_bytes) { + /* Store 4 bytes of data in the data buffer registers serially. */ + lp_spi_dev->data_buf[(tx_idx / 4)].reg_buf = *(uint32_t *)(trans_desc->tx_buffer + tx_idx); + tx_idx += 4; + } + + /* Apply the configuration */ + lp_spi_dev->spi_cmd.reg_update = 1; + while (lp_spi_dev->spi_cmd.reg_update) { + ; + } + + /* Start the transaction */ + lp_spi_dev->spi_cmd.reg_usr = 1; + + /* Wait for the transaction to complete */ + ret = lp_core_spi_wait_for_interrupt(ticks_to_wait); + if (ret != ESP_OK) { + return ret; + } + + /* Clear the transaction done interrupt */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + } + + return ret; +} + +#endif /* SOC_LP_SPI_SUPPORTED */ diff --git a/components/ulp/lp_core/lp_core/lp_core_startup.c b/components/ulp/lp_core/lp_core/lp_core_startup.c new file mode 100644 index 0000000000..eb318b1080 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_startup.c @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 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" + +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(); + + main(); + + ulp_lp_core_memory_shared_cfg_t* shared_mem = ulp_lp_core_memory_shared_cfg_get(); + +#if SOC_LP_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_LP_TIMER_SUPPORTED + + ulp_lp_core_halt(); +} diff --git a/components/ulp/lp_core/lp_core/lp_core_uart.c b/components/ulp/lp_core/lp_core/lp_core_uart.c new file mode 100644 index 0000000000..8161383082 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_uart.c @@ -0,0 +1,236 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 *ticker) +{ + if (timeout > -1) { + /* If the timeout value is not -1, delay for 1 CPU cycle and keep track of ticks */ + ulp_lp_core_delay_cycles(1); + *ticker = *ticker + 1; + if (*ticker >= 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) && !uart_hal_is_tx_idle(&hal)) { + /* 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))) { + 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); + uart_hal_ena_intr_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 to = 0; + + while (remaining_bytes > 0) { + /* Write to the Tx FIFO */ + tx_len = 0; + uart_hal_write_txfifo(&hal, (uint8_t*)((uint32_t*)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_intsts_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 */ + return ESP_FAIL; + } + + /* Check for transaction timeout */ + ret = lp_core_uart_check_timeout(intr_mask, timeout, &to); + if (ret == ESP_ERR_TIMEOUT) { + /* Timeout */ + uart_hal_disable_intr_mask(&hal, intr_mask); + return ret; + } + } + + /* 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, &to); + if (ret == ESP_ERR_TIMEOUT) { + /* Timeout */ + uart_hal_disable_intr_mask(&hal, intr_mask); + return ret; + } + } + } + + /* Disable the Tx done interrupt */ + uart_hal_disable_intr_mask(&hal, intr_mask); + + 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); + uart_hal_ena_intr_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 to = 0; + + while (remaining_bytes > 0) { + /* Read from the Rx FIFO + * We set rx_len to -1 to read all bytes in the Rx FIFO + */ + rx_len = -1; + uart_hal_read_rxfifo(&hal, (uint8_t *)((uint32_t*)buf + bytes_rcvd), &rx_len); + + if (rx_len) { + /* We have some data to read from the Rx FIFO. Check Rx interrupt status */ + intr_status = uart_hal_get_intsts_mask(&hal); + if ((intr_status & UART_INTR_RXFIFO_FULL) || + (intr_status & UART_INTR_RXFIFO_TOUT)) { + /* This is expected. Clear interrupt status and break */ + uart_hal_clr_intsts_mask(&hal, intr_mask); + break; + } else if ((intr_status & UART_INTR_RXFIFO_OVF)) { + /* We reset the Rx FIFO if it overflows */ + uart_hal_clr_intsts_mask(&hal, intr_mask); + uart_hal_rxfifo_rst(&hal); + break; + } else if ((intr_status & LP_UART_ERR_INT_FLAG)) { + /* Transaction error. Abort */ + uart_hal_clr_intsts_mask(&hal, intr_mask); + uart_hal_disable_intr_mask(&hal, intr_mask); + return -1; + } + + /* Update the byte counters */ + bytes_rcvd += rx_len; + remaining_bytes -= rx_len; + } else { + /* We have no data to read from the Rx FIFO. Check for transaction timeout */ + ret = lp_core_uart_check_timeout(intr_mask, timeout, &to); + if (ret == ESP_ERR_TIMEOUT) { + break; + } + } + } + + /* Disable the Rx interrupts */ + uart_hal_disable_intr_mask(&hal, intr_mask); + + /* Return the number of bytes received */ + return bytes_rcvd; +} diff --git a/components/ulp/lp_core/lp_core/lp_core_ubsan.c b/components/ulp/lp_core/lp_core/lp_core_ubsan.c new file mode 100644 index 0000000000..7e15478891 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_ubsan.c @@ -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__); +} diff --git a/components/ulp/lp_core/lp_core/lp_core_utils.c b/components/ulp/lp_core/lp_core/lp_core_utils.c new file mode 100644 index 0000000000..d693b1ae3d --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_utils.c @@ -0,0 +1,175 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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_LP_TIMER_SUPPORTED +#include "hal/lp_timer_ll.h" +#endif + +#include "esp_cpu.h" + +/* LP_FAST_CLK is not very accurate, for now use a rough estimate */ +#if CONFIG_RTC_FAST_CLK_SRC_RC_FAST +#define LP_CORE_CPU_FREQUENCY_HZ 16000000 // For P4 TRM says 20 MHz by default, but we tune it closer to 16 MHz +#elif CONFIG_RTC_FAST_CLK_SRC_XTAL +#if SOC_XTAL_SUPPORT_48M +#define LP_CORE_CPU_FREQUENCY_HZ 48000000 +#else +#define LP_CORE_CPU_FREQUENCY_HZ 40000000 +#endif +#else // Default value in chip without rtc fast clock sel option +#define LP_CORE_CPU_FREQUENCY_HZ 16000000 +#endif + +static uint32_t lp_wakeup_cause = 0; + +void ulp_lp_core_update_wakeup_cause(void) +{ + 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 ((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 ((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_LP_TIMER_SUPPORTED + if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER) \ + && (lp_timer_ll_get_lp_intr_raw(&LP_TIMER) & LP_TIMER_MAIN_TIMER_LP_INT_RAW)) { + lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER; + lp_timer_ll_clear_lp_intsts_mask(&LP_TIMER, LP_TIMER_MAIN_TIMER_LP_INT_CLR); + } +#endif /* SOC_LP_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) +{ + uint32_t start = RV_READ_CSR(mcycle); + uint32_t end = us * (LP_CORE_CPU_FREQUENCY_HZ / 1000000); + + while ((RV_READ_CSR(mcycle) - start) < end) { + /* nothing to do */ + } +} + +/** + * @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) +{ + uint32_t start = RV_READ_CSR(mcycle); + uint32_t end = cycles; + + while ((RV_READ_CSR(mcycle) - start) < end) { + /* nothing to do */ + } +} + +void ulp_lp_core_halt(void) +{ + lp_core_ll_request_sleep(); + + 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); + lp_core_ll_request_sleep(); +} + +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_enable(bool enable) +{ + pmu_ll_lp_enable_sw_intr(&PMU, enable); +} + +void ulp_lp_core_sw_intr_clear(void) +{ + pmu_ll_lp_clear_sw_intr_status(&PMU); +} + +void ulp_lp_core_wait_for_intr(void) +{ + asm volatile("wfi"); +} diff --git a/components/ulp/lp_core/lp_core/port/esp32c5/vector_table.S b/components/ulp/lp_core/lp_core/port/esp32c5/vector_table.S new file mode 100644 index 0000000000..a1309e3cf8 --- /dev/null +++ b/components/ulp/lp_core/lp_core/port/esp32c5/vector_table.S @@ -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 diff --git a/components/ulp/lp_core/lp_core/port/esp32c6/vector_table.S b/components/ulp/lp_core/lp_core/port/esp32c6/vector_table.S new file mode 100644 index 0000000000..a1309e3cf8 --- /dev/null +++ b/components/ulp/lp_core/lp_core/port/esp32c6/vector_table.S @@ -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 diff --git a/components/ulp/lp_core/lp_core/port/esp32p4/vector_table.S b/components/ulp/lp_core/lp_core/port/esp32p4/vector_table.S new file mode 100644 index 0000000000..c5e7ca9fee --- /dev/null +++ b/components/ulp/lp_core/lp_core/port/esp32p4/vector_table.S @@ -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 diff --git a/components/ulp/lp_core/lp_core/start.S b/components/ulp/lp_core/lp_core/start.S new file mode 100644 index 0000000000..b4665c9a3c --- /dev/null +++ b/components/ulp/lp_core/lp_core/start.S @@ -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 diff --git a/components/ulp/lp_core/lp_core/vector.S b/components/ulp/lp_core/lp_core/vector.S new file mode 100644 index 0000000000..b62cbc9ec3 --- /dev/null +++ b/components/ulp/lp_core/lp_core/vector.S @@ -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 diff --git a/components/ulp/lp_core/lp_core_etm.c b/components/ulp/lp_core/lp_core_etm.c new file mode 100644 index 0000000000..16be0fc5da --- /dev/null +++ b/components/ulp/lp_core/lp_core_etm.c @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "sdkconfig.h" +#include "lp_core_etm.h" +#include "esp_private/etm_interface.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "soc/soc_etm_source.h" + +#define LP_CORE_ETM_EVENT_TABLE(event) \ + (uint32_t [LP_CORE_EVENT_MAX]){ \ + [LP_CORE_EVENT_ERR_INTR] = ULP_EVT_ERR_INTR, \ + [LP_CORE_EVENT_START_INTR] = ULP_EVT_START_INTR, \ + }[event] + +#define LP_CORE_ETM_TASK_TABLE(task) \ + (uint32_t [LP_CORE_TASK_MAX]){ \ + [LP_CORE_TASK_WAKEUP_CPU] = ULP_TASK_WAKEUP_CPU, \ + }[task] + +const static char* TAG = "lp-core-etm"; + +static esp_err_t lp_core_del_etm_event(esp_etm_event_t *event) +{ + free(event); + return ESP_OK; +} + +static esp_err_t lp_core_del_etm_task(esp_etm_task_t *task) +{ + free(task); + return ESP_OK; +} + +esp_err_t lp_core_new_etm_task(const lp_core_etm_task_config_t *config, esp_etm_task_handle_t *out_task) +{ + esp_etm_task_t *task = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(config && out_task, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(config->task_type < LP_CORE_TASK_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid task type"); + task = heap_caps_calloc(1, sizeof(esp_etm_task_t), MALLOC_CAP_DEFAULT); + ESP_GOTO_ON_FALSE(task, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM task"); + + uint32_t task_id = LP_CORE_ETM_TASK_TABLE(config->task_type); + ESP_GOTO_ON_FALSE(task_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported task type"); + + // fill the ETM task object + task->task_id = task_id; + task->trig_periph = ETM_TRIG_PERIPH_LP_CORE; + task->del = lp_core_del_etm_task; + ESP_LOGD(TAG, "new task @%p, task_id=%"PRIu32, task, task_id); + *out_task = task; + return ESP_OK; + +err: + if (task) { + lp_core_del_etm_task(task); + } + return ret; +} + +esp_err_t lp_core_new_etm_event(const lp_core_etm_event_config_t *config, esp_etm_event_handle_t *out_event) +{ + esp_etm_event_t *event = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(config && out_event, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(config->event_type < LP_CORE_EVENT_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid event type"); + event = heap_caps_calloc(1, sizeof(esp_etm_event_t), MALLOC_CAP_DEFAULT); + ESP_GOTO_ON_FALSE(event, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM event"); + + uint32_t event_id = LP_CORE_ETM_EVENT_TABLE(config->event_type); + ESP_GOTO_ON_FALSE(event_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported event type"); + + // fill the ETM event object + event->event_id = event_id; + event->trig_periph = ETM_TRIG_PERIPH_LP_CORE; + event->del = lp_core_del_etm_event; + *out_event = event; + return ESP_OK; + +err: + if (event) { + lp_core_del_etm_event(event); + } + return ret; +} diff --git a/components/ulp/lp_core/lp_core_i2c.c b/components/ulp/lp_core/lp_core_i2c.c new file mode 100644 index 0000000000..154af3e38b --- /dev/null +++ b/components/ulp/lp_core/lp_core_i2c.c @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "lp_core_i2c.h" +#include "esp_check.h" +#include "hal/i2c_hal.h" +#include "driver/rtc_io.h" +#include "soc/rtc_io_channel.h" +#include "esp_private/esp_clk_tree_common.h" +#include "esp_private/periph_ctrl.h" + +static const char *LPI2C_TAG = "lp_core_i2c"; + +#if SOC_LP_GPIO_MATRIX_SUPPORTED +#include "driver/lp_io.h" +#include "soc/lp_gpio_sig_map.h" +#endif /* !SOC_LP_GPIO_MATRIX_SUPPORTED */ + +#define LP_I2C_FILTER_CYC_NUM_DEF (7) + +/* I2C LL context */ +i2c_hal_context_t i2c_hal; + +static esp_err_t lp_i2c_gpio_is_cfg_valid(gpio_num_t sda_io_num, gpio_num_t scl_io_num) +{ + /* Verify that the SDA and SCL GPIOs are valid LP IO (RTCIO) pins */ + ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(sda_io_num), LPI2C_TAG, "LP I2C SDA GPIO invalid"); + ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(scl_io_num), LPI2C_TAG, "LP I2C SCL GPIO invalid"); + +#if !SOC_LP_GPIO_MATRIX_SUPPORTED + /* Verify that the SDA and SCL line belong to the LP IO Mux I2C function group */ + if (sda_io_num != RTCIO_GPIO6_CHANNEL) { + ESP_LOGE(LPI2C_TAG, "SDA pin can only be configured as GPIO#6"); + return ESP_ERR_INVALID_ARG; + } + + if (scl_io_num != RTCIO_GPIO7_CHANNEL) { + ESP_LOGE(LPI2C_TAG, "SCL pin can only be configured as GPIO#7"); + return ESP_ERR_INVALID_ARG; + } +#endif /* !SOC_LP_GPIO_MATRIX_SUPPORTED */ + + return ESP_OK; +} + +static esp_err_t lp_i2c_configure_io(gpio_num_t io_num, bool pullup_en) +{ + /* Set the IO pin to high to avoid them from toggling from Low to High state during initialization. This can register a spurious I2C start condition. */ + ESP_RETURN_ON_ERROR(rtc_gpio_set_level(io_num, 1), LPI2C_TAG, "LP GPIO failed to set level to high for %d", io_num); + /* Initialize IO Pin */ + ESP_RETURN_ON_ERROR(rtc_gpio_init(io_num), LPI2C_TAG, "LP GPIO Init failed for GPIO %d", io_num); + /* Set direction to input+output open-drain mode */ + ESP_RETURN_ON_ERROR(rtc_gpio_set_direction(io_num, RTC_GPIO_MODE_INPUT_OUTPUT_OD), LPI2C_TAG, "LP GPIO Set direction failed for %d", io_num); + /* Disable pulldown on the io pin */ + ESP_RETURN_ON_ERROR(rtc_gpio_pulldown_dis(io_num), LPI2C_TAG, "LP GPIO pulldown disable failed for %d", io_num); + /* Enable pullup based on pullup_en flag */ + if (pullup_en) { + ESP_RETURN_ON_ERROR(rtc_gpio_pullup_en(io_num), LPI2C_TAG, "LP GPIO pullup enable failed for %d", io_num); + } else { + ESP_RETURN_ON_ERROR(rtc_gpio_pullup_dis(io_num), LPI2C_TAG, "LP GPIO pullup disable failed for %d", io_num); + } + + return ESP_OK; +} + +static esp_err_t lp_i2c_set_pin(const lp_core_i2c_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + + gpio_num_t sda_io_num = cfg->i2c_pin_cfg.sda_io_num; + gpio_num_t scl_io_num = cfg->i2c_pin_cfg.scl_io_num; + bool sda_pullup_en = cfg->i2c_pin_cfg.sda_pullup_en; + bool scl_pullup_en = cfg->i2c_pin_cfg.scl_pullup_en; + + /* Verify that the LP I2C GPIOs are valid */ + ESP_RETURN_ON_ERROR(lp_i2c_gpio_is_cfg_valid(sda_io_num, scl_io_num), LPI2C_TAG, "LP I2C GPIO config invalid"); + + // NOTE: We always initialize the SCL pin first, then the SDA pin. + // This order of initialization is important to avoid any spurious + // I2C start conditions on the bus. + + /* Initialize SCL Pin */ + ESP_RETURN_ON_ERROR(lp_i2c_configure_io(scl_io_num, scl_pullup_en), LPI2C_TAG, "LP I2C SCL pin config failed"); + + /* Initialize SDA Pin */ + ESP_RETURN_ON_ERROR(lp_i2c_configure_io(sda_io_num, sda_pullup_en), LPI2C_TAG, "LP I2C SDA pin config failed"); + +#if !SOC_LP_GPIO_MATRIX_SUPPORTED + const i2c_signal_conn_t *p_i2c_pin = &i2c_periph_signal[LP_I2C_NUM_0]; + ret = rtc_gpio_iomux_func_sel(sda_io_num, p_i2c_pin->iomux_func); + ret = rtc_gpio_iomux_func_sel(scl_io_num, p_i2c_pin->iomux_func); +#else + /* Connect the SDA pin of the LP_I2C peripheral to the LP_IO Matrix */ + ret = lp_gpio_connect_out_signal(sda_io_num, LP_I2C_SDA_PAD_OUT_IDX, 0, 0); + ret = lp_gpio_connect_in_signal(sda_io_num, LP_I2C_SDA_PAD_IN_IDX, 0); + + /* Connect the SCL pin of the LP_I2C peripheral to the LP_IO Matrix */ + ret = lp_gpio_connect_out_signal(scl_io_num, LP_I2C_SCL_PAD_OUT_IDX, 0, 0); + ret = lp_gpio_connect_in_signal(scl_io_num, LP_I2C_SCL_PAD_IN_IDX, 0); +#endif /* !SOC_LP_GPIO_MATRIX_SUPPORTED */ + + return ret; +} + +static esp_err_t lp_i2c_config_clk(const lp_core_i2c_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + uint32_t source_freq = 0; + soc_periph_lp_i2c_clk_src_t source_clk = LP_I2C_SCLK_DEFAULT; + + /* Check if we have a valid user configured source clock */ + soc_periph_lp_i2c_clk_src_t clk_srcs[] = SOC_LP_I2C_CLKS; + for (int i = 0; i < sizeof(clk_srcs) / sizeof(clk_srcs[0]); i++) { + if (clk_srcs[i] == cfg->i2c_src_clk) { + /* Clock source matches. Override the source clock type with the user configured value */ + source_clk = cfg->i2c_src_clk; + break; + } + } + + /* Fetch the clock source frequency */ + ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(source_clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &source_freq), LPI2C_TAG, "Invalid LP I2C source clock selected"); + + /* Verify that the I2C_SCLK operates at a frequency 20 times larger than the requested SCL bus frequency */ + ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.clk_speed_hz * 20 <= source_freq, ESP_ERR_INVALID_ARG, LPI2C_TAG, "I2C_SCLK frequency (%"PRId32") should operate at a frequency at least 20 times larger than the I2C SCL bus frequency (%"PRId32")", source_freq, cfg->i2c_timing_cfg.clk_speed_hz); + + /* LP I2C clock source is mixed with other peripherals in the same register */ + PERIPH_RCC_ATOMIC() { + lp_i2c_ll_set_source_clk(i2c_hal.dev, source_clk); + + /* Configure LP I2C timing parameters. source_clk is ignored for LP_I2C in this call */ + i2c_hal_set_bus_timing(&i2c_hal, cfg->i2c_timing_cfg.clk_speed_hz, (i2c_clock_source_t)source_clk, source_freq); + } + + return ret; +} + +esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg) +{ + /* Verify LP_I2C port number */ + ESP_RETURN_ON_FALSE((lp_i2c_num == LP_I2C_NUM_0), ESP_ERR_INVALID_ARG, LPI2C_TAG, "LP I2C port number incorrect"); + + /* Verify that the input cfg param is valid */ + ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, LPI2C_TAG, "LP I2C configuration is NULL"); + + /* Configure LP I2C GPIOs */ + ESP_RETURN_ON_ERROR(lp_i2c_set_pin(cfg), LPI2C_TAG, "Failed to configure LP I2C GPIOs"); + + PERIPH_RCC_ATOMIC() { + /* Enable LP I2C bus clock */ + lp_i2c_ll_enable_bus_clock(lp_i2c_num - LP_I2C_NUM_0, true); + + /* Reset LP I2C register */ + lp_i2c_ll_reset_register(lp_i2c_num - LP_I2C_NUM_0); + + /* Initialize LP I2C HAL */ + i2c_hal_init(&i2c_hal, lp_i2c_num); + } + + /* Clear any pending interrupts */ + i2c_ll_clear_intr_mask(i2c_hal.dev, UINT32_MAX); + + /* Initialize LP I2C Master mode */ + i2c_hal_master_init(&i2c_hal); + + /* Enable internal open-drain mode for I2C IO lines */ + i2c_hal.dev->ctr.sda_force_out = 0; + i2c_hal.dev->ctr.scl_force_out = 0; + + /* Configure LP I2C clock and timing parameters */ + ESP_RETURN_ON_ERROR(lp_i2c_config_clk(cfg), LPI2C_TAG, "Failed to configure LP I2C source clock"); + + /* Enable SDA and SCL filtering. This configuration matches the HP I2C filter config */ + i2c_ll_master_set_filter(i2c_hal.dev, LP_I2C_FILTER_CYC_NUM_DEF); + + /* Configure the I2C master to send a NACK when the Rx FIFO count is full */ + i2c_ll_master_rx_full_ack_level(i2c_hal.dev, 1); + + /* Synchronize the config register values to the LP I2C peripheral clock */ + i2c_ll_update(i2c_hal.dev); + + return ESP_OK; +} diff --git a/components/ulp/lp_core/lp_core_spi.c b/components/ulp/lp_core/lp_core_spi.c new file mode 100644 index 0000000000..0def39cc01 --- /dev/null +++ b/components/ulp/lp_core/lp_core_spi.c @@ -0,0 +1,306 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "lp_core_spi.h" +#include "driver/rtc_io.h" +#include "driver/lp_io.h" +#include "hal/rtc_io_types.h" +#include "include/lp_core_spi.h" +#include "soc/lp_spi_struct.h" +#include "soc/lp_gpio_sig_map.h" +#include "soc/lpperi_struct.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/esp_clk_tree_common.h" +#include "hal/spi_ll.h" + +static const char *LP_SPI_TAG = "lp_spi"; + +/* Use the LP SPI register structure to access peripheral registers */ +lp_spi_dev_t *lp_spi_dev = &LP_SPI; + +static esp_err_t lp_spi_config_io(gpio_num_t pin, rtc_gpio_mode_t direction, uint32_t out_pad_idx, uint32_t in_pad_idx) +{ + esp_err_t ret = ESP_OK; + + /* If pin is -1, then it is not connected to any LP_IO */ + if (pin == -1) { + return ESP_OK; + } + + /* Initialize LP_IO */ + ESP_RETURN_ON_ERROR(rtc_gpio_init(pin), LP_SPI_TAG, "LP IO Init failed for GPIO %d", pin); + + /* Set LP_IO direction */ + ESP_RETURN_ON_ERROR(rtc_gpio_set_direction(pin, direction), LP_SPI_TAG, "LP IO Set direction failed for %d", pin); + + /* Connect the LP SPI signals to the LP_IO Matrix */ + ESP_RETURN_ON_ERROR(lp_gpio_connect_out_signal(pin, out_pad_idx, 0, 0), LP_SPI_TAG, "LP IO Matrix connect out signal failed for %d", pin); + ESP_RETURN_ON_ERROR(lp_gpio_connect_in_signal(pin, in_pad_idx, 0), LP_SPI_TAG, "LP IO Matrix connect in signal failed for %d", pin); + + return ret; +} + +static esp_err_t lp_spi_bus_init_io(const lp_spi_bus_config_t *bus_config) +{ + esp_err_t ret = ESP_OK; + + /* Argument sanity check */ +#if SOC_LP_GPIO_MATRIX_SUPPORTED + /* LP SPI signals can be routed to any LP_IO */ + ESP_RETURN_ON_FALSE((rtc_gpio_is_valid_gpio(bus_config->mosi_io_num)), ESP_FAIL, LP_SPI_TAG, "mosi_io_num error"); + ESP_RETURN_ON_FALSE((bus_config->miso_io_num == -1) || (rtc_gpio_is_valid_gpio(bus_config->miso_io_num)), ESP_FAIL, LP_SPI_TAG, "miso_io_num error"); + ESP_RETURN_ON_FALSE((rtc_gpio_is_valid_gpio(bus_config->sclk_io_num)), ESP_FAIL, LP_SPI_TAG, "sclk_io_num error"); + + /* Configure miso pin*/ + ret = lp_spi_config_io(bus_config->miso_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_Q_PAD_OUT_IDX, LP_SPI_Q_PAD_IN_IDX); + /* Configure mosi pin */ + ret = lp_spi_config_io(bus_config->mosi_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_D_PAD_OUT_IDX, LP_SPI_D_PAD_IN_IDX); + /* Configure sclk pin */ + ret = lp_spi_config_io(bus_config->sclk_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_CK_PAD_OUT_IDX, LP_SPI_CK_PAD_IN_IDX); +#else +#error "LP SPI bus initialization is not supported without LP GPIO Matrix." +#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */ + + return ret; +} + +static esp_err_t lp_spi_cs_pin_init(int cs_io_num) +{ + esp_err_t ret = ESP_OK; + +#if SOC_LP_GPIO_MATRIX_SUPPORTED + /* CS signal can be routed to any LP_IO */ + ESP_RETURN_ON_FALSE((rtc_gpio_is_valid_gpio(cs_io_num)), ESP_FAIL, LP_SPI_TAG, "cs_io_num error"); + + /* Configure CS pin */ + ret = lp_spi_config_io(cs_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_CS_PAD_OUT_IDX, LP_SPI_CS_PAD_IN_IDX); +#else +#error "LP SPI device Chip Select (CS) initialization is not supported without LP GPIO Matrix." +#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */ + + return ret; +} + +static void lp_spi_enable_clock_gate(void) +{ + lpperi_dev_t *lp_peri_dev = &LPPERI; + PERIPH_RCC_ATOMIC() { + (void)__DECLARE_RCC_ATOMIC_ENV; // Avoid warnings for unused variable __DECLARE_RCC_ATOMIC_ENV + lp_peri_dev->clk_en.ck_en_lp_spi = 1; + } +} + +static esp_err_t lp_spi_clock_init(const lp_spi_device_config_t *dev_config) +{ + esp_err_t ret = ESP_OK; + + /* Max requested clock frequency cannot be more than the LP_FAST_CLK frequency */ + uint32_t max_clock_source_hz = esp_clk_tree_lp_fast_get_freq_hz(ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX); + ESP_RETURN_ON_FALSE(dev_config->clock_speed_hz <= max_clock_source_hz, ESP_ERR_INVALID_ARG, LP_SPI_TAG, "Invalid clock speed for SPI device. Max allowed = %ld Hz", max_clock_source_hz); + + /* Set the duty cycle. If not specified, use 50% */ + int duty_cycle = dev_config->duty_cycle ? dev_config->duty_cycle : 128; + + /* Calculate the clock pre-div values. We use the HP SPI LL function here for the calculation. */ + spi_ll_clock_val_t spi_clock; + spi_ll_master_cal_clock(max_clock_source_hz, dev_config->clock_speed_hz, duty_cycle, &spi_clock); + lp_spi_dev->spi_clock.val = spi_clock; + + return ret; +} + +static void lp_spi_master_init(void) +{ + /* Initialize the LP SPI in master mode. + * (We do not have a HAL/LL layer for LP SPI, yet, so let's use the LP SPI registers directly). + */ + + /* Clear Slave mode to enable Master mode */ + lp_spi_dev->spi_slave.reg_slave_mode = 0; + lp_spi_dev->spi_slave.reg_clk_mode = 0; + + /* Reset CS timing */ + lp_spi_dev->spi_user1.reg_cs_setup_time = 0; + lp_spi_dev->spi_user1.reg_cs_hold_time = 0; + + /* Use all 64 bytes of the Tx/Rx buffers in CPU controlled transfer */ + lp_spi_dev->spi_user.reg_usr_mosi_highpart = 0; + lp_spi_dev->spi_user.reg_usr_miso_highpart = 0; +} + +static void lp_spi_slave_init(void) +{ + /* Set Slave mode */ + lp_spi_dev->spi_slave.reg_slave_mode = 1; + + /* Reset the SPI peripheral */ + lp_spi_dev->spi_slave.reg_soft_reset = 1; + lp_spi_dev->spi_slave.reg_soft_reset = 0; + + /* Configure slave */ + lp_spi_dev->spi_clock.val = 0; + lp_spi_dev->spi_user.val = 0; + lp_spi_dev->spi_ctrl.val = 0; + lp_spi_dev->spi_user.reg_doutdin = 1; //we only support full duplex + lp_spi_dev->spi_user.reg_sio = 0; + + /* Use all 64 bytes of the Tx/Rx buffers in CPU controlled transfer */ + lp_spi_dev->spi_user.reg_usr_miso_highpart = 0; + lp_spi_dev->spi_user.reg_usr_mosi_highpart = 0; +} + +static void lp_spi_master_setup_device(const lp_spi_device_config_t *dev_config) +{ + /* Configure transmission bit order */ + lp_spi_dev->spi_ctrl.reg_rd_bit_order = dev_config->flags & LP_SPI_DEVICE_RXBIT_LSBFIRST ? 1 : 0; + lp_spi_dev->spi_ctrl.reg_wr_bit_order = dev_config->flags & LP_SPI_DEVICE_TXBIT_LSBFIRST ? 1 : 0; + + /* Configure SPI mode in master mode */ + if (dev_config->spi_mode == 0) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 0; + lp_spi_dev->spi_user.reg_ck_out_edge = 0; + } else if (dev_config->spi_mode == 1) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 0; + lp_spi_dev->spi_user.reg_ck_out_edge = 1; + } else if (dev_config->spi_mode == 2) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 1; + lp_spi_dev->spi_user.reg_ck_out_edge = 1; + } else if (dev_config->spi_mode == 3) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 1; + lp_spi_dev->spi_user.reg_ck_out_edge = 0; + } + + /* Configure the polarity of the CS line */ + lp_spi_dev->spi_misc.reg_master_cs_pol = dev_config->flags & LP_SPI_DEVICE_CS_ACTIVE_HIGH ? 1 : 0; + + /* Configure half-duplex (0) or full-duplex (1) mode for LP SPI master */ + lp_spi_dev->spi_user.reg_doutdin = dev_config->flags & LP_SPI_DEVICE_HALF_DUPLEX ? 0 : 1; + + /* Configure 3-Wire half-duplex mode */ + lp_spi_dev->spi_user.reg_sio = dev_config->flags & LP_SPI_DEVICE_3WIRE ? 1 : 0; + + /* Configure CS setup and hold times */ + lp_spi_dev->spi_user1.reg_cs_setup_time = dev_config->cs_ena_pretrans == 0 ? 0 : dev_config->cs_ena_pretrans - 1; + lp_spi_dev->spi_user.reg_cs_setup = dev_config->cs_ena_pretrans ? 1 : 0; + lp_spi_dev->spi_user1.reg_cs_hold_time = dev_config->cs_ena_posttrans; + lp_spi_dev->spi_user.reg_cs_hold = dev_config->cs_ena_posttrans ? 1 : 0; + + /* Select the CS pin */ + lp_spi_dev->spi_misc.reg_cs0_dis = 0; +} + +static void lp_spi_slave_setup_device(const lp_spi_slave_config_t *slave_config) +{ + /* Configure transmission bit order */ + lp_spi_dev->spi_ctrl.reg_rd_bit_order = slave_config->flags & LP_SPI_DEVICE_RXBIT_LSBFIRST ? 1 : 0; + lp_spi_dev->spi_ctrl.reg_wr_bit_order = slave_config->flags & LP_SPI_DEVICE_TXBIT_LSBFIRST ? 1 : 0; + + /* Configure SPI mode in slave mode */ + if (slave_config->spi_mode == 0) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 0; + lp_spi_dev->spi_user.reg_rsck_i_edge = 0; + lp_spi_dev->spi_user.reg_tsck_i_edge = 0; + lp_spi_dev->spi_slave.reg_clk_mode_13 = 0; + } else if (slave_config->spi_mode == 1) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 0; + lp_spi_dev->spi_user.reg_rsck_i_edge = 1; + lp_spi_dev->spi_user.reg_tsck_i_edge = 1; + lp_spi_dev->spi_slave.reg_clk_mode_13 = 1; + } else if (slave_config->spi_mode == 2) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 1; + lp_spi_dev->spi_user.reg_rsck_i_edge = 1; + lp_spi_dev->spi_user.reg_tsck_i_edge = 1; + lp_spi_dev->spi_slave.reg_clk_mode_13 = 0; + } else if (slave_config->spi_mode == 3) { + lp_spi_dev->spi_misc.reg_ck_idle_edge = 1; + lp_spi_dev->spi_user.reg_rsck_i_edge = 0; + lp_spi_dev->spi_user.reg_tsck_i_edge = 0; + lp_spi_dev->spi_slave.reg_clk_mode_13 = 1; + } + + if (slave_config->flags & LP_SPI_DEVICE_CS_ACTIVE_HIGH) { + ESP_LOGW(LP_SPI_TAG, "Active high CS line is not supported in slave mode. Using active low CS line."); + } + lp_spi_dev->spi_misc.reg_slave_cs_pol = 0; + + if (slave_config->flags & LP_SPI_DEVICE_HALF_DUPLEX) { + ESP_LOGW(LP_SPI_TAG, "Half-duplex mode is not supported in slave mode. Using full-duplex mode."); + } + lp_spi_dev->spi_user.reg_doutdin = 1; + + /* Configure 3-Wire half-duplex mode */ + lp_spi_dev->spi_user.reg_sio = slave_config->flags & LP_SPI_DEVICE_3WIRE ? 1 : 0; + + /* Select the CS pin */ + lp_spi_dev->spi_misc.reg_cs0_dis = 0; +} + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////// Public APIs /////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +esp_err_t lp_core_lp_spi_bus_initialize(lp_spi_host_t host_id, const lp_spi_bus_config_t *bus_config) +{ + (void)host_id; + + /* Sanity check arguments */ + if (bus_config == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Connect the LP SPI peripheral to a "bus", i.e. a set of + * GPIO pins defined in the bus_config structure. + */ + esp_err_t ret = lp_spi_bus_init_io(bus_config); + + return ret; +} + +esp_err_t lp_core_lp_spi_bus_add_device(lp_spi_host_t host_id, const lp_spi_device_config_t *dev_config) +{ + (void)host_id; + + esp_err_t ret = ESP_OK; + + /* Configure the CS pin */ + ESP_RETURN_ON_ERROR(lp_spi_cs_pin_init(dev_config->cs_io_num), LP_SPI_TAG, "CS pin initialization failed"); + + /* Enable the LP SPI clock gate */ + lp_spi_enable_clock_gate(); + + /* Lazy initialize the LP SPI in master mode */ + lp_spi_master_init(); + + /* Configure clock */ + ESP_RETURN_ON_ERROR(lp_spi_clock_init(dev_config), LP_SPI_TAG, "Clock initialization failed"); + + /* Setup the SPI device */ + lp_spi_master_setup_device(dev_config); + + return ret; +} + +esp_err_t lp_core_lp_spi_slave_initialize(lp_spi_host_t host_id, const lp_spi_slave_config_t *slave_config) +{ + (void)host_id; + + esp_err_t ret = ESP_OK; + + /* Configure the CS pin */ + ESP_RETURN_ON_ERROR(lp_spi_cs_pin_init(slave_config->cs_io_num), LP_SPI_TAG, "CS pin initialization failed"); + + /* Enable the LP SPI clock gate */ + lp_spi_enable_clock_gate(); + + /* Initialize the LP SPI in slave mode */ + lp_spi_slave_init(); + + /* Setup the SPI device */ + lp_spi_slave_setup_device(slave_config); + + return ret; +} diff --git a/components/ulp/lp_core/lp_core_uart.c b/components/ulp/lp_core/lp_core_uart.c new file mode 100644 index 0000000000..b3541fa42f --- /dev/null +++ b/components/ulp/lp_core/lp_core_uart.c @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_err.h" +#include "esp_check.h" +#include "lp_core_uart.h" +#include "driver/rtc_io.h" +#include "driver/lp_io.h" +#include "soc/uart_periph.h" +#include "soc/lp_uart_struct.h" +#include "hal/uart_hal.h" +#include "hal/rtc_io_hal.h" +#include "hal/rtc_io_types.h" +#include "esp_clk_tree.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/uart_share_hw_ctrl.h" + +static const char *LP_UART_TAG = "lp_uart"; + +#define LP_UART_PORT_NUM LP_UART_NUM_0 +#define LP_UART_TX_IDLE_NUM_DEFAULT (0U) + +/* 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_param_config(const lp_core_uart_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + + /* Argument sanity check */ + if ((cfg->uart_proto_cfg.rx_flow_ctrl_thresh > SOC_LP_UART_FIFO_LEN) || + (cfg->uart_proto_cfg.flow_ctrl > UART_HW_FLOWCTRL_MAX) || + (cfg->uart_proto_cfg.data_bits > UART_DATA_BITS_MAX)) { + // Invalid config + return ESP_ERR_INVALID_ARG; + } + + /* Get LP UART source clock frequency */ + uint32_t sclk_freq = 0; + soc_periph_lp_uart_clk_src_t clk_src = cfg->lp_uart_source_clk ? cfg->lp_uart_source_clk : LP_UART_SCLK_DEFAULT; + ret = esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &sclk_freq); + if (ret != ESP_OK) { + // Unable to fetch LP UART source clock frequency + return ESP_FAIL; + } + + // LP UART clock source is mixed with other peripherals in the same register + LP_UART_SRC_CLK_ATOMIC() { + (void)__DECLARE_RCC_ATOMIC_ENV; // Avoid warnings for unused variable __DECLARE_RCC_ATOMIC_ENV + + /* Enable LP UART bus clock */ + lp_uart_ll_enable_bus_clock(0, true); + lp_uart_ll_set_source_clk(hal.dev, clk_src); + lp_uart_ll_sclk_enable(0); + } + + /* Initialize LP UART HAL with default parameters */ + uart_hal_init(&hal, LP_UART_PORT_NUM); + + /* Override protocol parameters from the configuration */ + lp_uart_ll_set_baudrate(hal.dev, cfg->uart_proto_cfg.baud_rate, sclk_freq); + uart_hal_set_parity(&hal, cfg->uart_proto_cfg.parity); + uart_hal_set_data_bit_num(&hal, cfg->uart_proto_cfg.data_bits); + uart_hal_set_stop_bits(&hal, cfg->uart_proto_cfg.stop_bits); + uart_hal_set_tx_idle_num(&hal, LP_UART_TX_IDLE_NUM_DEFAULT); + uart_hal_set_hw_flow_ctrl(&hal, cfg->uart_proto_cfg.flow_ctrl, cfg->uart_proto_cfg.rx_flow_ctrl_thresh); + + /* Reset Tx/Rx FIFOs */ + uart_hal_rxfifo_rst(&hal); + uart_hal_txfifo_rst(&hal); + + return ret; +} + +static esp_err_t lp_uart_config_io(gpio_num_t pin, rtc_gpio_mode_t direction, uint32_t idx) +{ + /* Skip configuration if the LP_IO is -1 */ + if (pin < 0) { + return ESP_OK; + } + + /* Initialize LP_IO */ + esp_err_t ret = rtc_gpio_init(pin); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + /* Set LP_IO direction */ + ret = rtc_gpio_set_direction(pin, direction); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + /* Connect pins */ + const uart_periph_sig_t *upin = &uart_periph_signal[LP_UART_PORT_NUM].pins[idx]; +#if !SOC_LP_GPIO_MATRIX_SUPPORTED + /* When LP_IO Matrix is not support, LP_IO Mux must be connected to the pins */ + ret = rtc_gpio_iomux_func_sel(pin, upin->iomux_func); +#else + /* If the configured pin is the default LP_IO Mux pin for LP UART, then set the LP_IO MUX function */ + if (upin->default_gpio == pin) { + ret = rtc_gpio_iomux_func_sel(pin, upin->iomux_func); + } else { + /* Select FUNC1 for LP_IO Matrix */ + ret = rtc_gpio_iomux_func_sel(pin, 1); + /* Connect the LP_IO to the LP UART peripheral signal */ + if (direction == RTC_GPIO_MODE_OUTPUT_ONLY) { + ret = lp_gpio_connect_out_signal(pin, UART_PERIPH_SIGNAL(LP_UART_PORT_NUM, idx), 0, 0); + } else { + ret = lp_gpio_connect_in_signal(pin, UART_PERIPH_SIGNAL(LP_UART_PORT_NUM, idx), 0); + } + } +#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */ + + return ret; +} + +static esp_err_t lp_core_uart_set_pin(const lp_core_uart_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + + /* Argument sanity check */ +#if !SOC_LP_GPIO_MATRIX_SUPPORTED + const uart_periph_sig_t *pins = uart_periph_signal[LP_UART_PORT_NUM].pins; + // LP_UART has its fixed IOs + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.tx_io_num < 0 || (cfg->uart_pin_cfg.tx_io_num == pins[SOC_UART_TX_PIN_IDX].default_gpio)), ESP_FAIL, LP_UART_TAG, "tx_io_num error"); + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.rx_io_num < 0 || (cfg->uart_pin_cfg.rx_io_num == pins[SOC_UART_RX_PIN_IDX].default_gpio)), ESP_FAIL, LP_UART_TAG, "rx_io_num error"); + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.rts_io_num < 0 || (cfg->uart_pin_cfg.rts_io_num == pins[SOC_UART_RTS_PIN_IDX].default_gpio)), ESP_FAIL, LP_UART_TAG, "rts_io_num error"); + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.cts_io_num < 0 || (cfg->uart_pin_cfg.cts_io_num == pins[SOC_UART_CTS_PIN_IDX].default_gpio)), ESP_FAIL, LP_UART_TAG, "cts_io_num error"); +#else + // LP_UART signals can be routed to any LP_IOs + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.tx_io_num < 0 || rtc_gpio_is_valid_gpio(cfg->uart_pin_cfg.tx_io_num)), ESP_FAIL, LP_UART_TAG, "tx_io_num error"); + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.rx_io_num < 0 || rtc_gpio_is_valid_gpio(cfg->uart_pin_cfg.rx_io_num)), ESP_FAIL, LP_UART_TAG, "rx_io_num error"); + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.rts_io_num < 0 || rtc_gpio_is_valid_gpio(cfg->uart_pin_cfg.rts_io_num)), ESP_FAIL, LP_UART_TAG, "rts_io_num error"); + ESP_RETURN_ON_FALSE((cfg->uart_pin_cfg.cts_io_num < 0 || rtc_gpio_is_valid_gpio(cfg->uart_pin_cfg.cts_io_num)), ESP_FAIL, LP_UART_TAG, "cts_io_num error"); +#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */ + + /* Configure Tx Pin */ + ret = lp_uart_config_io(cfg->uart_pin_cfg.tx_io_num, RTC_GPIO_MODE_OUTPUT_ONLY, SOC_UART_TX_PIN_IDX); + /* Configure Rx Pin */ + ret = lp_uart_config_io(cfg->uart_pin_cfg.rx_io_num, RTC_GPIO_MODE_INPUT_ONLY, SOC_UART_RX_PIN_IDX); + /* Configure RTS Pin */ + ret = lp_uart_config_io(cfg->uart_pin_cfg.rts_io_num, RTC_GPIO_MODE_OUTPUT_ONLY, SOC_UART_RTS_PIN_IDX); + /* Configure CTS Pin */ + ret = lp_uart_config_io(cfg->uart_pin_cfg.cts_io_num, RTC_GPIO_MODE_INPUT_ONLY, SOC_UART_CTS_PIN_IDX); + + return ret; +} + +esp_err_t lp_core_uart_init(const lp_core_uart_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + + /* Argument sanity check */ + if (!cfg) { + // NULL configuration + return ESP_ERR_INVALID_ARG; + } + + /* Configure LP UART protocol parameters */ + ret = lp_core_uart_param_config(cfg); + if (ret != ESP_OK) { + // Invalid protocol configuration + return ESP_FAIL; + } + + /* Configure LP UART IO pins */ + ret = lp_core_uart_set_pin(cfg); + if (ret != ESP_OK) { + // Invalid IO configuration + return ESP_FAIL; + } + + return ret; +} diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_critical_section_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_critical_section_shared.h new file mode 100644 index 0000000000..e946ecf906 --- /dev/null +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_critical_section_shared.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LOCK_CANDIDATE_LP_CORE = 0, + LOCK_CANDIDATE_HP_CORE_0, +#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1 + LOCK_CANDIDATE_HP_CORE_1, +#endif + LOCK_CANDIDATE_NUM_MAX, +} ulp_lp_core_spinlock_candidate_t; + +typedef struct { + volatile int level[LOCK_CANDIDATE_NUM_MAX]; + volatile int last_to_enter[LOCK_CANDIDATE_NUM_MAX - 1]; + volatile unsigned int prev_int_level; +} ulp_lp_core_spinlock_t; + +/** + * @brief Initialize the spinlock that protects shared resources between main CPU and LP CPU. + * + * @note The spinlock can be initialized in either main program or LP program. + * + * @param lock Pointer to lock struct + */ +void ulp_lp_core_spinlock_init(ulp_lp_core_spinlock_t *lock); + +/** + * @brief Enter the critical section that protects shared resources between main CPU and LP CPU. + * + * @note This critical section is designed for being used by multiple threads, it is safe to try to enter it + * simultaneously from multiple threads on multiple main CPU cores and LP CPU. + * + * @note This critical section does not support nesting entering and exiting. + * + * @param lock Pointer to lock struct + */ +void ulp_lp_core_enter_critical(ulp_lp_core_spinlock_t *lock); + +/** + * @brief Exit the critical section that protect shared resource between main CPU and LP CPU. + * + * @note This critical section does not support nesting entering and exiting. + * + * @param lock Pointer to lock struct + */ +void ulp_lp_core_exit_critical(ulp_lp_core_spinlock_t *lock); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h new file mode 100644 index 0000000000..310caa0b1a --- /dev/null +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" +#include "hal/adc_types.h" +#include "esp_adc/adc_oneshot.h" + +/** + * @brief LP ADC channel configurations + */ +typedef adc_oneshot_chan_cfg_t lp_core_lp_adc_chan_cfg_t; + +/** + * @brief Initialize the LP ADC + * + * @note We only support LP ADC1 and not LP ADC2 due to a HW issue. + * + * @param unit_id LP ADC unit to initialize + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_ERR_NOT_SUPPORTED if the API is not supported on the LP Core + * ESP_FAIL if the ADC unit failed to initialize + */ +esp_err_t lp_core_lp_adc_init(adc_unit_t unit_id); + +/** + * @brief Deinitialize the LP ADC + * + * @param unit_id LP ADC unit to deinitialize + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_ERR_NOT_SUPPORTED if the API is not supported on the LP Core + * ESP_FAIL if the ADC unit failed to deinitialize + */ +esp_err_t lp_core_lp_adc_deinit(adc_unit_t unit_id); + +/** + * @brief Configure an LP ADC channel + * + * @param unit_id ADC unit to configure the channel for + * @param channel ADC channel to configure + * @param chan_config Configuration for the channel + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_ERR_NOT_SUPPORTED if the API is not supported on the LP Core + * ESP_FAIL if the channel configuration fails + */ +esp_err_t lp_core_lp_adc_config_channel(adc_unit_t unit_id, adc_channel_t channel, const lp_core_lp_adc_chan_cfg_t *chan_config); + +/** + * @brief Read the raw ADC value from a channel + * + * @note The raw value is the 12-bit value read from the ADC. + * The value is between 0 and 4095. To convert this value + * to a voltage, use the formula: + * voltage = (raw_value * (1.1v / 4095)) * attenuation + * + * Alternatively, use lp_core_lp_adc_read_channel_converted() + * to get the converted value. + * + * @param[in] unit_id ADC unit to configure the channel for + * @param[in] channel ADC channel to configure + * @param[in] adc_raw Pointer to store the raw 12-bit ADC value + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid + * ESP_FAIL if the read fails + */ +esp_err_t lp_core_lp_adc_read_channel_raw(adc_unit_t unit_id, adc_channel_t channel, int *adc_raw); + +/** + * @brief Read the converted ADC value in millivolts from a channel + * + * @note The API converts the measured voltage based on the + * internal reference voltage of 1.1v and the the attenuation + * factors. It uses the formula: + * voltage = (raw_value * (1.1v / 4095)) * attenuation + * + * To avoid complex floating-point operations at runtime, + * the API converts the raw data to millivolts. Also, the + * conversion approximates the calculation when scaling + * the voltage by using pre-computed attenuation factors. + * + * @note The conversion approximates the measured voltage based on the + * internal reference voltage of 1.1v and the approximations of + * the attenuation factors. + * + * @param[in] unit_id ADC unit to configure the channel for + * @param[in] channel ADC channel to configure + * @param[out] voltage_mv Pointer to store the converted ADC value in millivolts + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the unit_id is invalid or voltage_mv is NULL + * ESP_FAIL if the read fails + */ +esp_err_t lp_core_lp_adc_read_channel_converted(adc_unit_t unit_id, adc_channel_t channel, int *voltage_mv); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_timer_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_timer_shared.h new file mode 100644 index 0000000000..6743ebd603 --- /dev/null +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_timer_shared.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the number of cycle count for the LP timer + * + * @return current timer cycle count + */ +uint64_t ulp_lp_core_lp_timer_get_cycle_count(void); + +/** + * @brief Sets the next wakeup alarm + * + * @note This only sets the alarm for a single wakeup. For periodic wakeups you will + * have to call this function again after each wakeup to configure the next time. + * + * @note If ulp_lp_core_cfg_t.lp_timer_sleep_duration_us is set the ulp will automatically set + * the next wakeup time after returning from main and override this value. + * + * @param sleep_duration_us Time to next wakeup in microseconds + */ +void ulp_lp_core_lp_timer_set_wakeup_time(uint64_t sleep_duration_us); + +/** + * @brief Set the next wakeup alarm in LP timer ticks + * + * @note This only sets the alarm for a single wakeup. For periodic wakeups you will + * have to call this function again after each wakeup to configure the next time. + * + * @note If ulp_lp_core_cfg_t.lp_timer_sleep_duration_us is set the ulp will automatically set + * the next wakeup time after returning from main and override this value. + * + * @param sleep_duration_ticks + */ +void ulp_lp_core_lp_timer_set_wakeup_ticks(uint64_t sleep_duration_ticks); + +/** + * @brief Converts from sleep duration in microseconds to LP timer ticks + * + * @param sleep_duration_us Sleep duration in microseconds + * @return uint64_t Number of LP timer ticks to sleep for + */ +uint64_t ulp_lp_core_lp_timer_calculate_sleep_ticks(uint64_t sleep_duration_us); + +/** + * @brief Disables the lp timer alarm and clears any pending alarm interrupts + * + */ +void ulp_lp_core_lp_timer_disable(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_vad_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_vad_shared.h new file mode 100644 index 0000000000..979398eab6 --- /dev/null +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_vad_shared.h @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/lp_i2s_vad.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LP VAD configurations + */ +typedef lp_vad_init_config_t lp_core_lp_vad_cfg_t; + +/** + * @brief State Machine + ┌──────────────────────────────────┐ + │ │ + ┌─────────────┤ speak-activity-listening-state │ ◄───────────────┐ + │ │ │ │ + │ └──────────────────────────────────┘ │ + │ ▲ │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ +detected speak activity │ │ detected speak activity │ detected speak activity + >= │ │ >= │ >= +'speak_activity_thresh' │ │ 'min_speak_activity_thresh' │ 'max_speak_activity_thresh' + │ │ │ + │ │ && │ + │ │ │ + │ │ detected non-speak activity │ + │ │ < │ + │ │ 'non_speak_activity_thresh' │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ + │ ┌───────────┴─────────────────────┐ │ + │ │ │ │ + └───────────► │ speak-activity-detected-state ├─────────────────┘ + │ │ + └─┬───────────────────────────────┘ + │ + │ ▲ + │ │ + │ │ + │ │ detected speak activity + │ │ >= + │ │ 'min_speak_activity_thresh' + │ │ + │ │ && + │ │ + │ │ detected non-speak activity + │ │ < + └─────────────────────┘ 'non_speak_activity_thresh' +*/ + +/** + * @brief LP VAD init + * + * @param[in] vad_id VAD ID + * @param[in] init_config Initial configurations + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_init(lp_vad_t vad_id, const lp_core_lp_vad_cfg_t *init_config); + +/** + * @brief Enable LP VAD + * + * @param[in] vad_id VAD ID + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_enable(lp_vad_t vad_id); + +/** + * @brief Disable LP VAD + * + * @param[in] vad_id VAD ID + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_disable(lp_vad_t vad_id); + +/** + * @brief Deinit LP VAD + * + * @param[in] vad_id VAD ID + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_deinit(lp_vad_t vad_id); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_memory_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_memory_shared.h new file mode 100644 index 0000000000..890b1ddca9 --- /dev/null +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_memory_shared.h @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint64_t sleep_duration_us; /* Configured sleep duration for periodic wakeup, if set the ulp will automatically schedule the next wakeup */ + uint64_t sleep_duration_ticks; /* Configured sleep duration, in LP-timer clock ticks, if set it allows us to skip doing integer division when configuring the timer */ +} ulp_lp_core_memory_shared_cfg_t; + +/** + * @brief Returns a pointer to a struct shared between the main cpu and lp core, + * intended for sharing variables between the ulp component and ulp binary + * + * @return ulp_lp_core_memory_shared_cfg_t* Pointer to the shared config struct + */ +ulp_lp_core_memory_shared_cfg_t* ulp_lp_core_memory_shared_cfg_get(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c b/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c new file mode 100644 index 0000000000..fa0816996a --- /dev/null +++ b/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ulp_lp_core_critical_section_shared.h" +#include "esp_cpu.h" +#if IS_ULP_COCPU +#include "ulp_lp_core_interrupts.h" +#endif + +/* Masked interrupt threshold varies between different types of interrupt controller */ +#if !SOC_INT_CLIC_SUPPORTED +#define INT_MASK_THRESHOLD RVHAL_EXCM_LEVEL +#else /* SOC_INT_CLIC_SUPPORTED */ +#define INT_MASK_THRESHOLD RVHAL_EXCM_LEVEL_CLIC +#endif /* !SOC_INIT_CLIC_SUPPORTED */ + +void ulp_lp_core_spinlock_init(ulp_lp_core_spinlock_t *lock) +{ + int i = 0; + for (i = 0; i < LOCK_CANDIDATE_NUM_MAX; i++) { + lock->level[i] = -1; + } + + for (i = 0; i < LOCK_CANDIDATE_NUM_MAX - 1; i++) { + lock->last_to_enter[i] = 0; + } +} + +#if RTC_MEM_AMO_INSTRUCTIONS_VERIFIED +/* ULP spinlock ACQ/REL functions also have a hardware implementation base on AMOSWAP instructions, + which has much better performance but have not been thoroughly verified yet on RTC memory. This set + of implementation will be adapted once it's verified. + */ +static void ulp_lp_core_spinlock_acquire(ulp_lp_core_spinlock_t *lock) +{ + + /* Based on sample code for AMOSWAP from RISCV specs v2.1 */ + asm volatile( + "li t0, 1\n" // Initialize swap value. + "1:\n" + "lw t1, (%0)\n" // Check if lock is held. + "bnez t1, 1b\n" // Retry if held. + "amoswap.w.aq t1, t0, (%0)\n" // Attempt to acquire lock. + "bnez t1, 1b\n" // Retry if held. + : + : "r"(lock) + : "t0", "t1", "memory" + ); +} + +static void ulp_lp_core_spinlock_release(ulp_lp_core_spinlock_t *lock) +{ + asm volatile( + "amoswap.w.rl x0, x0, (%0)\n" // Release lock by storing 0 + : + : "r"(lock) + : "memory" + ); +} +#else // !RTC_MEM_AMO_INSTRUCTIONS_VERIFIED +/** + * @brief Obtain the id of current lock candidate. + * + * @return lock candidate id + */ +static int ulp_lp_core_spinlock_get_candidate_id(void) +{ + int lock_candidate_id = 0; +#if !IS_ULP_COCPU + /* Refer to ulp_lp_core_spinlock_candidate_t, each HP_CORE lock candidate's index is the core id plus 1 */ + lock_candidate_id = esp_cpu_get_core_id() + 1; +#else + lock_candidate_id = LOCK_CANDIDATE_LP_CORE; +#endif + return lock_candidate_id; +} + +/** + * @brief Software lock implementation is based on the filter algorithm, which is a generalization of Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm#Filter_algorithm:_Peterson's_algorithm_for_more_than_two_processes + * + */ + +/** + * @brief Attempt to acquire the lock. Spins until lock is acquired. + * + * @note This lock is designed for being used by multiple threads, it is safe to try to acquire it + * simultaneously from multiple threads on multiple main CPU cores and LP CPU. + * + * @note This function is private to ulp lp core critical section and shall not be called from anywhere else. + * + * @param lock Pointer to lock struct, shared with ULP + */ +static void ulp_lp_core_spinlock_acquire(ulp_lp_core_spinlock_t *lock) +{ + /* Level index */ + int lv = 0; + /* Candidate index */ + int candidate = 0; + + /* Index of the current lock candidate */ + int lock_candidate_id = ulp_lp_core_spinlock_get_candidate_id(); + + for (lv = 0; lv < (int)LOCK_CANDIDATE_NUM_MAX - 1; lv++) { + /* Each candidate has to go through all the levels in order to get the spinlock. Start by notifying other candidates, we have reached level `lv` */ + lock->level[lock_candidate_id] = lv; + /* Notify other candidates we are the latest one who entered level `lv` */ + lock->last_to_enter[lv] = lock_candidate_id; + /* If there is any candidate that reached the same or a higher level than this candidate, wait for it to finish. Advance to the next level if another candidate becomes the latest one to arrive at our current level */ + for (candidate = 0; candidate < (int)LOCK_CANDIDATE_NUM_MAX; candidate++) { + while ((candidate != lock_candidate_id) && (lock->level[candidate] >= lv && lock->last_to_enter[lv] == lock_candidate_id)) { + } + } + } +} + +/** + * @brief Release the lock. + * + * @note This function is private to ulp lp core critical section and shall not be called from anywhere else. + * + * @param lock Pointer to lock struct, shared with ULP + */ +static void ulp_lp_core_spinlock_release(ulp_lp_core_spinlock_t *lock) +{ + int lock_candidate_id = ulp_lp_core_spinlock_get_candidate_id(); + + lock->level[lock_candidate_id] = -1; +} +#endif + +void ulp_lp_core_enter_critical(ulp_lp_core_spinlock_t *lock) +{ + /* disable interrupt */ +#if !IS_ULP_COCPU // HP core + unsigned prev_int_level = rv_utils_set_intlevel_regval(INT_MASK_THRESHOLD); + lock->prev_int_level = prev_int_level; +#else // LP core + ulp_lp_core_intr_disable(); +#endif + /* Busy-wait to acquire the spinlock. Use caution when deploying this lock in time-sensitive scenarios. */ + ulp_lp_core_spinlock_acquire(lock); +} + +void ulp_lp_core_exit_critical(ulp_lp_core_spinlock_t *lock) +{ + ulp_lp_core_spinlock_release(lock); + + /* re-enable interrupt */ +#if !IS_ULP_COCPU // HP core + unsigned prev_int_level = lock->prev_int_level; + rv_utils_restore_intlevel_regval(prev_int_level); +#else // LP core + ulp_lp_core_intr_enable(); +#endif +} diff --git a/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c b/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c new file mode 100644 index 0000000000..63bc549019 --- /dev/null +++ b/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c @@ -0,0 +1,157 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_LP_ADC_SUPPORTED + +#include "ulp_lp_core_lp_adc_shared.h" +#include "hal/adc_types.h" +#include "hal/adc_ll.h" + +#define VREF (1100) /* Internal Reference voltage in millivolts (1.1V) */ +#define INV_ATTEN_0DB (1000) /* Inverse of 10^0 * 1000 */ +#define INV_ATTEN_2_5DB (1335) /* Inverse of 10^(-2.5/20) * 1000 */ +#define INV_ATTEN_6DB (1996) /* Inverse of 10^(-6/20) * 1000 */ +#define INV_ATTEN_12DB (3984) /* Inverse of 10^(-12/20) * 1000 */ + +adc_oneshot_unit_handle_t s_adc1_handle; + +esp_err_t lp_core_lp_adc_init(adc_unit_t unit_id) +{ + if (unit_id != ADC_UNIT_1) { + // TODO: LP ADC2 does not work during sleep (DIG-396) + // For now, we do not allow LP ADC2 usage. + return ESP_ERR_INVALID_ARG; + } + +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + /* LP ADC is being initialized from the HP core. + * Hence, we use the standard ADC driver APIs here. + */ + + /* Initialize ADC */ + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = unit_id, + .ulp_mode = ADC_ULP_MODE_LP_CORE, // LP Core will use the ADC + }; + + return (adc_oneshot_new_unit(&init_config, &s_adc1_handle)); +#endif /* IS_ULP_COCPU */ +} + +esp_err_t lp_core_lp_adc_deinit(adc_unit_t unit_id) +{ + if (unit_id != ADC_UNIT_1) { + return ESP_ERR_INVALID_ARG; + } + +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + return (adc_oneshot_del_unit(s_adc1_handle)); +#endif /* IS_ULP_COCPU */ +} + +esp_err_t lp_core_lp_adc_config_channel(adc_unit_t unit_id, adc_channel_t channel, const lp_core_lp_adc_chan_cfg_t *chan_config) +{ + if (unit_id != ADC_UNIT_1) { + // TODO: LP ADC2 does not work during sleep (DIG-396) + // For now, we do not allow LP ADC2 usage. + return ESP_ERR_INVALID_ARG; + } +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + adc_oneshot_chan_cfg_t config = { + .atten = chan_config->atten, + .bitwidth = chan_config->bitwidth, + }; + + return (adc_oneshot_config_channel(s_adc1_handle, channel, &config)); +#endif /* IS_ULP_COCPU */ +} + +esp_err_t lp_core_lp_adc_read_channel_raw(adc_unit_t unit_id, adc_channel_t channel, int *adc_raw) +{ + if (unit_id != ADC_UNIT_1 || adc_raw == NULL) { + return ESP_ERR_INVALID_ARG; + } + +#if IS_ULP_COCPU + uint32_t event = ADC_LL_EVENT_ADC1_ONESHOT_DONE; + + adc_oneshot_ll_clear_event(event); + adc_oneshot_ll_disable_all_unit(); + adc_oneshot_ll_enable(unit_id); + adc_oneshot_ll_set_channel(unit_id, channel); + + adc_oneshot_ll_start(unit_id); + while (!adc_oneshot_ll_get_event(event)) { + ; + } + *adc_raw = adc_oneshot_ll_get_raw_result(unit_id); + + adc_oneshot_ll_disable_all_unit(); +#else + return (adc_oneshot_read(s_adc1_handle, channel, adc_raw)); +#endif /* IS_ULP_COCPU */ + + return ESP_OK; +} + +esp_err_t lp_core_lp_adc_read_channel_converted(adc_unit_t unit_id, adc_channel_t channel, int *voltage_mv) +{ + esp_err_t ret = ESP_OK; + + if (unit_id != ADC_UNIT_1 || voltage_mv == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Read the raw ADC value */ + int adc_raw; + ret = lp_core_lp_adc_read_channel_raw(unit_id, channel, &adc_raw); + if (ret != ESP_OK) { + return ret; + } + + /* On the esp32p4, the ADC raw value can be 12-bit wide. The internal Vref is 1.1V. + * The formula to convert the raw value to voltage is: + * voltage = (((raw_value / (2^12 - 1)) * 1.1V) * attenuation) + * + * To avoid many floating point calculations, we precompute the attenuation factors + * and perform the conversion in millivolts instead of volts. + */ + + int measured_voltage = adc_raw * VREF; // millivolts + measured_voltage /= 4095; + + adc_atten_t atten = adc_ll_get_atten(unit_id, channel); + switch (atten) { + case ADC_ATTEN_DB_0: + *voltage_mv = (measured_voltage * INV_ATTEN_0DB) / 1000; + break; + case ADC_ATTEN_DB_2_5: + *voltage_mv = (measured_voltage * INV_ATTEN_2_5DB) / 1000; + break; + case ADC_ATTEN_DB_6: + *voltage_mv = (measured_voltage * INV_ATTEN_6DB) / 1000; + break; + case ADC_ATTEN_DB_12: + *voltage_mv = (measured_voltage * INV_ATTEN_12DB) / 1000; + break; + default: + ret = ESP_ERR_INVALID_STATE; + } + + return ret; +} + +#endif /* CONFIG_SOC_LP_ADC_SUPPORTED */ diff --git a/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c b/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c new file mode 100644 index 0000000000..b39e5ba0f2 --- /dev/null +++ b/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "ulp_lp_core_lp_timer_shared.h" +#include "soc/soc_caps.h" + +#if SOC_LP_TIMER_SUPPORTED +#include "hal/lp_timer_ll.h" +#include "hal/clk_tree_ll.h" +#include "soc/rtc.h" + +#define TIMER_ID 1 + +static struct { + lp_timer_dev_t *dev; +} lp_timer_context = { .dev = &LP_TIMER }; + +static void lp_timer_hal_set_alarm_target(uint64_t value) +{ + lp_timer_ll_clear_lp_alarm_intr_status(lp_timer_context.dev); + lp_timer_ll_set_alarm_target(lp_timer_context.dev, TIMER_ID, value); + lp_timer_ll_set_target_enable(lp_timer_context.dev, TIMER_ID, true); +} + +uint64_t ulp_lp_core_lp_timer_get_cycle_count(void) +{ + lp_timer_ll_counter_snapshot(lp_timer_context.dev); + + uint32_t lo = lp_timer_ll_get_counter_value_low(lp_timer_context.dev, 0); + uint32_t hi = lp_timer_ll_get_counter_value_high(lp_timer_context.dev, 0); + + lp_timer_counter_value_t result = { + .lo = lo, + .hi = hi + }; + + return result.val; +} + +void ulp_lp_core_lp_timer_set_wakeup_time(uint64_t sleep_duration_us) +{ + uint64_t cycle_cnt = ulp_lp_core_lp_timer_get_cycle_count(); + uint64_t alarm_target = cycle_cnt + ulp_lp_core_lp_timer_calculate_sleep_ticks(sleep_duration_us); + + lp_timer_hal_set_alarm_target(alarm_target); +} + +void ulp_lp_core_lp_timer_set_wakeup_ticks(uint64_t sleep_duration_ticks) +{ + uint64_t cycle_cnt = ulp_lp_core_lp_timer_get_cycle_count(); + uint64_t alarm_target = cycle_cnt + sleep_duration_ticks; + + lp_timer_hal_set_alarm_target(alarm_target); +} + +void ulp_lp_core_lp_timer_disable(void) +{ + lp_timer_ll_set_target_enable(lp_timer_context.dev, TIMER_ID, false); + lp_timer_ll_clear_lp_alarm_intr_status(lp_timer_context.dev); +} + +uint64_t ulp_lp_core_lp_timer_calculate_sleep_ticks(uint64_t sleep_duration_us) +{ + return (sleep_duration_us * (1 << RTC_CLK_CAL_FRACT) / clk_ll_rtc_slow_load_cal()); +} + +#endif //SOC_LP_TIMER_SUPPORTED diff --git a/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c b/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c new file mode 100644 index 0000000000..db78e74633 --- /dev/null +++ b/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_LP_VAD_SUPPORTED +#include "esp_check.h" +#include "esp_err.h" +#include "ulp_lp_core_lp_vad_shared.h" +#if SOC_LP_I2S_SUPPORT_VAD +//For VAD +#include "hal/lp_i2s_ll.h" +#include "hal/lp_i2s_hal.h" +#include "esp_private/lp_i2s_private.h" +#endif //SOC_LP_I2S_SUPPORT_VAD + +//make this available for multi vad id in future +vad_unit_handle_t s_vad_handle; + +esp_err_t lp_core_lp_vad_init(lp_vad_t vad_id, const lp_core_lp_vad_cfg_t *init_config) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_new_unit(vad_id, init_config, &s_vad_handle); + return ret; +#endif +} + +esp_err_t lp_core_lp_vad_enable(lp_vad_t vad_id) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_enable(s_vad_handle); + return ret; +#endif +} + +esp_err_t lp_core_lp_vad_disable(lp_vad_t vad_id) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_disable(s_vad_handle); + return ret; +#endif +} + +esp_err_t lp_core_lp_vad_deinit(lp_vad_t vad_id) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_del_unit(s_vad_handle); + return ret; +#endif +} +#endif /* SOC_LP_VAD_SUPPORTED */ diff --git a/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c b/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c new file mode 100644 index 0000000000..c8ee1e9d44 --- /dev/null +++ b/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "ulp_lp_core_memory_shared.h" + +#include "soc/soc.h" +#include "esp_rom_caps.h" +#include "esp_assert.h" +#include + +#define ALIGN_DOWN(SIZE, AL) (SIZE & ~(AL - 1)) + +#define ULP_COPROC_RESERVE_MEM DT_REG_SIZE(DT_NODELABEL(sramlp)) +#define ULP_SHARED_MEM DT_REG_SIZE(DT_NODELABEL(shmlp)) + +/* The last CONFIG_ULP_SHARED_MEM bytes of the reserved memory are reserved for a shared cfg struct + The main cpu app and the ulp binary can share variables automatically through the linkerscript generated from + esp32ulp_mapgen.py, but this is not available when compiling the ULP library. + + For those special cases, e.g. config settings. We can use this shared area. + */ + +#if IS_ULP_COCPU +static ulp_lp_core_memory_shared_cfg_t __attribute__((section(".shared_mem"))) s_shared_mem = {}; +ESP_STATIC_ASSERT(CONFIG_ULP_SHARED_MEM == sizeof(ulp_lp_core_memory_shared_cfg_t)); +#endif + +ulp_lp_core_memory_shared_cfg_t* ulp_lp_core_memory_shared_cfg_get(void) +{ +#if IS_ULP_COCPU + return &s_shared_mem; +#else +#if ESP_ROM_HAS_LP_ROM + extern uint32_t _rtc_ulp_memory_start; + uint32_t ulp_base_addr = (uint32_t)&_rtc_ulp_memory_start; +#else + uint32_t ulp_base_addr = SOC_RTC_DRAM_LOW; +#endif + /* Ensure the end where the shared memory starts is aligned to 8 bytes + if updating this also update the same in ulp_lp_core_riscv.ld + */ + return (ulp_lp_core_memory_shared_cfg_t *)(ulp_base_addr + ALIGN_DOWN(ULP_COPROC_RESERVE_MEM, 0x8) - ULP_SHARED_MEM); +#endif +} diff --git a/components/ulp/project_include.cmake b/components/ulp/project_include.cmake new file mode 100644 index 0000000000..3cb52588ef --- /dev/null +++ b/components/ulp/project_include.cmake @@ -0,0 +1,101 @@ +# ulp_embed_binary +# +# Create ULP binary and embed into the application. + +function(__setup_ulp_project app_name project_path s_sources exp_dep_srcs) + + if(NOT CMAKE_BUILD_EARLY_EXPANSION) + spaces2list(s_sources) + foreach(source ${s_sources}) + get_filename_component(source ${source} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR}) + list(APPEND sources ${source}) + endforeach() + + foreach(source ${sources}) + get_filename_component(ps_source ${source} NAME_WE) + set(ps_output ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${ps_source}.ulp.S) + list(APPEND ps_sources ${ps_output}) + endforeach() + + set(ulp_artifacts_prefix ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}) + + set(ulp_artifacts ${ulp_artifacts_prefix}.bin + ${ulp_artifacts_prefix}.ld + ${ulp_artifacts_prefix}.h) + + set(ulp_artifacts_extras ${ulp_artifacts_prefix}.map + ${ulp_artifacts_prefix}.sym + ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/esp32.ulp.ld) + + # Replace the separator for the list of ULP source files that will be passed to + # the external ULP project. This is a workaround to the bug https://public.kitware.com/Bug/view.php?id=16137. + string(REPLACE ";" "|" ulp_s_sources "${ulp_s_sources}") + + idf_build_get_property(sdkconfig_header SDKCONFIG_HEADER) + idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE) + idf_build_get_property(idf_path IDF_PATH) + idf_build_get_property(idf_target IDF_TARGET) + idf_build_get_property(python PYTHON) + idf_build_get_property(extra_cmake_args EXTRA_CMAKE_ARGS) + + if(IDF_TARGET STREQUAL "esp32") + set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-${idf_target}-ulp.cmake) + set(ULP_IS_RISCV OFF) + elseif(IDF_TARGET STREQUAL "esp32s2" OR IDF_TARGET STREQUAL "esp32s3") + if(CONFIG_ULP_COPROC_TYPE_RISCV STREQUAL "y") + set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-ulp-riscv.cmake) + else() + set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-${idf_target}-ulp.cmake) + endif() + elseif(CONFIG_ULP_COPROC_TYPE_LP_CORE) + set(TOOLCHAIN_FLAG ${idf_path}/components/ulp/cmake/toolchain-lp-core-riscv.cmake) + endif() + + externalproject_add(${app_name} + SOURCE_DIR ${project_path} + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${app_name} + INSTALL_COMMAND "" + CMAKE_ARGS -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FLAG} + -DULP_S_SOURCES=$ + -DULP_APP_NAME=${app_name} + -DADD_PICOLIBC_SPECS=${CONFIG_LIBC_PICOLIBC} + -DCOMPONENT_DIR=${COMPONENT_DIR} + -DCOMPONENT_INCLUDES=$ + -DIDF_TARGET=${idf_target} + -DIDF_PATH=${idf_path} + -DSDKCONFIG_HEADER=${SDKCONFIG_HEADER} + -DSDKCONFIG_CMAKE=${SDKCONFIG_CMAKE} + -DPYTHON=${python} + -DCMAKE_MODULE_PATH=${idf_path}/components/ulp/cmake/ + ${extra_cmake_args} + BUILD_COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/${app_name} --target build + BUILD_BYPRODUCTS ${ulp_artifacts} ${ulp_artifacts_extras} ${ulp_ps_sources} + ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name} + BUILD_ALWAYS 1 + ) + + set_property(TARGET ${app_name} PROPERTY ULP_SOURCES "${sources}") + + spaces2list(exp_dep_srcs) + set_source_files_properties(${exp_dep_srcs} PROPERTIES OBJECT_DEPENDS ${ulp_artifacts}) + + include_directories(${CMAKE_CURRENT_BINARY_DIR}/${app_name}) + + add_custom_target(${app_name}_artifacts DEPENDS ${app_name}) + + add_dependencies(${COMPONENT_LIB} ${app_name}_artifacts) + + target_linker_script(${COMPONENT_LIB} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}.ld) + target_add_binary_data(${COMPONENT_LIB} ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}.bin BINARY) + endif() +endfunction() + +function(ulp_embed_binary app_name s_sources exp_dep_srcs) + __setup_ulp_project("${app_name}" "${idf_path}/components/ulp/cmake" "${s_sources}" "${exp_dep_srcs}") +endfunction() + +function(ulp_add_project app_name project_path) + __setup_ulp_project("${app_name}" "${project_path}" "" "") +endfunction() diff --git a/components/ulp/sdkconfig.rename.esp32 b/components/ulp/sdkconfig.rename.esp32 new file mode 100644 index 0000000000..0d33e58905 --- /dev/null +++ b/components/ulp/sdkconfig.rename.esp32 @@ -0,0 +1,5 @@ +# sdkconfig replacement configurations for deprecated options formatted as +# CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION + +CONFIG_ESP32_ULP_COPROC_ENABLED CONFIG_ULP_COPROC_ENABLED +CONFIG_ESP32_ULP_COPROC_RESERVE_MEM CONFIG_ULP_COPROC_RESERVE_MEM diff --git a/components/ulp/sdkconfig.rename.esp32s2 b/components/ulp/sdkconfig.rename.esp32s2 new file mode 100644 index 0000000000..17319fa763 --- /dev/null +++ b/components/ulp/sdkconfig.rename.esp32s2 @@ -0,0 +1,6 @@ +# sdkconfig replacement configurations for deprecated options formatted as +# CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION + +CONFIG_ESP32S2_ULP_COPROC_ENABLED CONFIG_ULP_COPROC_ENABLED +CONFIG_ESP32S2_ULP_COPROC_RESERVE_MEM CONFIG_ULP_COPROC_RESERVE_MEM +CONFIG_ESP32S2_ULP_COPROC_RISCV CONFIG_ULP_COPROC_TYPE_RISCV diff --git a/components/ulp/test_apps/.build-test-rules.yml b/components/ulp/test_apps/.build-test-rules.yml new file mode 100644 index 0000000000..b3c0386d56 --- /dev/null +++ b/components/ulp/test_apps/.build-test-rules.yml @@ -0,0 +1,18 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/ulp/test_apps/lp_core/lp_core_basic_tests: + disable: + - if: SOC_LP_CORE_SUPPORTED != 1 + - if: CONFIG_NAME == "xtal" and SOC_CLK_LP_FAST_SUPPORT_XTAL != 1 + +components/ulp/test_apps/lp_core/lp_core_hp_uart: + disable: + - if: SOC_LP_CORE_SUPPORTED != 1 + +components/ulp/test_apps/ulp_fsm: + enable: + - if: SOC_ULP_FSM_SUPPORTED == 1 + +components/ulp/test_apps/ulp_riscv: + disable: + - if: SOC_RISCV_COPROC_SUPPORTED != 1 diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_basic_tests/CMakeLists.txt new file mode 100644 index 0000000000..2881fcd2f4 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/CMakeLists.txt @@ -0,0 +1,14 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") + +set(EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/unit-test-app/components" +) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(lp_core_basic_tests) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/README.md b/components/ulp/test_apps/lp_core/lp_core_basic_tests/README.md new file mode 100644 index 0000000000..59db987a22 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/README.md @@ -0,0 +1,3 @@ +| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 | +| ----------------- | -------- | -------- | -------- | + diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt new file mode 100644 index 0000000000..239b78cf33 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt @@ -0,0 +1,94 @@ +set(app_sources "test_app_main.c" "test_lp_core.c") + +if(CONFIG_SOC_LP_I2C_SUPPORTED) + list(APPEND app_sources "test_lp_core_i2c.c") +endif() + +if(CONFIG_SOC_ULP_LP_UART_SUPPORTED) + list(APPEND app_sources "test_lp_core_uart.c") +endif() + +if(CONFIG_SOC_LP_SPI_SUPPORTED) + list(APPEND app_sources "test_lp_core_spi.c") +endif() + +if(CONFIG_SOC_LP_CORE_SUPPORT_ETM AND CONFIG_SOC_ETM_SUPPORTED) + list(APPEND app_sources "test_lp_core_etm.c") +endif() + +if(CONFIG_SOC_LP_ADC_SUPPORTED) + list(APPEND app_sources "test_lp_core_adc.c") +endif() + +if(CONFIG_SOC_LP_VAD_SUPPORTED) + list(APPEND app_sources "test_lp_core_vad.c") +endif() + +set(lp_core_sources "lp_core/test_main.c") +set(lp_core_sources_counter "lp_core/test_main_counter.c") + +if(CONFIG_SOC_LP_TIMER_SUPPORTED) + set(lp_core_sources_set_timer_wakeup "lp_core/test_main_set_timer_wakeup.c") +endif() + +set(lp_core_sources_gpio "lp_core/test_main_gpio.c") + +if(CONFIG_SOC_LP_I2C_SUPPORTED) + set(lp_core_sources_i2c "lp_core/test_main_i2c.c") +endif() + +if(CONFIG_SOC_ULP_LP_UART_SUPPORTED) + set(lp_core_sources_uart "lp_core/test_main_uart.c") +endif() + +if(CONFIG_SOC_LP_SPI_SUPPORTED) + set(lp_core_sources_spi_master "lp_core/test_main_spi_master.c") + set(lp_core_sources_spi_slave "lp_core/test_main_spi_slave.c") +endif() + +if(CONFIG_SOC_LP_ADC_SUPPORTED) + set(lp_core_sources_adc "lp_core/test_main_adc.c") +endif() + +if(CONFIG_SOC_LP_VAD_SUPPORTED) + set(lp_core_sources_vad "lp_core/test_main_vad.c") +endif() + +idf_component_register(SRCS ${app_sources} + INCLUDE_DIRS "lp_core" + REQUIRES ulp unity esp_timer test_utils + WHOLE_ARCHIVE + EMBED_FILES "test_vad_8k.pcm") + +set(lp_core_exp_dep_srcs ${app_sources}) + +ulp_embed_binary(lp_core_test_app "${lp_core_sources}" "${lp_core_exp_dep_srcs}") +ulp_embed_binary(lp_core_test_app_counter "${lp_core_sources_counter}" "${lp_core_exp_dep_srcs}") +ulp_embed_binary(lp_core_test_app_isr "lp_core/test_main_isr.c" "${lp_core_exp_dep_srcs}") + +if(CONFIG_SOC_LP_TIMER_SUPPORTED) + ulp_embed_binary(lp_core_test_app_set_timer_wakeup "${lp_core_sources_set_timer_wakeup}" "${lp_core_exp_dep_srcs}") +endif() + +ulp_embed_binary(lp_core_test_app_gpio "${lp_core_sources_gpio}" "${lp_core_exp_dep_srcs}") + +if(CONFIG_SOC_LP_I2C_SUPPORTED) + ulp_embed_binary(lp_core_test_app_i2c "${lp_core_sources_i2c}" "${lp_core_exp_dep_srcs}") +endif() + +if(CONFIG_SOC_ULP_LP_UART_SUPPORTED) + ulp_embed_binary(lp_core_test_app_uart "${lp_core_sources_uart}" "${lp_core_exp_dep_srcs}") +endif() + +if(CONFIG_SOC_LP_SPI_SUPPORTED) + ulp_embed_binary(lp_core_test_app_spi_master "${lp_core_sources_spi_master}" "${lp_core_exp_dep_srcs}") + ulp_embed_binary(lp_core_test_app_spi_slave "${lp_core_sources_spi_slave}" "${lp_core_exp_dep_srcs}") +endif() + +if(CONFIG_SOC_LP_ADC_SUPPORTED) + ulp_embed_binary(lp_core_test_app_adc "${lp_core_sources_adc}" "${lp_core_exp_dep_srcs}") +endif() + +if(CONFIG_SOC_LP_VAD_SUPPORTED) + ulp_embed_binary(lp_core_test_app_vad "${lp_core_sources_vad}" "${lp_core_exp_dep_srcs}") +endif() diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/Kconfig.projbuild b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/Kconfig.projbuild new file mode 100644 index 0000000000..5783fc5daf --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "Test Configurations" + + config TEST_LP_CORE_VAD_ENABLE + bool "Enable LP VAD test" + default n + help + Enable this to trigger LP VAD test + +endmenu diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main.c new file mode 100644 index 0000000000..b9adad2670 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main.c @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "test_shared.h" +#include "ulp_lp_core_utils.h" + +volatile lp_core_test_commands_t main_cpu_command = LP_CORE_NO_COMMAND; +volatile lp_core_test_command_reply_t main_cpu_reply = LP_CORE_COMMAND_INVALID; +volatile lp_core_test_commands_t command_resp = LP_CORE_NO_COMMAND; +volatile uint32_t test_data_in = 0; +volatile uint32_t test_data_out = 0; + +volatile uint32_t incrementer = 0; + +void handle_commands(lp_core_test_commands_t cmd) +{ + + switch (cmd) { + case LP_CORE_READ_WRITE_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = LP_CORE_READ_WRITE_TEST; + + /* Process test data */ + test_data_out = test_data_in ^ XOR_MASK; + + /* Set the command reply status */ + main_cpu_reply = LP_CORE_COMMAND_OK; + main_cpu_command = LP_CORE_NO_COMMAND; + + break; + + case LP_CORE_DELAY_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = LP_CORE_DELAY_TEST; + + ulp_lp_core_delay_us(test_data_in); + main_cpu_reply = LP_CORE_COMMAND_OK; + main_cpu_command = LP_CORE_NO_COMMAND; + break; + + case LP_CORE_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = LP_CORE_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST; + + /* Set the command reply status */ + main_cpu_reply = LP_CORE_COMMAND_OK; + main_cpu_command = LP_CORE_NO_COMMAND; + + ulp_lp_core_delay_us(1000 * 1000); + ulp_lp_core_wakeup_main_processor(); + + break; + + case LP_CORE_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = LP_CORE_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST; + + /* Set the command reply status */ + main_cpu_reply = LP_CORE_COMMAND_OK; + main_cpu_command = LP_CORE_NO_COMMAND; + + ulp_lp_core_delay_us(10000 * 1000); + ulp_lp_core_wakeup_main_processor(); + + break; + + case LP_CORE_NO_COMMAND: + break; + + default: + break; + } +} + +int main(void) +{ + while (1) { + handle_commands(main_cpu_command); + } + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_adc.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_adc.c new file mode 100644 index 0000000000..4bc1b60220 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_adc.c @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "ulp_lp_core_lp_adc_shared.h" + +volatile int adc_raw[8]; + +int main(void) +{ + while (1) { + for (int i = 0; i < 8; i++) { + lp_core_lp_adc_read_channel_raw(ADC_UNIT_1, i, (int *)&adc_raw[i]); + } + } + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_counter.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_counter.c new file mode 100644 index 0000000000..7bab470565 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_counter.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_lp_core_utils.h" + +volatile uint32_t counter; +volatile uint32_t counter_wakeup_limit; + +int main(void) +{ + counter++; + + if (counter_wakeup_limit && (counter > counter_wakeup_limit)) { + counter = 0; + + ulp_lp_core_wakeup_main_processor(); + } + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_gpio.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_gpio.c new file mode 100644 index 0000000000..53e71fc19c --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_gpio.c @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_gpio.h" + +volatile uint32_t gpio_test_finished; +volatile uint32_t gpio_test_succeeded; + +int main(void) +{ + ulp_lp_core_gpio_init(LP_IO_NUM_0); + + ulp_lp_core_gpio_input_enable(LP_IO_NUM_0); + ulp_lp_core_gpio_output_enable(LP_IO_NUM_0); + + ulp_lp_core_gpio_set_level(LP_IO_NUM_0, 0); + ulp_lp_core_delay_us(100); + gpio_test_succeeded = (ulp_lp_core_gpio_get_level(LP_IO_NUM_0) == 0); + + ulp_lp_core_gpio_set_level(LP_IO_NUM_0, 1); + ulp_lp_core_delay_us(100); + gpio_test_succeeded &= (ulp_lp_core_gpio_get_level(LP_IO_NUM_0) == 1); + + ulp_lp_core_gpio_set_level(LP_IO_NUM_0, 0); + ulp_lp_core_delay_us(100); + gpio_test_succeeded &= (ulp_lp_core_gpio_get_level(LP_IO_NUM_0) == 0); + + gpio_test_finished = 1; + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_i2c.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_i2c.c new file mode 100644 index 0000000000..06657c0de6 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_i2c.c @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "test_shared.h" +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_i2c.h" + +#define LP_I2C_TRANS_WAIT_FOREVER -1 + +volatile lp_core_test_command_reply_t read_test_reply = LP_CORE_COMMAND_INVALID; +volatile lp_core_test_command_reply_t write_test_cmd = LP_CORE_COMMAND_INVALID; + +uint8_t data_rd[DATA_LENGTH] = {}; +uint8_t data_wr[DATA_LENGTH] = {}; + +int main(void) +{ + lp_core_i2c_master_read_from_device(LP_I2C_NUM_0, I2C_SLAVE_ADDRESS, data_rd, RW_TEST_LENGTH, LP_I2C_TRANS_WAIT_FOREVER); + read_test_reply = LP_CORE_COMMAND_OK; + + /* Wait for write command from main CPU */ + while (write_test_cmd != LP_CORE_COMMAND_OK) { + } + + lp_core_i2c_master_write_to_device(LP_I2C_NUM_0, I2C_SLAVE_ADDRESS, data_wr, RW_TEST_LENGTH, LP_I2C_TRANS_WAIT_FOREVER); + + write_test_cmd = LP_CORE_COMMAND_NOK; + + while (1) { + } +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_isr.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_isr.c new file mode 100644 index 0000000000..c3cc33c910 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_isr.c @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_interrupts.h" +#include "ulp_lp_core_gpio.h" +#include "hal/pmu_ll.h" + +volatile uint32_t io_isr_counter = 0; +volatile uint32_t pmu_isr_counter = 0; +volatile bool isr_test_started; + +void LP_CORE_ISR_ATTR ulp_lp_core_lp_io_intr_handler(void) +{ + ulp_lp_core_gpio_clear_intr_status(); + io_isr_counter++; +} + +void LP_CORE_ISR_ATTR ulp_lp_core_lp_pmu_intr_handler(void) +{ + ulp_lp_core_sw_intr_clear(); + pmu_isr_counter++; +} + +int main(void) +{ + ulp_lp_core_sw_intr_enable(true); + + ulp_lp_core_intr_enable(); + + isr_test_started = true; + + while (1) { + // Busy wait for the interrupts to occur + } + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_set_timer_wakeup.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_set_timer_wakeup.c new file mode 100644 index 0000000000..c584221e98 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_set_timer_wakeup.c @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_lp_timer_shared.h" + +volatile uint32_t set_timer_wakeup_counter; +volatile uint32_t WAKEUP_PERIOD_BASE_US = 100000; + +int main(void) +{ + set_timer_wakeup_counter++; + + /* Alternate between WAKEUP_PERIOD_BASE_US and 2*WAKEUP_PERIOD_BASE_US to let the main CPU see that + the wake-up time can be reconfigured */ + ulp_lp_core_lp_timer_set_wakeup_time(((set_timer_wakeup_counter % 2) + 1)*WAKEUP_PERIOD_BASE_US); + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_master.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_master.c new file mode 100644 index 0000000000..f7872dbcfc --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_master.c @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "ulp_lp_core_spi.h" +#include "test_shared.h" + +volatile lp_core_test_commands_t spi_test_cmd = LP_CORE_NO_COMMAND; + +volatile uint8_t spi_master_tx_buf[100] = {0}; +volatile uint8_t spi_master_rx_buf[100] = {0}; +volatile uint32_t spi_tx_len = 0; + +int main(void) +{ + /* Wait for the HP core to start the test */ + while (spi_test_cmd == LP_CORE_NO_COMMAND) { + + } + + /* Setup SPI transaction */ + lp_spi_transaction_t trans_desc = { + .tx_length = spi_tx_len, + .rx_length = spi_tx_len, + .tx_buffer = (uint8_t *)spi_master_tx_buf, + .rx_buffer = (uint8_t *)spi_master_rx_buf, + }; + + /* Transmit data */ + lp_core_lp_spi_master_transfer(&trans_desc, -1); + + /* Synchronize with the HP core running the test */ + spi_test_cmd = LP_CORE_NO_COMMAND; + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_slave.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_slave.c new file mode 100644 index 0000000000..81b208295e --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_slave.c @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "ulp_lp_core_spi.h" +#include "test_shared.h" + +volatile lp_core_test_command_reply_t spi_test_cmd_reply = LP_CORE_COMMAND_NOK; + +volatile uint8_t spi_slave_tx_buf[100] = {0}; +volatile uint8_t spi_slave_rx_buf[100] = {0}; +volatile uint32_t spi_rx_len = 0; + +int main(void) +{ + /* Setup SPI transaction */ + lp_spi_transaction_t trans_desc = { + .rx_length = spi_rx_len, + .rx_buffer = (uint8_t *)spi_slave_rx_buf, + .tx_buffer = NULL, + }; + + /* Receive data */ + lp_core_lp_spi_slave_transfer(&trans_desc, -1); + + /* Synchronize with the HP core running the test */ + spi_test_cmd_reply = LP_CORE_COMMAND_OK; + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart.c new file mode 100644 index 0000000000..6dc5e3c167 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart.c @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/uart_types.h" +#include "test_shared.h" +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_uart.h" +#include "ulp_lp_core_print.h" + +#define LP_UART_PORT_NUM LP_UART_NUM_0 +#define LP_UART_BUFFER_LEN UART_BUF_SIZE +#define LP_UART_TRANS_WAIT_FOREVER -1 + +volatile lp_core_test_commands_t test_cmd = LP_CORE_NO_COMMAND; +volatile lp_core_test_command_reply_t test_cmd_reply = LP_CORE_COMMAND_INVALID; + +/* Tx and Rx buffers for LP UART */ +uint8_t tx_data[LP_UART_BUFFER_LEN] = {}; +uint8_t rx_data[LP_UART_BUFFER_LEN] = {}; + +/* Data transmission length */ +volatile uint8_t tx_len = 0; +volatile uint8_t rx_len = 0; + +/* LP Core print test variables */ +volatile char test_string[25]; +volatile char test_long_string[200]; +volatile int test_signed_integer; +volatile uint32_t test_unsigned_integer; +volatile int test_hex; +volatile char test_character; + +int main(void) +{ + while (1) { + /* Wait for the HP core to start the test */ + while (test_cmd == LP_CORE_NO_COMMAND) { + + } + + if (test_cmd == LP_CORE_LP_UART_WRITE_TEST) { + /* Write data on LP UART */ + lp_core_uart_write_bytes(LP_UART_PORT_NUM, (const char *)tx_data, tx_len, LP_UART_TRANS_WAIT_FOREVER); + } + + if (test_cmd == LP_CORE_LP_UART_READ_TEST) { + /* Read data from LP UART */ + int bytes_received = 0; + int idx = 0; + while (1) { + /* Read 1 byte at a time until we receive the end_pattern of 0xFEEDBEEF */ + bytes_received = lp_core_uart_read_bytes(LP_UART_PORT_NUM, rx_data + idx, 1, 100); + if (bytes_received < 0) { + break; + } + if (idx > 3 && rx_data[idx] == 0xEF && rx_data[idx - 1] == 0xBE && rx_data[idx - 2] == 0xED && rx_data[idx - 3] == 0xFE) { + /* Break and notify HP core of test completion */ + break; + } + idx += bytes_received; + } + } + + if (test_cmd == LP_CORE_LP_UART_MULTI_BYTE_READ_TEST) { + int bytes_remaining = rx_len; + int bytes_received = 0; + int idx = 0; + while (bytes_remaining > 0) { + /* Read as much data as we can in one iteration */ + bytes_received = lp_core_uart_read_bytes(LP_UART_PORT_NUM, rx_data + idx, bytes_remaining, LP_UART_TRANS_WAIT_FOREVER); + if (bytes_received < 0) { + break; + } + bytes_remaining -= bytes_received; + idx += bytes_received; + } + } + + if (test_cmd == LP_CORE_LP_UART_PRINT_TEST) { + /* Write various cases to test lp_core_printf to test various format specifiers */ + lp_core_printf("%s\r\n", test_string); + lp_core_printf("%s\r\n", test_long_string); + lp_core_printf("Test printf signed integer %d\r\n", test_signed_integer); + lp_core_printf("Test printf unsigned integer %u\r\n", test_unsigned_integer); + lp_core_printf("Test printf hex %x\r\n", test_hex); + lp_core_printf("Test printf character %c\r\n", test_character); + // TODO: Floating point prints are not supported + // lp_core_printf("Test printf float %f\r\n", (float)0.99); + } + + /* Synchronize with the HP core running the test */ + test_cmd = LP_CORE_NO_COMMAND; + test_cmd_reply = LP_CORE_COMMAND_OK; + } +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_vad.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_vad.c new file mode 100644 index 0000000000..5f6198fe37 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_vad.c @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +volatile bool vad_wakup; + +int main(void) +{ + vad_wakup = true; + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h new file mode 100644 index 0000000000..8fc1c43140 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#define XOR_MASK 0xDEADBEEF + +/* I2C test params */ +#define I2C_SLAVE_ADDRESS 0x28 +#define DATA_LENGTH 200 +#define RW_TEST_LENGTH 129 /*!= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c new file mode 100644 index 0000000000..62ead4122d --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c @@ -0,0 +1,387 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "soc/soc_caps.h" +#include "esp_rom_caps.h" +#include "lp_core_test_app.h" +#include "lp_core_test_app_counter.h" +#include "lp_core_test_app_isr.h" + +#if SOC_LP_TIMER_SUPPORTED +#include "lp_core_test_app_set_timer_wakeup.h" +#endif + +#include "lp_core_test_app_gpio.h" +#include "ulp_lp_core.h" +#include "ulp_lp_core_lp_timer_shared.h" +#include "test_shared.h" +#include "unity.h" +#include "esp_sleep.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "hal/lp_core_ll.h" +#include "hal/rtc_io_ll.h" +#include "driver/rtc_io.h" + +extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_test_app_bin_start"); +extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_test_app_bin_end"); + +extern const uint8_t lp_core_main_counter_bin_start[] asm("_binary_lp_core_test_app_counter_bin_start"); +extern const uint8_t lp_core_main_counter_bin_end[] asm("_binary_lp_core_test_app_counter_bin_end"); + +extern const uint8_t lp_core_main_set_timer_wakeup_bin_start[] asm("_binary_lp_core_test_app_set_timer_wakeup_bin_start"); +extern const uint8_t lp_core_main_set_timer_wakeup_bin_end[] asm("_binary_lp_core_test_app_set_timer_wakeup_bin_end"); + +extern const uint8_t lp_core_main_gpio_bin_start[] asm("_binary_lp_core_test_app_gpio_bin_start"); +extern const uint8_t lp_core_main_gpio_bin_end[] asm("_binary_lp_core_test_app_gpio_bin_end"); + +extern const uint8_t lp_core_main_isr_bin_start[] asm("_binary_lp_core_test_app_isr_bin_start"); +extern const uint8_t lp_core_main_isr_bin_end[] asm("_binary_lp_core_test_app_isr_bin_end"); + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); + +} + +static void clear_test_cmds(void) +{ + ulp_main_cpu_command = LP_CORE_NO_COMMAND; + ulp_command_resp = LP_CORE_NO_COMMAND; +} + +TEST_CASE("LP core and main CPU are able to exchange data", "[lp_core]") +{ + const uint32_t test_data = 0x12345678; + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_bin_start, lp_core_main_bin_end); + + /* Setup test data */ + ulp_test_data_in = test_data ^ XOR_MASK; + ulp_main_cpu_command = LP_CORE_READ_WRITE_TEST; + + /* Wait till we receive the correct command response */ + while (ulp_command_resp != LP_CORE_READ_WRITE_TEST) { + } + + /* Verify test data */ + TEST_ASSERT(ulp_command_resp == LP_CORE_READ_WRITE_TEST); + + /* Wait till we receive COMMAND_OK reply */ + while (ulp_main_cpu_reply != LP_CORE_COMMAND_OK) { + } + + printf("data out: 0x%" PRIx32 ", expected: 0x%" PRIx32 " \n", ulp_test_data_out, test_data); + TEST_ASSERT(test_data == ulp_test_data_out); + + clear_test_cmds(); +} + +TEST_CASE("Test LP core delay", "[lp_core]") +{ + int64_t start, diff; + const uint32_t delay_period_us = 5000000; + const uint32_t delta_us = 500000; // RTC FAST is not very accurate + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_bin_start, lp_core_main_bin_end); + + /* Setup test data */ + ulp_test_data_in = delay_period_us; + ulp_main_cpu_command = LP_CORE_DELAY_TEST; + + /* Wait till we receive the correct command response */ + while (ulp_command_resp != LP_CORE_DELAY_TEST) { + } + + start = esp_timer_get_time(); + + /* Wait till we receive COMMAND_OK reply */ + while (ulp_main_cpu_reply != LP_CORE_COMMAND_OK) { + } + + diff = esp_timer_get_time() - start; + + printf("Waited for %" PRIi64 "us, expected: %" PRIi32 "us\n", diff, delay_period_us); + TEST_ASSERT_INT_WITHIN(delta_us, delay_period_us, diff); + + clear_test_cmds(); +} + +#define LP_TIMER_TEST_DURATION_S (5) +#define LP_TIMER_TEST_SLEEP_DURATION_US (20000) + +#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C5) +#if SOC_DEEP_SLEEP_SUPPORTED && CONFIG_RTC_FAST_CLK_SRC_RC_FAST + +static void do_ulp_wakeup_deepsleep(lp_core_test_commands_t ulp_cmd) +{ + /* Load ULP firmware and start the ULP RISC-V Coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_bin_start, lp_core_main_bin_end); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Setup test data */ + ulp_main_cpu_command = ulp_cmd; + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} + +static void check_reset_reason_ulp_wakeup(void) +{ + TEST_ASSERT_EQUAL(ESP_SLEEP_WAKEUP_ULP, esp_sleep_get_wakeup_cause()); +} + +static void do_ulp_wakeup_after_short_delay_deepsleep(void) +{ + do_ulp_wakeup_deepsleep(LP_CORE_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST); +} + +static void do_ulp_wakeup_after_long_delay_deepsleep(void) +{ + do_ulp_wakeup_deepsleep(LP_CORE_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST); +} + +TEST_CASE_MULTIPLE_STAGES("LP-core is able to wakeup main CPU from deep sleep after a short delay", "[ulp]", + do_ulp_wakeup_after_short_delay_deepsleep, + check_reset_reason_ulp_wakeup); + +/* Certain erroneous wake-up triggers happen only after sleeping for a few seconds */ +TEST_CASE_MULTIPLE_STAGES("LP-core is able to wakeup main CPU from deep sleep after a long delay", "[ulp]", + do_ulp_wakeup_after_long_delay_deepsleep, + check_reset_reason_ulp_wakeup); + +RTC_FAST_ATTR static struct timeval tv_start; + +#define ULP_COUNTER_WAKEUP_LIMIT_CNT 50 + +static void do_ulp_wakeup_with_lp_timer_deepsleep(void) +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, + .lp_timer_sleep_duration_us = LP_TIMER_TEST_SLEEP_DURATION_US, +#if ESP_ROM_HAS_LP_ROM + /* ROM Boot takes quite a bit longer, which skews the numbers of wake-ups. skip rom boot to keep the calculation simple */ + .skip_lp_rom_boot = true, +#endif + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_counter_bin_start, lp_core_main_counter_bin_end); + ulp_counter_wakeup_limit = ULP_COUNTER_WAKEUP_LIMIT_CNT; + + gettimeofday(&tv_start, NULL); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} + +static void check_reset_reason_and_sleep_duration(void) +{ + struct timeval tv_stop = {}; + gettimeofday(&tv_stop, NULL); + + TEST_ASSERT_EQUAL(ESP_SLEEP_WAKEUP_ULP, esp_sleep_get_wakeup_cause()); + + int64_t sleep_duration = (tv_stop.tv_sec - tv_start.tv_sec) * 1000 + (tv_stop.tv_usec - tv_start.tv_usec) / 1000; + int64_t expected_sleep_duration_ms = ulp_counter_wakeup_limit * LP_TIMER_TEST_SLEEP_DURATION_US / 1000; + + printf("CPU slept for %"PRIi64" ms, expected it to sleep approx %"PRIi64" ms\n", sleep_duration, expected_sleep_duration_ms); + /* Rough estimate, as CPU spends quite some time waking up, but will test if lp core is waking up way too often etc */ + TEST_ASSERT_INT_WITHIN_MESSAGE(1000, expected_sleep_duration_ms, sleep_duration, "LP Core did not wake up the expected number of times"); + + clear_test_cmds(); +} + +TEST_CASE_MULTIPLE_STAGES("LP Timer can wakeup lp core periodically during deep sleep", "[ulp]", + do_ulp_wakeup_with_lp_timer_deepsleep, + check_reset_reason_and_sleep_duration); + +#endif //#if SOC_DEEP_SLEEP_SUPPORTED && CONFIG_RTC_FAST_CLK_SRC_RC_FAST +#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32C5) + +TEST_CASE("LP Timer can wakeup lp core periodically", "[lp_core]") +{ + int64_t start, test_duration; + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, + .lp_timer_sleep_duration_us = LP_TIMER_TEST_SLEEP_DURATION_US, +#if ESP_ROM_HAS_LP_ROM + /* ROM Boot takes quite a bit longer, which skews the numbers of wake-ups. skip rom boot to keep the calculation simple */ + .skip_lp_rom_boot = true, +#endif + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_counter_bin_start, lp_core_main_counter_bin_end); + + start = esp_timer_get_time(); + vTaskDelay(pdMS_TO_TICKS(LP_TIMER_TEST_DURATION_S * 1000)); + + test_duration = esp_timer_get_time() - start; + uint32_t expected_run_count = test_duration / LP_TIMER_TEST_SLEEP_DURATION_US; + printf("LP core ran %"PRIu32" times in %"PRIi64" ms, expected it to run approx %"PRIu32" times\n", ulp_counter, test_duration / 1000, expected_run_count); + + TEST_ASSERT_INT_WITHIN_MESSAGE(5, expected_run_count, ulp_counter, "LP Core did not wake up the expected number of times"); +} + +static bool ulp_is_running(uint32_t *counter_variable) +{ + uint32_t start_cnt = *counter_variable; + + /* Wait a few ULP wakeup cycles to ensure ULP has run */ + vTaskDelay((5 * LP_TIMER_TEST_SLEEP_DURATION_US / 1000) / portTICK_PERIOD_MS); + + uint32_t end_cnt = *counter_variable; + printf("start run count: %" PRIu32 ", end run count %" PRIu32 "\n", start_cnt, end_cnt); + + /* If the ulp is running the counter should have been incremented */ + return (start_cnt != end_cnt); +} + +#define STOP_TEST_ITERATIONS 10 + +TEST_CASE("LP core can be stopped and and started again from main CPU", "[ulp]") +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, + .lp_timer_sleep_duration_us = LP_TIMER_TEST_SLEEP_DURATION_US, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_counter_bin_start, lp_core_main_counter_bin_end); + + TEST_ASSERT(ulp_is_running(&ulp_counter)); + + for (int i = 0; i < STOP_TEST_ITERATIONS; i++) { + ulp_lp_core_stop(); + TEST_ASSERT(!ulp_is_running(&ulp_counter)); + + ulp_lp_core_run(&cfg); + TEST_ASSERT(ulp_is_running(&ulp_counter)); + } +} + +#if SOC_LP_TIMER_SUPPORTED +TEST_CASE("LP core can schedule next wake-up time by itself", "[ulp]") +{ + int64_t start, test_duration; + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, +#if ESP_ROM_HAS_LP_ROM + /* ROM Boot takes quite a bit longer, which skews the numbers of wake-ups. skip rom boot to keep the calculation simple */ + .skip_lp_rom_boot = true, +#endif + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_set_timer_wakeup_bin_start, lp_core_main_set_timer_wakeup_bin_end); + + start = esp_timer_get_time(); + vTaskDelay(pdMS_TO_TICKS(LP_TIMER_TEST_DURATION_S * 1000)); + + test_duration = esp_timer_get_time() - start; + /* ULP will alternative between setting WAKEUP_PERIOD_BASE_US and 2*WAKEUP_PERIOD_BASE_US + as a wakeup period which should give an average wakeup time of 1.5*WAKEUP_PERIOD_BASE_US */ + uint32_t expected_run_count = test_duration / (1.5 * ulp_WAKEUP_PERIOD_BASE_US); + printf("LP core ran %"PRIu32" times in %"PRIi64" ms, expected it to run approx %"PRIu32" times\n", ulp_set_timer_wakeup_counter, test_duration / 1000, expected_run_count); + + TEST_ASSERT_INT_WITHIN_MESSAGE(5, expected_run_count, ulp_set_timer_wakeup_counter, "LP Core did not wake up the expected number of times"); +} + +#if SOC_RTCIO_PIN_COUNT > 0 +TEST_CASE("LP core gpio tests", "[ulp]") +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, + .lp_timer_sleep_duration_us = LP_TIMER_TEST_SLEEP_DURATION_US, + }; + + rtc_gpio_init(GPIO_NUM_0); + load_and_start_lp_core_firmware(&cfg, lp_core_main_gpio_bin_start, lp_core_main_gpio_bin_end); + + while (!ulp_gpio_test_finished) { + } + + TEST_ASSERT_TRUE(ulp_gpio_test_succeeded); +} +#endif //SOC_RTCIO_PIN_COUNT > 0 + +#endif //SOC_LP_TIMER_SUPPORTED + +#define ISR_TEST_ITERATIONS 100 +#define IO_TEST_PIN 0 + +TEST_CASE("LP core ISR tests", "[ulp]") +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_isr_bin_start, lp_core_main_isr_bin_end); + + while (!ulp_isr_test_started) { + } + + for (int i = 0; i < ISR_TEST_ITERATIONS; i++) { + lp_core_ll_hp_wake_lp(); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + printf("ULP PMU ISR triggered %"PRIu32" times\n", ulp_pmu_isr_counter); + TEST_ASSERT_EQUAL(ISR_TEST_ITERATIONS, ulp_pmu_isr_counter); + +#if SOC_RTCIO_PIN_COUNT > 0 + /* Test LP IO interrupt */ + rtc_gpio_init(IO_TEST_PIN); + rtc_gpio_set_direction(IO_TEST_PIN, RTC_GPIO_MODE_INPUT_ONLY); + TEST_ASSERT_EQUAL(0, ulp_io_isr_counter); + + for (int i = 0; i < ISR_TEST_ITERATIONS; i++) { +#if CONFIG_IDF_TARGET_ESP32C6 + LP_IO.status_w1ts.val = 0x00000001; // Set GPIO 0 intr status to high +#else + LP_GPIO.status_w1ts.val = 0x00000001; // Set GPIO 0 intr status to high +#endif + vTaskDelay(pdMS_TO_TICKS(10)); + } + + printf("ULP LP IO ISR triggered %"PRIu32" times\n", ulp_io_isr_counter); + TEST_ASSERT_EQUAL(ISR_TEST_ITERATIONS, ulp_io_isr_counter); +#endif //SOC_RTCIO_PIN_COUNT > 0 +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_adc.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_adc.c new file mode 100644 index 0000000000..537d93b44b --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_adc.c @@ -0,0 +1,243 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/adc_types.h" +#include "lp_core_test_app_adc.h" +#include "ulp_lp_core.h" +#include "ulp_lp_core_lp_adc_shared.h" +#include "soc/adc_periph.h" +#include "driver/gpio.h" +#include "driver/temperature_sensor.h" + +#include "unity.h" + +extern const uint8_t lp_core_main_adc_bin_start[] asm("_binary_lp_core_test_app_adc_bin_start"); +extern const uint8_t lp_core_main_adc_bin_end[] asm("_binary_lp_core_test_app_adc_bin_end"); + +#if CONFIG_IDF_TARGET_ESP32P4 +// Threshold values picked up empirically after manual testing +#define ADC_TEST_LOW_VAL 1500 +#define ADC_TEST_HIGH_VAL 2000 +#else +#error "ADC threshold values not defined" +#endif + +#define ADC_GET_IO_NUM(unit, channel) (adc_channel_io_map[unit][channel]) + +static void test_adc_set_io_level(adc_unit_t unit, adc_channel_t channel, bool level) +{ + TEST_ASSERT(channel < SOC_ADC_CHANNEL_NUM(unit) && "invalid channel"); + +#if !ADC_LL_RTC_GPIO_SUPPORTED + uint32_t io_num = ADC_GET_IO_NUM(unit, channel); + TEST_ESP_OK(gpio_set_pull_mode(io_num, (level ? GPIO_PULLUP_ONLY : GPIO_PULLDOWN_ONLY))); +#else + gpio_num_t io_num = ADC_GET_IO_NUM(unit, channel); + if (level) { + TEST_ESP_OK(rtc_gpio_pullup_en(io_num)); + TEST_ESP_OK(rtc_gpio_pulldown_dis(io_num)); + } else { + TEST_ESP_OK(rtc_gpio_pullup_dis(io_num)); + TEST_ESP_OK(rtc_gpio_pulldown_en(io_num)); + } +#endif +} + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); + +} + +void test_lp_adc(adc_unit_t unit_id) +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); + + /* LP ADC Init */ + ESP_ERROR_CHECK(lp_core_lp_adc_init(unit_id)); + + /* LP ADC channel config */ + const lp_core_lp_adc_chan_cfg_t config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + + /* Configure all ADC channels. + * LP ADC1: Channels 0 - 7 + * LP ADC2: Channels 0 - 5 + */ + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_0, &config) == ESP_OK); + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_1, &config) == ESP_OK); + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_2, &config) == ESP_OK); + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_3, &config) == ESP_OK); + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_4, &config) == ESP_OK); + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_5, &config) == ESP_OK); + if (unit_id == ADC_UNIT_1) { + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_6, &config) == ESP_OK); + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_7, &config) == ESP_OK); + } + + /* Set all the ADC channel IOs to low */ + test_adc_set_io_level(unit_id, ADC_CHANNEL_0, 0); + test_adc_set_io_level(unit_id, ADC_CHANNEL_1, 0); + test_adc_set_io_level(unit_id, ADC_CHANNEL_2, 0); + test_adc_set_io_level(unit_id, ADC_CHANNEL_3, 0); + test_adc_set_io_level(unit_id, ADC_CHANNEL_4, 0); + test_adc_set_io_level(unit_id, ADC_CHANNEL_5, 0); + test_adc_set_io_level(unit_id, ADC_CHANNEL_6, 0); + test_adc_set_io_level(unit_id, ADC_CHANNEL_7, 0); + + vTaskDelay(10); + + int *adc_raw = (int *)&ulp_adc_raw; + + /* Verify that the LP ADC values reflect a low-state of the input pins */ + for (int i = 0; i < SOC_ADC_CHANNEL_NUM(unit_id); i++) { + printf("LP ADC low[%d] = %d\n", i, adc_raw[i]); + TEST_ASSERT_LESS_THAN_INT(ADC_TEST_LOW_VAL, adc_raw[i]); + } + + /* Set all the ADC channel IOs to high */ + test_adc_set_io_level(unit_id, ADC_CHANNEL_0, 1); + test_adc_set_io_level(unit_id, ADC_CHANNEL_1, 1); + test_adc_set_io_level(unit_id, ADC_CHANNEL_2, 1); + test_adc_set_io_level(unit_id, ADC_CHANNEL_3, 1); + test_adc_set_io_level(unit_id, ADC_CHANNEL_4, 1); + test_adc_set_io_level(unit_id, ADC_CHANNEL_5, 1); + test_adc_set_io_level(unit_id, ADC_CHANNEL_6, 1); + test_adc_set_io_level(unit_id, ADC_CHANNEL_7, 1); + + vTaskDelay(10); + + /* Verify that the LP ADC values reflect a high-state of the input pins */ + for (int i = 0; i < SOC_ADC_CHANNEL_NUM(unit_id); i++) { + printf("LP ADC high[%d] = %d\n", i, adc_raw[i]); + TEST_ASSERT_GREATER_THAN_INT(ADC_TEST_HIGH_VAL, adc_raw[i]); + } + + /* Deinit LP ADC */ + ESP_ERROR_CHECK(lp_core_lp_adc_deinit(unit_id)); +} + +TEST_CASE("LP ADC 1 raw read test", "[lp_core]") +{ + test_lp_adc(ADC_UNIT_1); +} + +// Enable when DIG-396 is fixed +// TEST_CASE("LP ADC 2 raw read test", "[lp_core]") +// { +// test_lp_adc(ADC_UNIT_2); +// } + +static void test_lp_adc_stress(adc_unit_t unit_id) +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); + + for (int i = 0; i < 100; i++) { + /* LP ADC Init */ + ESP_ERROR_CHECK(lp_core_lp_adc_init(unit_id)); + + /* LP ADC channel config */ + const lp_core_lp_adc_chan_cfg_t config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + + TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_0, &config) == ESP_OK); + + /* Set LP ADC channel IO and read raw value */ + test_adc_set_io_level(unit_id, ADC_CHANNEL_0, 1); + vTaskDelay(10); + int *adc_raw = (int *)&ulp_adc_raw; + TEST_ASSERT_NOT_EQUAL(0, adc_raw[0]); + + /* De-init LP ADC */ + ESP_ERROR_CHECK(lp_core_lp_adc_deinit(unit_id)); + } +} + +TEST_CASE("LP ADC 1 raw read stress test", "[lp_core]") +{ + test_lp_adc_stress(ADC_UNIT_1); +} + +// Enable when DIG-396 is fixed +// TEST_CASE("LP ADC 2 raw read stress test", "[lp_core]") +// { +// test_lp_adc_stress(ADC_UNIT_2); +// } + +TEST_CASE("Test temperature sensor does not affect LP ADC", "[lp_core]") +{ + printf("Install temperature sensor, expected temp ranger range: 10~50 ℃\n"); + temperature_sensor_handle_t temp_sensor = NULL; + temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); + TEST_ESP_OK(temperature_sensor_install(&temp_sensor_config, &temp_sensor)); + int cnt = 2; + float tsens_value; + while (cnt--) { + temperature_sensor_enable(temp_sensor); + TEST_ESP_OK(temperature_sensor_get_celsius(temp_sensor, &tsens_value)); + printf("Temperature value %.02f ℃\n", tsens_value); + vTaskDelay(pdMS_TO_TICKS(100)); + TEST_ESP_OK(temperature_sensor_disable(temp_sensor)); + } + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); + + /* LP ADC Init */ + ESP_ERROR_CHECK(lp_core_lp_adc_init(ADC_UNIT_1)); + + /* LP ADC channel config */ + const lp_core_lp_adc_chan_cfg_t config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + + /* Configure ADC channel 0 */ + TEST_ASSERT(lp_core_lp_adc_config_channel(ADC_UNIT_1, ADC_CHANNEL_0, &config) == ESP_OK); + + int *adc_raw = (int *)&ulp_adc_raw; + cnt = 2; + while (cnt--) { + printf("LP ADC%d Channel[%d] Raw Data: %d\n", ADC_UNIT_1 + 1, 0, adc_raw[0]); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + TEST_ESP_OK(lp_core_lp_adc_deinit(ADC_UNIT_1)); + + cnt = 2; + while (cnt--) { + temperature_sensor_enable(temp_sensor); + TEST_ESP_OK(temperature_sensor_get_celsius(temp_sensor, &tsens_value)); + printf("Temperature value %.02f ℃\n", tsens_value); + vTaskDelay(pdMS_TO_TICKS(100)); + TEST_ESP_OK(temperature_sensor_disable(temp_sensor)); + } + + TEST_ESP_OK(temperature_sensor_uninstall(temp_sensor)); +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_etm.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_etm.c new file mode 100644 index 0000000000..4e640ac0f5 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_etm.c @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "lp_core_test_app_counter.h" +#include "ulp_lp_core.h" +#include "test_shared.h" +#include "unity.h" +#include "test_utils.h" +#include "esp_log.h" +#include "driver/gptimer.h" +#include "lp_core_etm.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t lp_core_main_counter_bin_start[] asm("_binary_lp_core_test_app_counter_bin_start"); +extern const uint8_t lp_core_main_counter_bin_end[] asm("_binary_lp_core_test_app_counter_bin_end"); + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); + +} + +#define STOP_TEST_ITERATIONS 50 +#define TIMER_PERIOD_MS 10 + +static int timer_cb_count; + +static bool on_gptimer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) +{ + timer_cb_count++; + if (timer_cb_count >= STOP_TEST_ITERATIONS) { + gptimer_stop(timer); + } + return false; +} + +/** + * @brief Test connects ULP wakeup source to a GP timer alarm ETM event. + * At every wakeup the ULP program increments a counter and in the + * end we check that the ULP woke-up the expected number of times. + */ +TEST_CASE("LP core can be woken up by ETM event", "[ulp]") +{ + // GPTimer alarm ---> ETM channel A ---> ULP wake-up + printf("allocate etm channel\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + + esp_etm_task_handle_t lp_core_task = NULL; + + lp_core_etm_task_config_t lp_core_task_config = { + .task_type = LP_CORE_TASK_WAKEUP_CPU, + }; + TEST_ESP_OK(lp_core_new_etm_task(&lp_core_task_config, &lp_core_task)); + + printf("create a gptimer\r\n"); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + + printf("get gptimer etm event handle\r\n"); + esp_etm_event_handle_t gptimer_event = NULL; + gptimer_etm_event_config_t gptimer_etm_event_conf = { + .event_type = GPTIMER_ETM_EVENT_ALARM_MATCH, + }; + TEST_ESP_OK(gptimer_new_etm_event(gptimer, &gptimer_etm_event_conf, &gptimer_event)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, gptimer_event, lp_core_task)); + + printf("enable etm channel\r\n"); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + + printf("set timer alarm action\r\n"); + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = TIMER_PERIOD_MS * 1000, // 10ms per alarm event + .flags.auto_reload_on_alarm = true, + }; + TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config)); + + printf("register alarm callback\r\n"); + gptimer_event_callbacks_t cbs = { + .on_alarm = on_gptimer_alarm_cb, + }; + TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, NULL)); + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_ETM, +#if ESP_ROM_HAS_LP_ROM + .skip_lp_rom_boot = true, +#endif //ESP_ROM_HAS_LP_ROM + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_counter_bin_start, lp_core_main_counter_bin_end); + + printf("enable and start timer\r\n"); + TEST_ESP_OK(gptimer_enable(gptimer)); + TEST_ESP_OK(gptimer_start(gptimer)); + + // Wait for more than the expected time for the test to complete + // To ensure that the ULP ran exactly as many times as we expected + vTaskDelay((TIMER_PERIOD_MS * STOP_TEST_ITERATIONS * 2) / portTICK_PERIOD_MS); + + TEST_ASSERT_EQUAL(STOP_TEST_ITERATIONS, timer_cb_count); + TEST_ASSERT_EQUAL(STOP_TEST_ITERATIONS, ulp_counter); + + TEST_ESP_OK(gptimer_disable(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); + + TEST_ESP_OK(esp_etm_del_task(lp_core_task)); + TEST_ESP_OK(esp_etm_del_event(gptimer_event)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_i2c.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_i2c.c new file mode 100644 index 0000000000..a34b973dba --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_i2c.c @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "lp_core_test_app_i2c.h" +#include "ulp_lp_core.h" +#include "lp_core_i2c.h" + +#include "test_shared.h" +#include "unity.h" +#include "test_utils.h" +#include "esp_log.h" + +#include "driver/i2c.h" + +extern const uint8_t lp_core_main_i2c_bin_start[] asm("_binary_lp_core_test_app_i2c_bin_start"); +extern const uint8_t lp_core_main_i2c_bin_end[] asm("_binary_lp_core_test_app_i2c_bin_end"); + +static const char* TAG = "lp_core_i2c_test"; + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); + +} + +#define I2C_SCL_IO 7 /*! +#include "lp_core_test_app_spi_master.h" +#include "lp_core_test_app_spi_slave.h" +#include "ulp_lp_core.h" +#include "lp_core_spi.h" +#include "unity.h" +#include "test_utils.h" +#include "esp_log.h" +#include "test_shared.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t lp_core_main_spi_master_bin_start[] asm("_binary_lp_core_test_app_spi_master_bin_start"); +extern const uint8_t lp_core_main_spi_master_bin_end[] asm("_binary_lp_core_test_app_spi_master_bin_end"); +extern const uint8_t lp_core_main_spi_slave_bin_start[] asm("_binary_lp_core_test_app_spi_slave_bin_start"); +extern const uint8_t lp_core_main_spi_slave_bin_end[] asm("_binary_lp_core_test_app_spi_slave_bin_end"); + +static const char* TAG = "lp_core_spi_test"; + +#define TEST_GPIO_PIN_MISO 6 +#define TEST_GPIO_PIN_MOSI 7 +#define TEST_GPIO_PIN_CLK 8 +#define TEST_GPIO_PIN_CS 4 + +#define TEST_DATA_LEN_BYTES 42 +uint8_t expected_data[100] = {0}; + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, (firmware_end - firmware_start)) == ESP_OK); + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); +} + +static void setup_test_data(void) +{ + uint8_t *tx_data = (uint8_t *)&ulp_spi_master_tx_buf; + ulp_spi_tx_len = TEST_DATA_LEN_BYTES; + + /* Setup test data */ + for (int i = 0; i < ulp_spi_tx_len; i++) { + tx_data[i] = (i + 1) % 256; + expected_data[i] = tx_data[i]; + } +} + +static void setup_expected_data(void) +{ + ulp_spi_rx_len = TEST_DATA_LEN_BYTES; + + /* Setup expected data */ + for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) { + expected_data[i] = (i + 1) % 256; + } +} + +/* Base LP SPI bus settings */ +lp_spi_host_t host_id = 0; +lp_spi_bus_config_t bus_config = { + .miso_io_num = TEST_GPIO_PIN_MISO, + .mosi_io_num = TEST_GPIO_PIN_MOSI, + .sclk_io_num = TEST_GPIO_PIN_CLK, +}; + +/* Base LP SPI device settings */ +lp_spi_device_config_t device = { + .cs_io_num = TEST_GPIO_PIN_CS, + .spi_mode = 0, + .clock_speed_hz = 10 * 1000, // 10 MHz + .duty_cycle = 128, // 50% duty cycle +}; + +/* Base LP SPI slave device settings */ +lp_spi_slave_config_t slv_device = { + .cs_io_num = TEST_GPIO_PIN_CS, + .spi_mode = 0, +}; + +static void lp_spi_master_init(int spi_flags, bool setup_master_loop_back) +{ + /* Initialize LP SPI bus */ + /* Setup loop back for tests which do not use an LP SPI slave for looping back the data. */ + bus_config.miso_io_num = setup_master_loop_back ? TEST_GPIO_PIN_MOSI : TEST_GPIO_PIN_MISO; + TEST_ASSERT(lp_core_lp_spi_bus_initialize(host_id, &bus_config) == ESP_OK); + + /* Add LP SPI device */ + device.flags = spi_flags; + TEST_ASSERT(lp_core_lp_spi_bus_add_device(host_id, &device) == ESP_OK); +} + +static void lp_spi_slave_init(int spi_flags) +{ + /* Initialize LP SPI bus */ + TEST_ASSERT(lp_core_lp_spi_bus_initialize(host_id, &bus_config) == ESP_OK); + + /* Add LP SPI slave device */ + if (spi_flags != 0) { + slv_device.flags = spi_flags; + } + TEST_ASSERT(lp_core_lp_spi_slave_initialize(host_id, &slv_device) == ESP_OK); +} + +static void lp_spi_master_execute_test(bool wait_for_slave_ready) +{ + /* Load and run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_spi_master_bin_start, lp_core_main_spi_master_bin_end); + + if (wait_for_slave_ready) { + /* Wait for the HP SPI device to be initialized */ + unity_wait_for_signal("LP SPI slave ready"); + } + + /* Setup test data */ + setup_test_data(); + + /* Start the test */ + ulp_spi_test_cmd = LP_CORE_LP_SPI_WRITE_READ_TEST; + + while (ulp_spi_test_cmd != LP_CORE_NO_COMMAND) { + /* Wait for the test to complete */ + vTaskDelay(1); + } + + /* Verify the received data if we expect the data to be looped back from the LP SPI slave */ + uint8_t *rx_data = (uint8_t *)&ulp_spi_master_rx_buf; + for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) { + ESP_LOGI(TAG, "LP SPI master received data: 0x%02x", rx_data[i]); + } + + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_data, ulp_spi_tx_len); +} + +static void lp_spi_slave_execute_test(void) +{ + /* Load and run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_spi_slave_bin_start, lp_core_main_spi_slave_bin_end); + + /* Setup expected test data */ + setup_expected_data(); + + /* Send signal to LP SPI master */ + unity_send_signal("LP SPI slave ready"); + + /* Wait for the test to complete */ + while (ulp_spi_test_cmd_reply != LP_CORE_COMMAND_OK) { + vTaskDelay(1); + } + + /* Verify the received data */ + uint8_t *rx_data = (uint8_t *)&ulp_spi_slave_rx_buf; + for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) { + ESP_LOGI(TAG, "LP SPI slave received data: 0x%02x", rx_data[i]); + } + + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_data, TEST_DATA_LEN_BYTES); +} + +void test_lp_spi_master(void) +{ + /* Initialize LP SPI in master mode */ + lp_spi_master_init(0, false); + + /* Start the LP SPI master test */ + lp_spi_master_execute_test(true); +} + +void test_lp_spi_slave(void) +{ + /* Initialize LP SPI in slave mode */ + lp_spi_slave_init(0); + + /* Start the LP SPI slave test */ + lp_spi_slave_execute_test(); +} +void test_lp_spi_master_3wire(void) +{ + /* Initialize LP SPI in master mode */ + int spi_flags = LP_SPI_DEVICE_3WIRE; + lp_spi_master_init(spi_flags, false); + + /* Start the LP SPI master test */ + lp_spi_master_execute_test(true); +} + +void test_lp_spi_slave_3wire(void) +{ + /* Initialize LP SPI in slave mode */ + int spi_flags = LP_SPI_DEVICE_3WIRE; + lp_spi_slave_init(spi_flags); + + /* Start the LP SPI slave test */ + lp_spi_slave_execute_test(); +} + +void test_lp_spi_master_lsbfirst(void) +{ + /* Initialize LP SPI in master mode */ + int spi_flags = LP_SPI_DEVICE_BIT_LSBFIRST; + lp_spi_master_init(spi_flags, false); + + /* Start the LP SPI master test */ + lp_spi_master_execute_test(true); +} + +void test_lp_spi_slave_lsbfirst(void) +{ + /* Initialize LP SPI in slave mode */ + int spi_flags = LP_SPI_DEVICE_BIT_LSBFIRST; + lp_spi_slave_init(spi_flags); + + /* Start the LP SPI slave test */ + lp_spi_slave_execute_test(); +} + +/* Test LP-SPI master loopback */ +TEST_CASE("LP-Core LP-SPI master loopback test", "[lp_core]") +{ + /* Initialize LP SPI in master mode */ + lp_spi_master_init(0, true); + + /* Start the LP SPI master test */ + lp_spi_master_execute_test(false); +} + +/* Test LP-SPI master loopback with active low CS line */ +TEST_CASE("LP-Core LP-SPI master loopback test with active high CS line", "[lp_core]") +{ + /* Initialize LP SPI in master mode */ + int spi_flags = LP_SPI_DEVICE_CS_ACTIVE_HIGH; + lp_spi_master_init(spi_flags, true); + + /* Start the LP SPI master test */ + lp_spi_master_execute_test(false); +} + +/* Test LP-SPI master and LP-SPI slave communication */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-SPI master and LP-SPI slave read write test", "[lp_core_spi][test_env=generic_multi_device][timeout=150]", test_lp_spi_master, test_lp_spi_slave); + +/* Test LP-SPI master in 3-Wire SPI mode */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-SPI master and LP-SPI slave in 3-Wire SPI mode", "[lp_core_spi][test_env=generic_multi_device][timeout=150]", test_lp_spi_master_3wire, test_lp_spi_slave_3wire); + +/* Test LP-SPI master and LP-SPI slave in LSB first mode */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-SPI master and LP-SPI in LSB first SPI mode", "[lp_core_spi][test_env=generic_multi_device][timeout=150]", test_lp_spi_master_lsbfirst, test_lp_spi_slave_lsbfirst); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c new file mode 100644 index 0000000000..563f69ca1a --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c @@ -0,0 +1,666 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "hal/uart_types.h" +#include "lp_core_test_app_uart.h" +#include "portmacro.h" +#include "ulp_lp_core.h" +#include "lp_core_uart.h" + +#include "test_shared.h" +#include "unity.h" +#include "test_utils.h" +#include "esp_log.h" + +#include "driver/uart.h" + +extern const uint8_t lp_core_main_uart_bin_start[] asm("_binary_lp_core_test_app_uart_bin_start"); +extern const uint8_t lp_core_main_uart_bin_end[] asm("_binary_lp_core_test_app_uart_bin_end"); + +static const char* TAG = "lp_core_uart_test"; + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); + +} + +TEST_CASE("LP-Core LP-UART initialization test", "[lp_core]") +{ + /* Default UART configuration must be successful */ + ESP_LOGI(TAG, "Verifying default LP UART configuration"); + lp_core_uart_cfg_t cfg = LP_CORE_UART_DEFAULT_CONFIG(); + TEST_ASSERT(ESP_OK == lp_core_uart_init(&cfg)); + + /* NULL configuration should result in an error */ + ESP_LOGI(TAG, "Verifying NULL configuration"); + TEST_ASSERT(ESP_OK != lp_core_uart_init(NULL)); + + /* RX Flow control must be less than SOC_LP_UART_FIFO_LEN */ + ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Rx Flow Control Threshold"); + lp_core_uart_cfg_t cfg1 = LP_CORE_UART_DEFAULT_CONFIG(); + cfg1.uart_proto_cfg.rx_flow_ctrl_thresh = SOC_LP_UART_FIFO_LEN + 1; + TEST_ASSERT(ESP_OK != lp_core_uart_init(&cfg1)); + +#if !SOC_LP_GPIO_MATRIX_SUPPORTED + /* If LP_GPIO Matrix is not supported then the UART pins must be fixed */ + ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Tx IO pin"); + lp_core_uart_cfg_t cfg2 = LP_CORE_UART_DEFAULT_CONFIG(); + cfg2.uart_pin_cfg.tx_io_num++; + TEST_ASSERT(ESP_OK != lp_core_uart_init(&cfg2)); + + ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Rx IO pin"); + lp_core_uart_cfg_t cfg3 = LP_CORE_UART_DEFAULT_CONFIG(); + cfg3.uart_pin_cfg.rx_io_num--; + TEST_ASSERT(ESP_OK != lp_core_uart_init(&cfg3)); +#else + /* When LP_GPIO Matrix is supported then any valid LP_IO should be configurable */ + ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Tx IO pin"); + lp_core_uart_cfg_t cfg4 = LP_CORE_UART_DEFAULT_CONFIG(); + cfg4.uart_pin_cfg.tx_io_num++; + TEST_ASSERT(ESP_OK == lp_core_uart_init(&cfg4)); + + ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Rx IO pin"); + lp_core_uart_cfg_t cfg5 = LP_CORE_UART_DEFAULT_CONFIG(); + cfg5.uart_pin_cfg.rx_io_num--; + TEST_ASSERT(ESP_OK == lp_core_uart_init(&cfg5)); +#endif /* !SOC_LP_GPIO_MATRIX_SUPPORTED */ +} + +/* LP UART default config */ +static lp_core_uart_cfg_t lp_uart_cfg = LP_CORE_UART_DEFAULT_CONFIG(); + +/* LP UART non-default configuration */ +static lp_core_uart_cfg_t lp_uart_cfg1 = { + .uart_proto_cfg.baud_rate = 9600, + .uart_proto_cfg.data_bits = UART_DATA_8_BITS, + .uart_proto_cfg.parity = UART_PARITY_ODD, + .uart_proto_cfg.stop_bits = UART_STOP_BITS_2, + .uart_proto_cfg.rx_flow_ctrl_thresh = 0, + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + LP_UART_DEFAULT_GPIO_CONFIG() +}; + +/* Global test data */ +const uint8_t start_pattern[4] = {0xDE, 0xAD, 0xBE, 0xEF}; +const uint8_t end_pattern[4] = {0xFE, 0xED, 0xBE, 0xEF}; +uint8_t expected_rx_data[UART_BUF_SIZE]; +#define TEST_DATA_LEN 234 // Select a random number of bytes to transmit +char test_string[25]; +char test_long_string[200]; +int test_signed_integer; +uint32_t test_unsigned_integer; +int test_hex; +char test_character; + +static void setup_test_data(uint8_t *tx_data, uint8_t *rx_data) +{ + if (tx_data) { + /* Copy the start pattern followed by the test data */ + memcpy(tx_data, start_pattern, sizeof(start_pattern)); + int i = 0; + for (i = sizeof(start_pattern); i < TEST_DATA_LEN + sizeof(start_pattern); i++) { + tx_data[i] = i + 7 - sizeof(start_pattern); // We use test data which is i + 7 + } + /* Copy the end pattern to mark the end of transmission. + * This is used by the LP core during read operations to + * notify the HP core of test completion. + */ + memcpy(tx_data + i, end_pattern, sizeof(end_pattern)); + } + + if (rx_data) { + for (int i = 0; i < TEST_DATA_LEN; i++) { + expected_rx_data[i] = i + 7; + } + } +} + +static void setup_test_print_data(void) +{ + strcpy(test_string, "Test printf string"); + strcpy(test_long_string, "Print a very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"); + test_signed_integer = -77; + test_unsigned_integer = 1234567890; + test_hex = 0xa; + test_character = 'n'; +} + +static void hp_uart_read(void) +{ + /* Configure HP UART driver */ + uart_config_t hp_uart_cfg = { + .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, + .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, + .parity = lp_uart_cfg.uart_proto_cfg.parity, + .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, + .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; + +#if CONFIG_UART_ISR_IN_IRAM + intr_alloc_flags = ESP_INTR_FLAG_IRAM; +#endif + + /* Install HP UART driver */ + ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + + /* Cross-connect the HP UART pins and the LP UART pins for the test */ + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg.uart_pin_cfg.rx_io_num, lp_uart_cfg.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + /* Setup test data */ + setup_test_data(NULL, expected_rx_data); + + /* Notify the LP UART that the HP UART is initialized */ + unity_send_signal("HP UART init done"); + + /* Receive data from LP UART */ + int bytes_remaining = TEST_DATA_LEN + sizeof(start_pattern); + uint8_t rx_data[UART_BUF_SIZE]; + int recv_idx = 0; + while (bytes_remaining > 0) { + int bytes_received = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); + if (bytes_received < 0) { + TEST_FAIL_MESSAGE("HP UART read error"); + } else if (bytes_received > 0) { + recv_idx += bytes_received; + bytes_remaining -= bytes_received; + } + } + + /* Check if we received the start pattern */ + int data_idx = -1; + for (int i = 0; i < UART_BUF_SIZE; i++) { + if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { + data_idx = i + 4; // Index of byte just after the start_pattern + } + } + + /* Test should pass if we received the start_pattern */ + TEST_ASSERT_NOT_EQUAL(-1, data_idx); + + /* Verify test data */ + ESP_LOGI(TAG, "Verify Rx data"); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); + + /* Uninstall the HP UART driver */ + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +static void test_lp_uart_write(void) +{ + /* Setup LP UART with default configuration */ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); + + /* Wait for the HP UART device to be initialized */ + unity_wait_for_signal("HP UART init done"); + + /* Load and Run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + /* Setup test data */ + setup_test_data((uint8_t *)&ulp_tx_data, NULL); + ulp_tx_len = TEST_DATA_LEN + sizeof(start_pattern); + + /* Start the test */ + ESP_LOGI(TAG, "Write test start"); + ulp_test_cmd = LP_CORE_LP_UART_WRITE_TEST; +} + +static void hp_uart_read_options(void) +{ + /* Wait for LP UART to be initialized first */ + unity_wait_for_signal("LP UART init done"); + + /* Configure HP UART driver */ + uart_config_t hp_uart_cfg = { + .baud_rate = lp_uart_cfg1.uart_proto_cfg.baud_rate, + .data_bits = lp_uart_cfg1.uart_proto_cfg.data_bits, + .parity = lp_uart_cfg1.uart_proto_cfg.parity, + .stop_bits = lp_uart_cfg1.uart_proto_cfg.stop_bits, + .flow_ctrl = lp_uart_cfg1.uart_proto_cfg.flow_ctrl, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; + +#if CONFIG_UART_ISR_IN_IRAM + intr_alloc_flags = ESP_INTR_FLAG_IRAM; +#endif + + /* Install HP UART driver */ + ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + + /* Cross-connect the HP UART pins and the LP UART pins for the test */ + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg1.uart_pin_cfg.rx_io_num, lp_uart_cfg1.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + /* Setup test data */ + setup_test_data(NULL, expected_rx_data); + + /* Notify the LP UART that the HP UART is initialized */ + unity_send_signal("HP UART init done"); + + /* Receive data from LP UART */ + int bytes_remaining = TEST_DATA_LEN + sizeof(start_pattern); + uint8_t rx_data[UART_BUF_SIZE]; + + int recv_idx = 0; + while (bytes_remaining > 0) { + int bytes_received = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); + if (bytes_received < 0) { + TEST_FAIL_MESSAGE("HP UART read error"); + } else if (bytes_received > 0) { + recv_idx += bytes_received; + bytes_remaining -= bytes_received; + } + } + + /* Check if we received the start pattern */ + int data_idx = -1; + for (int i = 0; i < UART_BUF_SIZE; i++) { + if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { + data_idx = i + 4; // Index of byte just after the start_pattern + } + } + + /* Test should pass if we received the start_pattern */ + TEST_ASSERT_NOT_EQUAL(-1, data_idx); + + /* Verify test data */ + ESP_LOGI(TAG, "Verify Rx data"); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); + + /* Uninstall the HP UART driver */ + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +static void test_lp_uart_write_options(void) +{ + /* Setup LP UART with updated configuration */ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg1)); + + /* Notify HP UART once LP UART is initialized */ + unity_send_signal("LP UART init done"); + + /* Wait for the HP UART device to be initialized */ + unity_wait_for_signal("HP UART init done"); + + /* Load and Run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + /* Setup test data */ + setup_test_data((uint8_t *)&ulp_tx_data, NULL); + ulp_tx_len = TEST_DATA_LEN + sizeof(start_pattern); + + /* Start the test */ + ESP_LOGI(TAG, "Write test start"); + ulp_test_cmd = LP_CORE_LP_UART_WRITE_TEST; +} + +static void hp_uart_write(void) +{ + /* Configure HP UART driver */ + uart_config_t hp_uart_cfg = { + .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, + .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, + .parity = lp_uart_cfg.uart_proto_cfg.parity, + .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, + .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; + +#if CONFIG_UART_ISR_IN_IRAM + intr_alloc_flags = ESP_INTR_FLAG_IRAM; +#endif + + /* Install HP UART driver */ + ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + + /* Cross-connect the HP UART pins and the LP UART pins for the test */ + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg.uart_pin_cfg.rx_io_num, lp_uart_cfg.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + /* Setup test data */ + uint8_t tx_data[UART_BUF_SIZE]; + setup_test_data(tx_data, NULL); + + /* Notify the LP UART that the HP UART is initialized */ + unity_send_signal("HP UART init done"); + + /* Wait for the LP UART to be in receiving state */ + unity_wait_for_signal("LP UART recv ready"); + + /* Write data to LP UART */ + uart_write_bytes(UART_NUM_1, (const char *)tx_data, TEST_DATA_LEN + sizeof(start_pattern) + sizeof(end_pattern)); + + /* Wait for the LP UART receive data done */ + unity_wait_for_signal("LP UART recv data done"); + + /* Uninstall the HP UART driver */ + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +static void test_lp_uart_read(void) +{ + /* Setup LP UART with updated configuration */ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); + + /* Wait for the HP UART device to be initialized */ + unity_wait_for_signal("HP UART init done"); + + /* Load and Run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + /* Setup test data */ + setup_test_data(NULL, expected_rx_data); + + /* Start the test */ + ESP_LOGI(TAG, "Read test start"); + ulp_test_cmd = LP_CORE_LP_UART_READ_TEST; + vTaskDelay(10); + + /* Notify the HP UART to write data */ + unity_send_signal("LP UART recv ready"); + + /* Wait for test completion */ + while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { + vTaskDelay(10); + } + + /* Check if we received the start pattern */ + uint8_t *rx_data = (uint8_t*)&ulp_rx_data; + int data_idx = -1; + for (int i = 0; i < UART_BUF_SIZE; i++) { + if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { + data_idx = i + 4; // Index of byte just after the start_pattern + } + } + + /* Verify test data */ + ESP_LOGI(TAG, "Verify Rx data"); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); + + /* Notify the HP UART data received done and delete the UART driver */ + unity_send_signal("LP UART recv data done"); +} + +static void hp_uart_write_options(void) +{ + /* Configure HP UART driver */ + uart_config_t hp_uart_cfg = { + .baud_rate = lp_uart_cfg1.uart_proto_cfg.baud_rate, + .data_bits = lp_uart_cfg1.uart_proto_cfg.data_bits, + .parity = lp_uart_cfg1.uart_proto_cfg.parity, + .stop_bits = lp_uart_cfg1.uart_proto_cfg.stop_bits, + .flow_ctrl = lp_uart_cfg1.uart_proto_cfg.flow_ctrl, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; + +#if CONFIG_UART_ISR_IN_IRAM + intr_alloc_flags = ESP_INTR_FLAG_IRAM; +#endif + + /* Install HP UART driver */ + ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + + /* Cross-connect the HP UART pins and the LP UART pins for the test */ + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg1.uart_pin_cfg.rx_io_num, lp_uart_cfg1.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + /* Setup test data */ + uint8_t tx_data[UART_BUF_SIZE]; + setup_test_data(tx_data, NULL); + + /* Notify the LP UART that the HP UART is initialized */ + unity_send_signal("HP UART init done"); + + /* Wait for the LP UART to be in receiving state */ + unity_wait_for_signal("LP UART recv ready"); + + /* Write data to LP UART */ + uart_write_bytes(UART_NUM_1, (const char *)tx_data, TEST_DATA_LEN + sizeof(start_pattern) + sizeof(end_pattern)); + + /* Wait for the LP UART receive data done */ + unity_wait_for_signal("LP UART recv data done"); + + /* Uninstall the HP UART driver */ + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +static void test_lp_uart_read_options(void) +{ + /* Setup LP UART with updated configuration */ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg1)); + + /* Wait for the HP UART device to be initialized */ + unity_wait_for_signal("HP UART init done"); + + /* Load and Run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + /* Setup test data */ + setup_test_data(NULL, expected_rx_data); + + /* Start the test */ + ESP_LOGI(TAG, "Read test start"); + ulp_test_cmd = LP_CORE_LP_UART_READ_TEST; + vTaskDelay(10); + + /* Notify the HP UART to write data */ + unity_send_signal("LP UART recv ready"); + + /* Wait for test completion */ + while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { + vTaskDelay(10); + } + + /* Check if we received the start pattern */ + uint8_t *rx_data = (uint8_t*)&ulp_rx_data; + int data_idx = -1; + for (int i = 0; i < UART_BUF_SIZE; i++) { + if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { + data_idx = i + 4; // Index of byte just after the start_pattern + } + } + + /* Verify test data */ + ESP_LOGI(TAG, "Verify Rx data"); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); + + /* Notify the HP UART data received done and delete the UART driver */ + unity_send_signal("LP UART recv data done"); +} + +static void test_lp_uart_read_multi_byte(void) +{ + /* Setup LP UART with updated configuration */ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); + + /* Wait for the HP UART device to be initialized */ + unity_wait_for_signal("HP UART init done"); + + /* Load and Run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + /* Setup test data */ + setup_test_data(NULL, expected_rx_data); + ulp_rx_len = TEST_DATA_LEN + sizeof(start_pattern); + + /* Start the test */ + ESP_LOGI(TAG, "Read test start"); + ulp_test_cmd = LP_CORE_LP_UART_MULTI_BYTE_READ_TEST; + vTaskDelay(10); + + /* Notify the HP UART to write data */ + unity_send_signal("LP UART recv ready"); + + /* Wait for test completion */ + while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { + vTaskDelay(10); + } + + /* Check if we received the start pattern */ + uint8_t *rx_data = (uint8_t*)&ulp_rx_data; + int data_idx = -1; + for (int i = 0; i < UART_BUF_SIZE; i++) { + if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { + data_idx = i + 4; // Index of byte just after the start_pattern + } + } + + /* Verify test data. We verify 10 bytes less because the multi-device can sometimes + * begin with garbage data which fills the initial part of the receive buffer. */ + ESP_LOGI(TAG, "Verify Rx data"); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN - 10); + + /* Notify the HP UART data received done and delete the UART driver */ + unity_send_signal("LP UART recv data done"); +} + +static void hp_uart_read_print(void) +{ + /* Configure HP UART driver */ + uart_config_t hp_uart_cfg = { + .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, + .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, + .parity = lp_uart_cfg.uart_proto_cfg.parity, + .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, + .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; + +#if CONFIG_UART_ISR_IN_IRAM + intr_alloc_flags = ESP_INTR_FLAG_IRAM; +#endif + + /* Install HP UART driver */ + ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + + /* Cross-connect the HP UART pins and the LP UART pins for the test */ + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg.uart_pin_cfg.rx_io_num, lp_uart_cfg.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + /* Notify the LP UART that the HP UART is initialized */ + unity_send_signal("HP UART init done"); + + /* Setup test data */ + setup_test_print_data(); + + /* Receive data from LP UART */ + uint8_t rx_data[UART_BUF_SIZE]; + int recv_idx = 0; + int idle_count = 0; + while (1) { + int bytes_received = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); + if (bytes_received < 0) { + TEST_FAIL_MESSAGE("HP UART read error"); + } else if (bytes_received > 0) { + recv_idx += bytes_received; + } else if (bytes_received == 0) { + idle_count++; + vTaskDelay(10); + if (idle_count > 10) { + break; + } + } + } + rx_data[UART_BUF_SIZE - 1] = '\0'; + + /* Parse the rx_data to verify the printed data */ + /* Search for expected_string in rx_data. Report test pass if the string is found */ + char expected_string[TEST_DATA_LEN]; + snprintf(expected_string, TEST_DATA_LEN, "%s", test_string); + TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); + + snprintf(expected_string, TEST_DATA_LEN, "%s", test_long_string); + TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); + + snprintf(expected_string, TEST_DATA_LEN, "Test printf signed integer %d", test_signed_integer); + TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); + + snprintf(expected_string, TEST_DATA_LEN, "Test printf unsigned integer %lu", test_unsigned_integer); + TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); + + snprintf(expected_string, TEST_DATA_LEN, "Test printf hex %x", test_hex); + TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); + + snprintf(expected_string, TEST_DATA_LEN, "Test printf character %c", test_character); + TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); + + /* Uninstall the HP UART driver */ + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +static void test_lp_uart_print(void) +{ + /* Setup LP UART with default configuration */ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); + + /* Wait for the HP UART device to be initialized */ + unity_wait_for_signal("HP UART init done"); + + /* Load and Run the LP core firmware */ + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + /* Setup test data */ + setup_test_print_data(); + strcpy((char *)&ulp_test_string, test_string); + strcpy((char *)&ulp_test_long_string, test_long_string); + ulp_test_signed_integer = test_signed_integer; + ulp_test_unsigned_integer = test_unsigned_integer; + ulp_test_hex = test_hex; + ulp_test_character = test_character; + + /* Start the test */ + ESP_LOGI(TAG, "LP Core print test start"); + ulp_test_cmd = LP_CORE_LP_UART_PRINT_TEST; +} + +/* Test LP UART write operation with default LP UART initialization configuration */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - default config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_write, hp_uart_read); +/* Test LP UART write operation with updated LP UART initialization configuration */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - optional config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_write_options, hp_uart_read_options); +/* Test LP UART read operation with default LP UART initialization configuration */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - default config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_read, hp_uart_write); +/* Test LP UART read operation with updated LP UART initialization configuration */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - optional config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_read_options, hp_uart_write_options); +/* Test LP UART multi-byte read operation */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART multi-byte read test", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_read_multi_byte, hp_uart_write); +/* Test LP Core print */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART print test", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_print, hp_uart_read_print); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_vad.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_vad.c new file mode 100644 index 0000000000..16a3c059dd --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_vad.c @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/lp_i2s.h" +#include "driver/lp_i2s_std.h" +#include "driver/i2s_std.h" +#include "ulp_lp_core.h" +#include "ulp_lp_core_lp_vad_shared.h" +#include "lp_core_test_app_vad.h" + +#if CONFIG_TEST_LP_CORE_VAD_ENABLE + +#define TEST_I2S_FRAME_SIZE (128) // Frame numbers in every writing / reading +#define TEST_I2S_TRANS_SIZE (4096) // Trans size + +extern const uint8_t test_vad_pcm_start[] asm("_binary_test_vad_8k_pcm_start"); +extern const uint8_t test_vad_pcm_end[] asm("_binary_test_vad_8k_pcm_end"); +extern const uint8_t lp_core_main_vad_bin_start[] asm("_binary_lp_core_test_app_vad_bin_start"); +extern const uint8_t lp_core_main_vad_bin_end[] asm("_binary_lp_core_test_app_vad_bin_end"); +static const char *TAG = "TEST_VAD"; + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); +} + +void test_lp_vad(lp_vad_t vad_id) +{ + esp_err_t ret = ESP_FAIL; + int pcm_size = test_vad_pcm_end - test_vad_pcm_start; + printf("pcm_size: %d\n", pcm_size); + + lp_i2s_chan_handle_t rx_handle = NULL; + lp_i2s_chan_config_t config = { + .id = 0, + .role = I2S_ROLE_SLAVE, + .threshold = 512, + }; + TEST_ESP_OK(lp_i2s_new_channel(&config, NULL, &rx_handle)); + + i2s_chan_handle_t tx_handle = NULL; + i2s_chan_config_t i2s_channel_config = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 4, + .dma_frame_num = TEST_I2S_FRAME_SIZE, + .auto_clear = false, + }; + TEST_ESP_OK(i2s_new_channel(&i2s_channel_config, &tx_handle, NULL)); + + lp_i2s_std_config_t lp_std_cfg = { + .pin_cfg = { + .bck = 4, + .ws = 5, + .din = 6, + }, + }; + lp_std_cfg.slot_cfg = (lp_i2s_std_slot_config_t)LP_I2S_STD_PCM_SHORT_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + TEST_ESP_OK(lp_i2s_channel_init_std_mode(rx_handle, &lp_std_cfg)); + + i2s_std_config_t i2s_std_config = { + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = GPIO_NUM_7, + .ws = GPIO_NUM_8, + .dout = GPIO_NUM_21, + .din = -1, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + i2s_std_config.clk_cfg = (i2s_std_clk_config_t)I2S_STD_CLK_DEFAULT_CONFIG(16000); + i2s_std_config.slot_cfg = (i2s_std_slot_config_t)I2S_STD_PCM_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &i2s_std_config)); + + // LP VAD Init + lp_vad_init_config_t init_config = { + .lp_i2s_chan = rx_handle, + .vad_config = { + .init_frame_num = 100, + .min_energy_thresh = 100, + .speak_activity_thresh = 10, + .non_speak_activity_thresh = 30, + .min_speak_activity_thresh = 3, + .max_speak_activity_thresh = 100, + }, + }; + TEST_ESP_OK(lp_core_lp_vad_init(0, &init_config)); + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_VAD, + }; + load_and_start_lp_core_firmware(&cfg, lp_core_main_vad_bin_start, lp_core_main_vad_bin_end); + + uint8_t *txbuf = (uint8_t *)heap_caps_calloc(1, TEST_I2S_TRANS_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(txbuf); + + uint8_t *prebuf = (uint8_t *)heap_caps_calloc(1, TEST_I2S_TRANS_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(prebuf); + + memcpy(prebuf, test_vad_pcm_start, TEST_I2S_TRANS_SIZE); + memcpy(txbuf, test_vad_pcm_start, TEST_I2S_TRANS_SIZE); + + for (int i = 0; i < TEST_I2S_TRANS_SIZE; i++) { + ESP_LOGD(TAG, "prebuf[%d]: %d", i, prebuf[i]); + ESP_LOGD(TAG, "txbuf[%d]: %d", i, txbuf[i]); + } + + size_t bytes_written = 0; + TEST_ESP_OK(i2s_channel_preload_data(tx_handle, prebuf, TEST_I2S_TRANS_SIZE, &bytes_written)); + TEST_ESP_OK(lp_i2s_channel_enable(rx_handle)); + TEST_ESP_OK(lp_core_lp_vad_enable(0)); + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + + ret = i2s_channel_write(tx_handle, txbuf, TEST_I2S_TRANS_SIZE, &bytes_written, 0); + if (ret != ESP_OK && ret != ESP_ERR_TIMEOUT) { + TEST_ESP_OK(ret); + } + ESP_LOGD(TAG, "bytes_written: %d", bytes_written); + + while (!ulp_vad_wakup) { + ; + } + + ESP_LOGI(TAG, "wakeup"); + + TEST_ESP_OK(lp_i2s_channel_disable(rx_handle)); + TEST_ESP_OK(lp_i2s_del_channel(rx_handle)); + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + TEST_ESP_OK(i2s_del_channel(tx_handle)); + free(txbuf); + free(prebuf); +} + +TEST_CASE("LP VAD wakeup test", "[lp_core][lp_vad]") +{ + test_lp_vad(0); +} +#endif //CONFIG_TEST_LP_CORE_VAD_ENABLE diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_vad_8k.pcm b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_vad_8k.pcm new file mode 100644 index 0000000000..5b6c32a037 Binary files /dev/null and b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_vad_8k.pcm differ diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py new file mode 100644 index 0000000000..33e7b3c950 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c5 +@pytest.mark.esp32c6 +@pytest.mark.esp32p4 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'default', + ], + indirect=True, +) +def test_lp_core(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32c5 +@pytest.mark.esp32p4 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'xtal', + ], + indirect=True, +) +def test_lp_core_xtal(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32p4 +@pytest.mark.lp_i2s +@pytest.mark.parametrize( + 'config', + [ + 'lp_vad', + ], + indirect=True, +) +def test_lp_vad(dut: Dut) -> None: + dut.run_all_single_board_cases(group='lp_vad') + + +@pytest.mark.esp32c6 +# TODO: Enable LP I2C test for esp32p4 (IDF-9407) +@pytest.mark.generic_multi_device +@pytest.mark.parametrize( + 'count', [2], indirect=True +) +def test_lp_core_multi_device(case_tester) -> None: # type: ignore + for case in case_tester.test_menu: + if case.attributes.get('test_env', 'generic_multi_device') == 'generic_multi_device': + case_tester.run_multi_dev_case(case=case, reset=True) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.lp_vad b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.lp_vad new file mode 100644 index 0000000000..766d6c83cd --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.lp_vad @@ -0,0 +1 @@ +CONFIG_TEST_LP_CORE_VAD_ENABLE=y diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.xtal b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.xtal new file mode 100644 index 0000000000..340667e4bd --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.xtal @@ -0,0 +1 @@ +CONFIG_RTC_FAST_CLK_SRC_XTAL=y diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.defaults b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.defaults new file mode 100644 index 0000000000..c7626f4733 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_ESP_TASK_WDT_INIT=n + +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=12000 +CONFIG_ULP_PANIC_OUTPUT_ENABLE=y diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_hp_uart/CMakeLists.txt new file mode 100644 index 0000000000..2c8bfb9861 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/CMakeLists.txt @@ -0,0 +1,14 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") + +set(EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/unit-test-app/components" +) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(lp_core_hp_uart_test) diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/README.md b/components/ulp/test_apps/lp_core/lp_core_hp_uart/README.md new file mode 100644 index 0000000000..59db987a22 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/README.md @@ -0,0 +1,3 @@ +| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 | +| ----------------- | -------- | -------- | -------- | + diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/CMakeLists.txt new file mode 100644 index 0000000000..a1ca21afaf --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/CMakeLists.txt @@ -0,0 +1,20 @@ +set(app_sources "test_app_main.c" "test_lp_core.c") +set(lp_core_sources "lp_core/test_hello_main.c") +set(lp_core_sources_panic "lp_core/test_panic_main.c") +set(lp_core_sources_shared_mem "lp_core/test_shared_mem_main.c") +set(lp_core_sources_lp_rom "lp_core/test_lp_rom_main.c") + +idf_component_register(SRCS ${app_sources} + INCLUDE_DIRS "lp_core" + REQUIRES ulp unity esp_timer test_utils + WHOLE_ARCHIVE) + +set(lp_core_exp_dep_srcs ${app_sources}) + +ulp_embed_binary(lp_core_test_app "${lp_core_sources}" "${lp_core_exp_dep_srcs}") +ulp_embed_binary(lp_core_test_app_panic "${lp_core_sources_panic}" "${lp_core_exp_dep_srcs}") +ulp_embed_binary(lp_core_test_app_shared_mem "${lp_core_sources_shared_mem}" "${lp_core_exp_dep_srcs}") + +if(CONFIG_ESP_ROM_HAS_LP_ROM) + ulp_embed_binary(lp_core_test_app_lp_rom "${lp_core_sources_lp_rom}" "${lp_core_exp_dep_srcs}") +endif() diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_hello_main.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_hello_main.c new file mode 100644 index 0000000000..504aedd026 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_hello_main.c @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "ulp_lp_core_print.h" + +volatile int hex_value = 0x1234ABCD; +volatile int dec_value = 56; + +int main(void) +{ + lp_core_printf("Hello, World!\n"); + + lp_core_print_hex(hex_value); + lp_core_print_char('\n'); + + lp_core_print_dec_two_digits(dec_value); + lp_core_print_char('\n'); + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_lp_rom_main.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_lp_rom_main.c new file mode 100644 index 0000000000..6a99e116f7 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_lp_rom_main.c @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "soc/soc.h" +#include "ulp_lp_core_print.h" +#include + +void assert_function_in_rom(void *func) +{ + if ((intptr_t)func < SOC_LP_ROM_LOW || (intptr_t)func > SOC_LP_ROM_HIGH) { + abort(); + } +} + +static void test_memset(void) +{ +#define TEST_MEMSET_VAL 0xAB + assert_function_in_rom(memset); + + lp_core_printf("Testing memset\n"); + uint8_t test_buf[100]; + + memset(test_buf, TEST_MEMSET_VAL, sizeof(test_buf)); + + for (int i = 0; i < sizeof(test_buf); i++) { + if (test_buf[i] != TEST_MEMSET_VAL) { + lp_core_printf("test_buf[%d]: 0x%X != 0x%X\n", i, test_buf[i], TEST_MEMSET_VAL); + abort(); + } + } +} + +static void test_memcpy(void) +{ +#define TEST_MEMCPY_VAL 0xAC +#define TEST_SIZE 100 + + assert_function_in_rom(memcpy); + lp_core_printf("Testing memcpy\n"); + uint8_t test_buf_a[TEST_SIZE]; + memset(test_buf_a, TEST_MEMCPY_VAL, TEST_SIZE); + + uint8_t test_buf_b[TEST_SIZE]; + + memcpy(test_buf_b, test_buf_a, TEST_SIZE); + + for (int i = 0; i < TEST_SIZE; i++) { + if (test_buf_b[i] != TEST_MEMCPY_VAL) { + lp_core_printf("test_buf_b[%d]: 0x%X != 0x%X\n", i, test_buf_b[i], TEST_MEMCPY_VAL); + abort(); + } + } +} + +static void test_abs(void) +{ + assert_function_in_rom(abs); + lp_core_printf("Testing abs\n"); + if (abs(-123) != 123) { + lp_core_printf("Failed abs() test\n"); + abort(); + } +} + +volatile bool lp_rom_test_finished; + +int main(void) +{ + // Test a misc of ROM functions to catch any regression with LD file updates + test_memset(); + test_memcpy(); + test_abs(); + + lp_core_printf("ULP: all tests passed\n"); + lp_rom_test_finished = true; + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_panic_main.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_panic_main.c new file mode 100644 index 0000000000..4985426beb --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_panic_main.c @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +int main(void) +{ + asm volatile("ebreak"); + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_shared.h b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_shared.h new file mode 100644 index 0000000000..17571e195c --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_shared.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#define SHARED_MEM_INIT_VALUE 0xEE +#define SHARED_MEM_END_VALUE 0xAA diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_shared_mem_main.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_shared_mem_main.c new file mode 100644 index 0000000000..c37c83962c --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_shared_mem_main.c @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "ulp_lp_core_print.h" +#include "ulp_lp_core_memory_shared.h" +#include "test_shared.h" + +int main(void) +{ + ulp_lp_core_memory_shared_cfg_t *shared_cfg = ulp_lp_core_memory_shared_cfg_get(); + lp_core_printf("ULP shared memory address: %p\n", shared_cfg); + + volatile uint8_t* shared_mem = (uint8_t*)shared_cfg; + for (int i = 0; i < sizeof(ulp_lp_core_memory_shared_cfg_t); i++) { + if (shared_mem[i] != SHARED_MEM_INIT_VALUE) { + lp_core_printf("Test failed: expected %X, got %X at %d\n", SHARED_MEM_INIT_VALUE, shared_mem[i], i); + return 0; + } + } + + for (int i = 0; i < sizeof(ulp_lp_core_memory_shared_cfg_t); i++) { + shared_mem[i] = SHARED_MEM_END_VALUE; + } + + lp_core_printf("ULP shared memory test passed\n"); + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_app_main.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_app_main.c new file mode 100644 index 0000000000..d8a9a98ccc --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_app_main.c @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in the sleep code, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-500) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_lp_core.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_lp_core.c new file mode 100644 index 0000000000..3567f4a65f --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_lp_core.c @@ -0,0 +1,125 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include "unity.h" +#include "soc/soc_caps.h" +#include "esp_rom_caps.h" +#include "lp_core_test_app.h" +#include "ulp_lp_core.h" +#include "ulp_lp_core_memory_shared.h" +#include "test_shared.h" + +#if ESP_ROM_HAS_LP_ROM +#include "lp_core_test_app_lp_rom.h" +#endif + +extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_test_app_bin_start"); +extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_test_app_bin_end"); + +extern const uint8_t lp_core_panic_bin_start[] asm("_binary_lp_core_test_app_panic_bin_start"); +extern const uint8_t lp_core_panic_bin_end[] asm("_binary_lp_core_test_app_panic_bin_end"); + +extern const uint8_t lp_core_shared_mem_bin_start[] asm("_binary_lp_core_test_app_shared_mem_bin_start"); +extern const uint8_t lp_core_shared_mem_bin_end[] asm("_binary_lp_core_test_app_shared_mem_bin_end"); + +#if ESP_ROM_HAS_LP_ROM +extern const uint8_t lp_core_lp_rom_bin_start[] asm("_binary_lp_core_test_app_lp_rom_bin_start"); +extern const uint8_t lp_core_lp_rom_bin_end[] asm("_binary_lp_core_test_app_lp_rom_bin_end"); +#endif + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); + +} + +TEST_CASE("lp-print can output to hp-uart", "[lp_core]") +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_bin_start, lp_core_main_bin_end); + + // Actual test output on UART is checked by pytest, not unity test-case + // We simply wait to allow the lp-core to run once + vTaskDelay(1000 / portTICK_PERIOD_MS); +} + +TEST_CASE("LP-Core panic", "[lp_core]") +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_panic_bin_start, lp_core_panic_bin_end); + + // Actual test output on UART is checked by pytest, not unity test-case + // We simply wait to allow the lp-core to run once + vTaskDelay(1000 / portTICK_PERIOD_MS); +} + +TEST_CASE("LP-Core Shared-mem", "[lp_core]") +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + TEST_ASSERT(ulp_lp_core_load_binary(lp_core_shared_mem_bin_start, (lp_core_shared_mem_bin_end - lp_core_shared_mem_bin_start)) == ESP_OK); + + printf("HP shared memory address: %p\n", ulp_lp_core_memory_shared_cfg_get()); + + volatile uint8_t* shared_mem = (uint8_t*)ulp_lp_core_memory_shared_cfg_get(); + for (int i = 0; i < sizeof(ulp_lp_core_memory_shared_cfg_t); i++) { + shared_mem[i] = SHARED_MEM_INIT_VALUE; + } + + TEST_ASSERT(ulp_lp_core_run(&cfg) == ESP_OK); + // Actual test output on UART is checked by pytest, not unity test-case + // We simply wait to allow the lp-core to run once + vTaskDelay(1000 / portTICK_PERIOD_MS); + + // Check that ULP set the shared memory to 0xAA, and it did not get overwritten by anything + for (int i = 0; i < sizeof(ulp_lp_core_memory_shared_cfg_t); i++) { + TEST_ASSERT_EQUAL(SHARED_MEM_END_VALUE, shared_mem[i]); + } + + printf("HP shared memory test passed\n"); +} + +#if ESP_ROM_HAS_LP_ROM +TEST_CASE("LP-Core LP-ROM", "[lp_core]") +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + TEST_ASSERT(ulp_lp_core_load_binary(lp_core_lp_rom_bin_start, (lp_core_lp_rom_bin_end - lp_core_lp_rom_bin_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(&cfg) == ESP_OK); + // Actual test output on UART is checked by pytest, not unity test-case + // We simply wait to allow the lp-core to run once + while (!ulp_lp_rom_test_finished) { + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + printf("LP ROM test passed\n"); +} +#endif diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/pytest_lp_core_hp_uart.py b/components/ulp/test_apps/lp_core/lp_core_hp_uart/pytest_lp_core_hp_uart.py new file mode 100644 index 0000000000..1da7285165 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/pytest_lp_core_hp_uart.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c5 +@pytest.mark.esp32c6 +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_lp_core_hp_uart_print(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"lp-print can output to hp-uart"') + dut.expect_exact('Hello, World!') + dut.expect_exact('1234abcd') + dut.expect_exact('56') + + +@pytest.mark.esp32c5 +@pytest.mark.esp32c6 +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_lp_core_panic(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"LP-Core panic"') + + dut.expect_exact("Guru Meditation Error: LP Core panic'ed Breakpoint") + dut.expect_exact('Core 0 register dump:') + dut.expect_exact('ELF file SHA256:') + + +@pytest.mark.esp32c5 +@pytest.mark.esp32c6 +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_lp_core_shared_mem(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"LP-Core Shared-mem"') + + result = dut.expect(r'HP shared memory address: (0x[0-9a-fA-F]+)') + hp_addr = result[1] + + result = dut.expect(r'ULP shared memory address: (0x[0-9a-fA-F]+)') + ulp_addr = result[1] + + assert ulp_addr == hp_addr + + dut.expect_exact('ULP shared memory test passed') + dut.expect_exact('HP shared memory test passed') + + +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_lp_core_lp_rom(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"LP-Core LP-ROM"') + dut.expect_exact('ULP: all tests passed') + dut.expect_exact('LP ROM test passed') diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.ci.default b/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.ci.default new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.defaults b/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.defaults new file mode 100644 index 0000000000..e3c08c60fa --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_ESP_TASK_WDT_INIT=n + +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=12000 +CONFIG_ULP_PANIC_OUTPUT_ENABLE=y +CONFIG_ULP_HP_UART_CONSOLE_PRINT=y diff --git a/components/ulp/test_apps/ulp_fsm/CMakeLists.txt b/components/ulp/test_apps/ulp_fsm/CMakeLists.txt new file mode 100644 index 0000000000..84b8b921c2 --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/CMakeLists.txt @@ -0,0 +1,9 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") + +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ulp_fsm_test) diff --git a/components/ulp/test_apps/ulp_fsm/README.md b/components/ulp/test_apps/ulp_fsm/README.md new file mode 100644 index 0000000000..682e2df8ef --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/README.md @@ -0,0 +1,3 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | + diff --git a/components/ulp/test_apps/ulp_fsm/main/CMakeLists.txt b/components/ulp/test_apps/ulp_fsm/main/CMakeLists.txt new file mode 100644 index 0000000000..f106fc52d8 --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/main/CMakeLists.txt @@ -0,0 +1,10 @@ +set(app_sources "test_app_main.c" "test_ulp.c" "test_ulp_manual.c") +set(ulp_sources "ulp/test_jumps.S") + +idf_component_register(SRCS ${app_sources} + REQUIRES ulp unity + WHOLE_ARCHIVE) + +set(ulp_app_name ulp_test_app) +set(ulp_exp_dep_srcs ${app_sources}) +ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}") diff --git a/components/ulp/test_apps/ulp_fsm/main/test_app_main.c b/components/ulp/test_apps/ulp_fsm/main/test_app_main.c new file mode 100644 index 0000000000..b131f2b318 --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/main/test_app_main.c @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in the sleep code, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-500) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/ulp/test_apps/ulp_fsm/main/test_ulp.c b/components/ulp/test_apps/ulp_fsm/main/test_ulp.c new file mode 100644 index 0000000000..ee4ae12f88 --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/main/test_ulp.c @@ -0,0 +1,487 @@ +/* + * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_sleep.h" +#include "ulp.h" +#include "soc/soc.h" +#include "soc/rtc.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" +#include "soc/rtc_io_reg.h" +#include "hal/misc.h" +#include "driver/rtc_io.h" + +#include "sdkconfig.h" +#include "esp_rom_sys.h" + +#include "ulp_test_app.h" + +extern const uint8_t ulp_test_app_bin_start[] asm("_binary_ulp_test_app_bin_start"); +extern const uint8_t ulp_test_app_bin_end[] asm("_binary_ulp_test_app_bin_end"); + +#define HEX_DUMP_DEBUG 0 + +static void hexdump(const uint32_t* src, size_t count) +{ +#if HEX_DUMP_DEBUG + for (size_t i = 0; i < count; ++i) { + printf("%08x ", *src); + ++src; + if ((i + 1) % 4 == 0) { + printf("\n"); + } + } +#else + (void)src; + (void)count; +#endif +} + +TEST_CASE("ULP FSM addition test", "[ulp]") +{ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* ULP co-processor program to add data in 2 memory locations using ULP macros */ + const ulp_insn_t program[] = { + I_MOVI(R3, 16), // r3 = 16 + I_LD(R0, R3, 0), // r0 = mem[r3 + 0] + I_LD(R1, R3, 1), // r1 = mem[r3 + 1] + I_ADDR(R2, R0, R1), // r2 = r0 + r1 + I_ST(R2, R3, 2), // mem[r3 + 2] = r2 + I_HALT() // halt + }; + + /* Load the memory regions used by the ULP co-processor */ + RTC_SLOW_MEM[16] = 10; + RTC_SLOW_MEM[17] = 11; + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ASSERT_EQUAL(ESP_OK, ulp_process_macros_and_load(0, program, &size)); + TEST_ASSERT_EQUAL(ESP_OK, ulp_run(0)); + + /* Wait for the ULP co-processor to finish up */ + esp_rom_delay_us(1000); + hexdump(RTC_SLOW_MEM, 20); + + /* Verify the test results */ + TEST_ASSERT_EQUAL(10 + 11, RTC_SLOW_MEM[18] & 0xffff); +} + +TEST_CASE("ULP FSM subtraction and branch test", "[ulp]") +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* ULP co-processor program to perform subtractions and branch to a label */ + const ulp_insn_t program[] = { + I_MOVI(R0, 34), // r0 = 34 + M_LABEL(1), // define a label with label number as 1 + I_MOVI(R1, 32), // r1 = 32 + I_LD(R1, R1, 0), // r1 = mem[32 + 0] + I_MOVI(R2, 33), // r2 = 33 + I_LD(R2, R2, 0), // r2 = mem[33 + 0] + I_SUBR(R3, R1, R2), // r3 = r1 - r2 + I_ST(R3, R0, 0), // mem[r0 + 0] = r3 + I_ADDI(R0, R0, 1), // r0 = r0 + 1 + M_BL(1, 64), // branch to label 1 if r0 < 64 + I_HALT(), // halt + }; + + /* Load the memory regions used by the ULP co-processor */ + RTC_SLOW_MEM[32] = 42; + RTC_SLOW_MEM[33] = 18; + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ASSERT_EQUAL(ESP_OK, ulp_process_macros_and_load(0, program, &size)); + TEST_ASSERT_EQUAL(ESP_OK, ulp_run(0)); + printf("\n\n"); + + /* Wait for the ULP co-processor to finish up */ + esp_rom_delay_us(1000); + hexdump(RTC_SLOW_MEM, 50); + + /* Verify the test results */ + for (int i = 34; i < 64; ++i) { + TEST_ASSERT_EQUAL(42 - 18, RTC_SLOW_MEM[i] & 0xffff); + } + TEST_ASSERT_EQUAL(0, RTC_SLOW_MEM[64]); +} + +TEST_CASE("ULP FSM JUMPS instruction test", "[ulp]") +{ + /* + * Load the ULP binary. + * + * This ULP program is written in assembly. Please refer associated .S file. + */ + esp_err_t err = ulp_load_binary(0, ulp_test_app_bin_start, + (ulp_test_app_bin_end - ulp_test_app_bin_start) / sizeof(uint32_t)); + TEST_ESP_OK(err); + + /* Clear ULP FSM raw interrupt */ + REG_CLR_BIT(RTC_CNTL_INT_RAW_REG, RTC_CNTL_ULP_CP_INT_RAW); + + /* Run the ULP coprocessor */ + TEST_ESP_OK(ulp_run(&ulp_test_jumps - RTC_SLOW_MEM)); + + /* Wait for the ULP co-processor to finish up */ + esp_rom_delay_us(1000); + + /* Verify that ULP FSM issued an interrupt to wake up the main CPU */ + TEST_ASSERT_NOT_EQUAL(0, REG_GET_BIT(RTC_CNTL_INT_RAW_REG, RTC_CNTL_ULP_CP_INT_RAW)); + + /* Verify the test results */ + TEST_ASSERT_EQUAL(0, ulp_jumps_fail & UINT16_MAX); + TEST_ASSERT_EQUAL(1, ulp_jumps_pass & UINT16_MAX); +} + +TEST_CASE("ULP FSM light-sleep wakeup test", "[ulp]") +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* ULP co-processor program to perform some activities and wakeup the main CPU from deep-sleep */ + const ulp_insn_t program[] = { + I_MOVI(R1, 1024), // r1 = 1024 + M_LABEL(1), // define label 1 + I_DELAY(64000), // add a delay (NOP for 64000 cycles) + I_SUBI(R1, R1, 1), // r1 = r1 - 1 + M_BXZ(3), // branch to label 3 if ALU value is 0. (r1 = 0) + I_RSHI(R3, R1, 5), // r3 = r1 / 32 + I_ST(R1, R3, 16), // mem[r3 + 16] = r1 + M_BX(1), // loop to label 1 + M_LABEL(3), // define label 3 + I_MOVI(R2, 42), // r2 = 42 + I_MOVI(R3, 15), // r3 = 15 + I_ST(R2, R3, 0), // mem[r3 + 0] = r2 + I_WAKE(), // wake the SoC from deep-sleep + I_END(), // stop ULP timer + I_HALT() // halt + }; + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ASSERT_EQUAL(ESP_OK, ulp_process_macros_and_load(0, program, &size)); + TEST_ASSERT_EQUAL(ESP_OK, ulp_run(0)); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Also wake-up by timer to help debug issues, timer will only timeout if ULP for some reason failed to wake-up cpu */ + TEST_ASSERT(esp_sleep_enable_timer_wakeup(10 * 1000000) == ESP_OK); + + /* Enter Light Sleep */ + TEST_ASSERT(esp_light_sleep_start() == ESP_OK); + + /* Wait for wakeup from ULP FSM Coprocessor */ + printf("cause %d\r\n", esp_sleep_get_wakeup_cause()); + TEST_ASSERT(esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_ULP); +} + +static void ulp_fsm_deepsleep_wakeup_test(void) +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clearout the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* ULP co-processor program to perform some activities and wakeup the main CPU from deep-sleep */ + const ulp_insn_t program[] = { + I_MOVI(R1, 1024), // r1 = 1024 + M_LABEL(1), // define label 1 + I_DELAY(64000), // add a delay (NOP for 64000 cycles) + I_SUBI(R1, R1, 1), // r1 = r1 - 1 + M_BXZ(3), // branch to label 3 if ALU value is 0. (r1 = 0) + I_RSHI(R3, R1, 5), // r3 = r1 / 32 + I_ST(R1, R3, 16), // mem[r3 + 16] = r1 + M_BX(1), // loop to label 1 + M_LABEL(3), // define label 3 + I_MOVI(R2, 42), // r2 = 42 + I_MOVI(R3, 15), // r3 = 15 + I_ST(R2, R3, 0), // mem[r3 + 0] = r2 + I_WAKE(), // wake the SoC from deep-sleep + I_END(), // stop ULP timer + I_HALT() // halt + }; + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ASSERT_EQUAL(ESP_OK, ulp_process_macros_and_load(0, program, &size)); + TEST_ASSERT_EQUAL(ESP_OK, ulp_run(0)); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} + +static void check_sleep_reset(void) +{ + TEST_ASSERT_EQUAL(ESP_RST_DEEPSLEEP, esp_reset_reason()); + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + TEST_ASSERT_EQUAL(ESP_SLEEP_WAKEUP_ULP, cause); +} + +TEST_CASE_MULTIPLE_STAGES("ULP FSM deep-sleep wakeup test", "[deepsleep][reset=DEEPSLEEP_RESET]", + ulp_fsm_deepsleep_wakeup_test, + check_sleep_reset) + +TEST_CASE("ULP FSM can write and read peripheral registers", "[ulp]") +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear ULP timer */ + CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + uint32_t rtc_store0 = REG_READ(RTC_CNTL_STORE0_REG); + uint32_t rtc_store1 = REG_READ(RTC_CNTL_STORE1_REG); + + /* ULP co-processor program to read from and write to peripheral registers */ + const ulp_insn_t program[] = { + I_MOVI(R1, 64), // r1 = 64 + I_RD_REG(RTC_CNTL_STORE1_REG, 0, 15), // r0 = REG_READ(RTC_CNTL_STORE1_REG[15:0]) + I_ST(R0, R1, 0), // mem[r1 + 0] = r0 + I_RD_REG(RTC_CNTL_STORE1_REG, 4, 11), // r0 = REG_READ(RTC_CNTL_STORE1_REG[11:4]) + I_ST(R0, R1, 1), // mem[r1 + 1] = r0 + I_RD_REG(RTC_CNTL_STORE1_REG, 16, 31), // r0 = REG_READ(RTC_CNTL_STORE1_REG[31:16]) + I_ST(R0, R1, 2), // mem[r1 + 2] = r0 + I_RD_REG(RTC_CNTL_STORE1_REG, 20, 27), // r0 = REG_READ(RTC_CNTL_STORE1_REG[27:20]) + I_ST(R0, R1, 3), // mem[r1 + 3] = r0 + I_WR_REG(RTC_CNTL_STORE0_REG, 0, 7, 0x89), // REG_WRITE(RTC_CNTL_STORE0_REG[7:0], 0x89) + I_WR_REG(RTC_CNTL_STORE0_REG, 8, 15, 0xab), // REG_WRITE(RTC_CNTL_STORE0_REG[15:8], 0xab) + I_WR_REG(RTC_CNTL_STORE0_REG, 16, 23, 0xcd), // REG_WRITE(RTC_CNTL_STORE0_REG[23:16], 0xcd) + I_WR_REG(RTC_CNTL_STORE0_REG, 24, 31, 0xef), // REG_WRITE(RTC_CNTL_STORE0_REG[31:24], 0xef) + I_LD(R0, R1, 4), // r0 = mem[r1 + 4] + I_ADDI(R0, R0, 1), // r0 = r0 + 1 + I_ST(R0, R1, 4), // mem[r1 + 4] = r0 + I_END(), // stop ULP timer + I_HALT() // halt + }; + + /* Set data in the peripheral register to be read by the ULP co-processor */ + REG_WRITE(RTC_CNTL_STORE1_REG, 0x89abcdef); + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size)); + TEST_ESP_OK(ulp_run(0)); + + /* Wait for the ULP co-processor to finish up */ + vTaskDelay(100 / portTICK_PERIOD_MS); + + /* Verify the test results */ + TEST_ASSERT_EQUAL_HEX32(0xefcdab89, REG_READ(RTC_CNTL_STORE0_REG)); + TEST_ASSERT_EQUAL_HEX16(0xcdef, RTC_SLOW_MEM[64] & 0xffff); + TEST_ASSERT_EQUAL_HEX16(0xde, RTC_SLOW_MEM[65] & 0xffff); + TEST_ASSERT_EQUAL_HEX16(0x89ab, RTC_SLOW_MEM[66] & 0xffff); + TEST_ASSERT_EQUAL_HEX16(0x9a, RTC_SLOW_MEM[67] & 0xffff); + TEST_ASSERT_EQUAL_HEX16(1, RTC_SLOW_MEM[68] & 0xffff); + + /* Restore initial calibration values */ + REG_WRITE(RTC_CNTL_STORE0_REG, rtc_store0); + REG_WRITE(RTC_CNTL_STORE1_REG, rtc_store1); +} + +TEST_CASE("ULP FSM I_WR_REG instruction test", "[ulp]") +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* Define the test set */ + typedef struct { + int low; + int width; + } wr_reg_test_item_t; + + const wr_reg_test_item_t test_items[] = { + {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}, {0, 8}, + {3, 1}, {3, 2}, {3, 3}, {3, 4}, {3, 5}, {3, 6}, {3, 7}, {3, 8}, + {15, 1}, {15, 2}, {15, 3}, {15, 4}, {15, 5}, {15, 6}, {15, 7}, {15, 8}, + {16, 1}, {16, 2}, {16, 3}, {16, 4}, {16, 5}, {16, 6}, {16, 7}, {16, 8}, + {18, 1}, {18, 2}, {18, 3}, {18, 4}, {18, 5}, {18, 6}, {18, 7}, {18, 8}, + {24, 1}, {24, 2}, {24, 3}, {24, 4}, {24, 5}, {24, 6}, {24, 7}, {24, 8}, + }; + + const size_t test_items_count = + sizeof(test_items) / sizeof(test_items[0]); + for (size_t i = 0; i < test_items_count; ++i) { + const uint32_t mask = (uint32_t)(((1ULL << test_items[i].width) - 1) << test_items[i].low); + const uint32_t not_mask = ~mask; + printf("#%2d: low: %2d width: %2d mask: %08" PRIx32 " expected: %08" PRIx32 " ", i, + test_items[i].low, test_items[i].width, + mask, not_mask); + + /* Set all bits in RTC_CNTL_STORE0_REG and reset all bits in RTC_CNTL_STORE1_REG */ + uint32_t rtc_store0 = REG_READ(RTC_CNTL_STORE0_REG); + uint32_t rtc_store1 = REG_READ(RTC_CNTL_STORE1_REG); + REG_WRITE(RTC_CNTL_STORE0_REG, 0xffffffff); + REG_WRITE(RTC_CNTL_STORE1_REG, 0x00000000); + + /* ULP co-processor program to write to peripheral registers */ + const ulp_insn_t program[] = { + I_WR_REG(RTC_CNTL_STORE0_REG, + test_items[i].low, + test_items[i].low + test_items[i].width - 1, + 0), + I_WR_REG(RTC_CNTL_STORE1_REG, + test_items[i].low, + test_items[i].low + test_items[i].width - 1, + 0xff & ((1 << test_items[i].width) - 1)), + I_END(), + I_HALT() + }; + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size)); + TEST_ESP_OK(ulp_run(0)); + + /* Wait for the ULP co-processor to finish up */ + vTaskDelay(10 / portTICK_PERIOD_MS); + + /* Verify the test results */ + uint32_t clear = REG_READ(RTC_CNTL_STORE0_REG); + uint32_t set = REG_READ(RTC_CNTL_STORE1_REG); + printf("clear: %08" PRIx32 " set: %08" PRIx32 "\n", clear, set); + + /* Restore initial calibration values */ + REG_WRITE(RTC_CNTL_STORE0_REG, rtc_store0); + REG_WRITE(RTC_CNTL_STORE1_REG, rtc_store1); + + TEST_ASSERT_EQUAL_HEX32(not_mask, clear); + TEST_ASSERT_EQUAL_HEX32(mask, set); + } +} + +TEST_CASE("ULP FSM timer setting", "[ulp]") +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 32 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* + * Run a simple ULP program which increments the counter, for one second. + * Program calls I_HALT each time and gets restarted by the timer. + * Compare the expected number of times the program runs with the actual. + */ + const int offset = 6; + const ulp_insn_t program[] = { + I_MOVI(R1, offset), // r1 <- offset + I_LD(R2, R1, 0), // load counter + I_ADDI(R2, R2, 1), // counter += 1 + I_ST(R2, R1, 0), // save counter + I_HALT(), + }; + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size)); + assert(offset >= size && "data offset needs to be greater or equal to program size"); + TEST_ESP_OK(ulp_run(0)); + + /* Disable the ULP program timer — we will enable it later */ + ulp_timer_stop(); + + /* Define the test data */ + const uint32_t cycles_to_test[] = { 10000, // 10 ms + 20000, // 20 ms + 50000, // 50 ms + 100000, // 100 ms + 200000, // 200 ms + 500000, // 500 ms + 1000000 + }; // 1 sec + const size_t tests_count = sizeof(cycles_to_test) / sizeof(cycles_to_test[0]); + + for (size_t i = 0; i < tests_count; ++i) { + // zero out the counter + RTC_SLOW_MEM[offset] = 0; + // set the ulp timer period + ulp_set_wakeup_period(0, cycles_to_test[i]); + // enable the timer and wait for a second + ulp_timer_resume(); + vTaskDelay(1000 / portTICK_PERIOD_MS); + // stop the timer and get the counter value + ulp_timer_stop(); + uint32_t counter = RTC_SLOW_MEM[offset] & 0xffff; + // calculate the expected counter value and allow a tolerance of 15% + uint32_t expected_counter = 1000000 / cycles_to_test[i]; + uint32_t tolerance = (expected_counter * 15 / 100); + tolerance = tolerance ? tolerance : 1; // Keep a tolerance of at least 1 count + printf("expected: %" PRIu32 "\t tolerance: +/- %" PRIu32 "\t actual: %" PRIu32 "\n", expected_counter, tolerance, counter); + // Should be within 15% + TEST_ASSERT_INT_WITHIN(tolerance, expected_counter, counter); + } +} + +static void ulp_isr(void *arg) +{ + BaseType_t yield = 0; + SemaphoreHandle_t sem = (SemaphoreHandle_t)arg; + xSemaphoreGiveFromISR(sem, &yield); + if (yield) { + portYIELD_FROM_ISR(); + } +} + +TEST_CASE("ULP FSM interrupt signal can be handled via ISRs on the main core", "[ulp]") +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* ULP co-processor program to send a wakeup to the main CPU */ + const ulp_insn_t program[] = { + I_WAKE(), // send wakeup signal to main CPU + I_END(), // stop ULP timer + I_HALT() // halt + }; + + /* Create test semaphore */ + SemaphoreHandle_t ulp_isr_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(ulp_isr_sem); + + /* Register ULP wakeup signal ISR */ + TEST_ASSERT_EQUAL(ESP_OK, ulp_isr_register(ulp_isr, (void *)ulp_isr_sem)); + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ASSERT_EQUAL(ESP_OK, ulp_process_macros_and_load(0, program, &size)); + TEST_ASSERT_EQUAL(ESP_OK, ulp_run(0)); + + /* Wait from ISR to be called */ + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(ulp_isr_sem, portMAX_DELAY)); + + /* Deregister the ISR */ + TEST_ASSERT_EQUAL(ESP_OK, ulp_isr_deregister(ulp_isr, (void *)ulp_isr_sem)); + + /* Delete test semaphore */ + vSemaphoreDelete(ulp_isr_sem); +} diff --git a/components/ulp/test_apps/ulp_fsm/main/test_ulp_manual.c b/components/ulp/test_apps/ulp_fsm/main/test_ulp_manual.c new file mode 100644 index 0000000000..168dad4f90 --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/main/test_ulp_manual.c @@ -0,0 +1,283 @@ +/* + * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "unity.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_sleep.h" +#include "ulp.h" +#include "soc/soc.h" +#include "soc/rtc.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" +#include "soc/rtc_io_reg.h" +#include "hal/misc.h" +#include "driver/rtc_io.h" + +#include "sdkconfig.h" +#include "esp_rom_sys.h" + +#include "ulp_test_app.h" + +/* Test cases that require manual interaction, not run in CI */ + +void ulp_fsm_controls_rtc_io(void) +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* ULP co-processor program to toggle LED */ + const ulp_insn_t program[] = { + I_MOVI(R0, 0), // r0 is LED state + I_MOVI(R2, 16), // loop r2 from 16 down to 0 + M_LABEL(4), // define label 4 + I_SUBI(R2, R2, 1), // r2 = r2 - 1 + M_BXZ(6), // branch to label 6 if r2 = 0 + I_ADDI(R0, R0, 1), // r0 = (r0 + 1) % 2 + I_ANDI(R0, R0, 0x1), + M_BL(0, 1), // if r0 < 1 goto 0 + M_LABEL(1), // define label 1 + I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 1), // RTC_GPIO12 = 1 + M_BX(2), // goto 2 + M_LABEL(0), // define label 0 + I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 0), // RTC_GPIO12 = 0 + M_LABEL(2), // define label 2 + I_MOVI(R1, 100), // loop R1 from 100 down to 0 + M_LABEL(3), // define label 3 + I_SUBI(R1, R1, 1), // r1 = r1 - 1 + M_BXZ(5), // branch to label 5 if r1 = 0 + I_DELAY(32000), // delay for a while + M_BX(3), // goto 3 + M_LABEL(5), // define label 5 + M_BX(4), // loop back to label 4 + M_LABEL(6), // define label 6 + I_WAKE(), // wake up the SoC + I_END(), // stop ULP program timer + I_HALT() + }; + + /* Configure LED GPIOs */ + const gpio_num_t led_gpios[] = { + GPIO_NUM_2, + GPIO_NUM_0, + GPIO_NUM_4 + }; + for (size_t i = 0; i < sizeof(led_gpios) / sizeof(led_gpios[0]); ++i) { + rtc_gpio_init(led_gpios[i]); + rtc_gpio_set_direction(led_gpios[i], RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(led_gpios[i], 0); + } + + /* Calculate the size of the ULP co-processor binary, load it and run the ULP coprocessor */ + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size)); + TEST_ESP_OK(ulp_run(0)); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} + +void ulp_fsm_power_consumption(void) +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 4 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + /* Put the ULP coprocessor in halt state */ + ulp_insn_t insn = I_HALT(); + hal_memcpy(RTC_SLOW_MEM, &insn, sizeof(insn)); + + /* Set ULP timer */ + ulp_set_wakeup_period(0, 0x8000); + + /* Run the ULP coprocessor */ + TEST_ESP_OK(ulp_run(0)); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + TEST_ASSERT(esp_sleep_enable_timer_wakeup(10 * 1000000) == ESP_OK); + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} + +#if !DISABLED_FOR_TARGETS(ESP32) +void ulp_fsm_temp_sens(void) +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + + // Allow TSENS to be controlled by the ULP + SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 10, SENS_TSENS_CLK_DIV_S); +#if CONFIG_IDF_TARGET_ESP32S2 + SET_PERI_REG_BITS(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR, SENS_FORCE_XPD_SAR_FSM, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL2_REG, SENS_TSENS_CLKGATE_EN); +#elif CONFIG_IDF_TARGET_ESP32S3 + SET_PERI_REG_BITS(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR, 0, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_MASK(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_TSENS_CLK_EN); +#endif + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE); + + // data start offset + size_t offset = 20; + // number of samples to collect + RTC_SLOW_MEM[offset] = (CONFIG_ULP_COPROC_RESERVE_MEM) / 4 - offset - 8; + // sample counter + RTC_SLOW_MEM[offset + 1] = 0; + + /* ULP co-processor program to record temperature sensor readings */ + const ulp_insn_t program[] = { + I_MOVI(R1, offset), // r1 <- offset + I_LD(R2, R1, 1), // r2 <- counter + I_LD(R3, R1, 0), // r3 <- length + I_SUBI(R3, R3, 1), // end = length - 1 + I_SUBR(R3, R3, R2), // r3 = length - counter + M_BXF(1), // if overflow goto 1: + I_TSENS(R0, 16383), // r0 <- tsens + I_ST(R0, R2, offset + 4), // mem[r2 + offset +4] <- r0 + I_ADDI(R2, R2, 1), // counter += 1 + I_ST(R2, R1, 1), // save counter + I_HALT(), // enter sleep + M_LABEL(1), // done with measurements + I_END(), // stop ULP timer + I_WAKE(), // initiate wakeup + I_HALT() + }; + + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size)); + assert(offset >= size); + + /* Run the ULP coprocessor */ + TEST_ESP_OK(ulp_run(0)); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + TEST_ASSERT(esp_sleep_enable_timer_wakeup(10 * 1000000) == ESP_OK); + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} +#endif //#if !DISABLED_FOR_TARGETS(ESP32) + +void ulp_fsm_adc(void) +{ + assert(CONFIG_ULP_COPROC_RESERVE_MEM >= 260 && "this test needs ULP_COPROC_RESERVE_MEM option set in menuconfig"); + + const int adc = 0; + const int channel = 0; + const int atten = 0; + + /* Clear the RTC_SLOW_MEM region for the ULP co-processor binary to be loaded */ + hal_memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + +#if defined(CONFIG_IDF_TARGET_ESP32) + // Configure SAR ADCn resolution + SET_PERI_REG_BITS(SENS_SAR_START_FORCE_REG, SENS_SAR1_BIT_WIDTH, 3, SENS_SAR1_BIT_WIDTH_S); + SET_PERI_REG_BITS(SENS_SAR_START_FORCE_REG, SENS_SAR2_BIT_WIDTH, 3, SENS_SAR2_BIT_WIDTH_S); + SET_PERI_REG_BITS(SENS_SAR_READ_CTRL_REG, SENS_SAR1_SAMPLE_BIT, 0x3, SENS_SAR1_SAMPLE_BIT_S); + SET_PERI_REG_BITS(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_SAMPLE_BIT, 0x3, SENS_SAR2_SAMPLE_BIT_S); + + // SAR ADCn is started by ULP FSM + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_MEAS2_START_FORCE); + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_MEAS1_START_FORCE); + + // Use ULP FSM to power up SAR ADCn + SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 0, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_AMP, 2, SENS_FORCE_XPD_AMP_S); + + // SAR ADCn invert result + SET_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR1_DATA_INV); + SET_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR2_DATA_INV); + + // Set SAR ADCn pad enable bitmap to be controlled by ULP FSM + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START1_REG, SENS_SAR1_EN_PAD_FORCE_M); + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS_START2_REG, SENS_SAR2_EN_PAD_FORCE_M); +#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) + // SAR ADCn is started by ULP FSM + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS2_CTRL2_REG, SENS_MEAS2_START_FORCE); + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS1_CTRL2_REG, SENS_MEAS1_START_FORCE); + + // Use ULP FSM to power up/down SAR ADCn + SET_PERI_REG_BITS(SENS_SAR_POWER_XPD_SAR_REG, SENS_FORCE_XPD_SAR, 0, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_BITS(SENS_SAR_MEAS1_CTRL1_REG, SENS_FORCE_XPD_AMP, 2, SENS_FORCE_XPD_AMP_S); + + // SAR1 invert result + SET_PERI_REG_MASK(SENS_SAR_READER1_CTRL_REG, SENS_SAR1_DATA_INV); + SET_PERI_REG_MASK(SENS_SAR_READER2_CTRL_REG, SENS_SAR2_DATA_INV); + + // Set SAR ADCn pad enable bitmap to be controlled by ULP FSM + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS1_CTRL2_REG, SENS_SAR1_EN_PAD_FORCE_M); + CLEAR_PERI_REG_MASK(SENS_SAR_MEAS2_CTRL2_REG, SENS_SAR2_EN_PAD_FORCE_M); + + // Enable SAR ADCn clock gate on esp32s3 +#if CONFIG_IDF_TARGET_ESP32S3 + SET_PERI_REG_MASK(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_SARADC_CLK_EN); +#endif +#endif + + SET_PERI_REG_BITS(SENS_SAR_ATTEN1_REG, 3, atten, 2 * channel); //set SAR1 attenuation + SET_PERI_REG_BITS(SENS_SAR_ATTEN2_REG, 3, atten, 2 * channel); //set SAR2 attenuation + + // data start offset + size_t offset = 20; + // number of samples to collect + RTC_SLOW_MEM[offset] = (CONFIG_ULP_COPROC_RESERVE_MEM) / 4 - offset - 8; + // sample counter + RTC_SLOW_MEM[offset + 1] = 0; + + const ulp_insn_t program[] = { + I_MOVI(R1, offset), // r1 <- offset + I_LD(R2, R1, 1), // r2 <- counter + I_LD(R3, R1, 0), // r3 <- length + I_SUBI(R3, R3, 1), // end = length - 1 + I_SUBR(R3, R3, R2), // r3 = length - counter + M_BXF(1), // if overflow goto 1: + I_ADC(R0, adc, channel), // r0 <- ADC + I_ST(R0, R2, offset + 4), // mem[r2 + offset +4] = r0 + I_ADDI(R2, R2, 1), // counter += 1 + I_ST(R2, R1, 1), // save counter + I_HALT(), // enter sleep + M_LABEL(1), // done with measurements + I_END(), // stop ULP program timer + I_HALT() + }; + + size_t size = sizeof(program) / sizeof(ulp_insn_t); + TEST_ESP_OK(ulp_process_macros_and_load(0, program, &size)); + assert(offset >= size); + + /* Run the ULP coprocessor */ + TEST_ESP_OK(ulp_run(0)); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + TEST_ASSERT(esp_sleep_enable_timer_wakeup(10 * 1000000) == ESP_OK); + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} diff --git a/components/ulp/test_apps/ulp_fsm/main/ulp/test_jumps.S b/components/ulp/test_apps/ulp_fsm/main/ulp/test_jumps.S new file mode 100644 index 0000000000..debd131464 --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/main/ulp/test_jumps.S @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "soc/rtc_cntl_reg.h" +#include "soc/rtc_io_reg.h" +#include "soc/soc_ulp.h" + + .bss + + .global jumps_pass +jumps_pass: + .long 0 + + .global jumps_fail +jumps_fail: + .long 0 + + .text + .global test_jumps +test_jumps: + + /* tests for LT (less than) condition */ + stage_rst /* cnt = 0 */ + jumps test_fail, 0, LT /* 0 < 0: false, should not jump */ + jumps 1f, 1, LT /* 0 < 1: true, should jump */ + jump test_fail +1: + stage_inc 2 /* cnt = 2 */ + jumps 1f, 3, LT /* 2 < 1: true */ + jump test_fail +1: + jumps test_fail, 1, LT /* 2 < 1: false */ + jumps test_fail, 2, LT /* 2 < 2: false */ + + /* tests for LE (less or equal) condition */ + stage_rst /* cnt = 0 */ + jumps 1f, 0, LE /* 0 <= 0: true */ + jump test_fail +1: + jumps 1f, 1, LE /* 0 <= 1: true */ + jump test_fail +1: + stage_inc 2 /* cnt = 2 */ + jumps test_fail, 1, LE /* 2 <= 1: false */ + + /* tests for EQ (equal) condition */ + stage_rst /* cnt = 0 */ + jumps 1f, 0, EQ /* 0 = 0: true */ + jump test_fail +1: + jumps test_fail, 1, EQ /* 0 = 1: false */ + + stage_inc 1 /* cnt = 1 */ + jumps test_fail, 0, EQ /* 1 = 0: false */ + jumps test_fail, 2, EQ /* 1 = 2: false */ + jumps 1f, 1, EQ /* 1 = 1: true */ +1: + + /* tests for GE (greater or equal) condition */ + stage_rst /* cnt = 0 */ + jumps 1f, 0, GE /* 0 >= 0: true */ + jump test_fail +1: + jumps test_fail, 1, GE /* 0 >= 1: false */ + + stage_inc 1 /* cnt = 1 */ + jumps 1f, 0, GE /* 1 >= 0: true */ + jump test_fail +1: + jumps 1f, 1, GE /* 1 >= 1: true */ + jump test_fail +1: + jumps test_fail, 2, GE /* 1 >= 2: false */ + + /* tests for GT (greater than) condition */ + stage_rst /* cnt = 0 */ + jumps test_fail, 0, GT /* 0 > 0: false */ + jumps test_fail, 1, GE /* 0 > 1: false */ + + stage_inc 1 /* cnt = 1 */ + jumps 1f, 0, GT /* 1 > 0: true */ + jump test_fail +1: + jumps test_fail, 1, GT /* 1 > 1: false */ + jumps test_fail, 2, GT /* 1 > 2: false */ + + jump test_pass + +test_fail: + move r0, jumps_fail + move r1, 1 + st r1, r0, 0 + jump done + +test_pass: + move r0, jumps_pass + move r1, 1 + st r1, r0, 0 + jump done + + .global done +done: + wake + halt diff --git a/components/ulp/test_apps/ulp_fsm/pytest_ulp_fsm_app.py b/components/ulp/test_apps/ulp_fsm/pytest_ulp_fsm_app.py new file mode 100644 index 0000000000..9db9abb6a9 --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/pytest_ulp_fsm_app.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.generic +def test_ulp_fsm(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/ulp/test_apps/ulp_fsm/sdkconfig.defaults b/components/ulp/test_apps/ulp_fsm/sdkconfig.defaults new file mode 100644 index 0000000000..a2d3eeb79f --- /dev/null +++ b/components/ulp/test_apps/ulp_fsm/sdkconfig.defaults @@ -0,0 +1,5 @@ +CONFIG_ESP_TASK_WDT_EN=n + +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_FSM=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 diff --git a/components/ulp/test_apps/ulp_riscv/CMakeLists.txt b/components/ulp/test_apps/ulp_riscv/CMakeLists.txt new file mode 100644 index 0000000000..7c6144aee8 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/CMakeLists.txt @@ -0,0 +1,14 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") + +set(EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/unit-test-app/components" +) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ulp_riscv_test) diff --git a/components/ulp/test_apps/ulp_riscv/README.md b/components/ulp/test_apps/ulp_riscv/README.md new file mode 100644 index 0000000000..cfd217d911 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/README.md @@ -0,0 +1,3 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + diff --git a/components/ulp/test_apps/ulp_riscv/main/CMakeLists.txt b/components/ulp/test_apps/ulp_riscv/main/CMakeLists.txt new file mode 100644 index 0000000000..ff7fe44941 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/CMakeLists.txt @@ -0,0 +1,20 @@ +set(app_sources "test_app_main.c" "test_ulp_riscv.c" "test_ulp_riscv_i2c.c") +set(ulp_sources "ulp/test_main.c") +set(ulp_sources2 "ulp/test_main_second_cocpu_firmware.c") +set(ulp_sources3 "ulp/test_main_cocpu_crash.c") +set(ulp_sources4 "ulp/test_main_i2c.c") + +idf_component_register(SRCS ${app_sources} + INCLUDE_DIRS "ulp" + REQUIRES ulp unity test_utils + WHOLE_ARCHIVE) + +set(ulp_app_name ulp_test_app) +set(ulp_app_name2 ulp_test_app2) +set(ulp_app_name3 ulp_test_app3) +set(ulp_app_name4 ulp_test_app_i2c) +set(ulp_exp_dep_srcs ${app_sources}) +ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}") +ulp_embed_binary(${ulp_app_name2} "${ulp_sources2}" "${ulp_exp_dep_srcs}") +ulp_embed_binary(${ulp_app_name3} "${ulp_sources3}" "${ulp_exp_dep_srcs}") +ulp_embed_binary(${ulp_app_name4} "${ulp_sources4}" "${ulp_exp_dep_srcs}") diff --git a/components/ulp/test_apps/ulp_riscv/main/test_app_main.c b/components/ulp/test_apps/ulp_riscv/main/test_app_main.c new file mode 100644 index 0000000000..b131f2b318 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/test_app_main.c @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in the sleep code, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-500) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c new file mode 100644 index 0000000000..33db873eb2 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c @@ -0,0 +1,471 @@ +/* + * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_sleep.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" +#include "soc/rtc_periph.h" +#include "ulp_riscv.h" +#include "ulp_riscv_lock.h" +#include "ulp_adc.h" +#include "ulp_test_app.h" +#include "ulp_test_app2.h" +#include "ulp_test_shared.h" +#include "unity.h" +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_adc/adc_oneshot.h" + +#define ULP_WAKEUP_PERIOD 1000000 // 1 second + +// First ULP firmware +extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_test_app_bin_start"); +extern const size_t ulp_main_bin_length asm("ulp_test_app_bin_length"); +static bool firmware_loaded = false; + +// Second ULP firmware +extern const uint8_t ulp_test_app2_bin_start[] asm("_binary_ulp_test_app2_bin_start"); +extern const size_t ulp_test_app2_bin_length asm("ulp_test_app2_bin_length"); + +// Faulty ULP firmware +extern const uint8_t ulp_test_app3_bin_start[] asm("_binary_ulp_test_app3_bin_start"); +extern const size_t ulp_test_app3_bin_length asm("ulp_test_app3_bin_length"); + +static void load_and_start_ulp_firmware(const uint8_t* ulp_bin, size_t ulp_bin_len) +{ + if (!firmware_loaded) { + TEST_ASSERT(ulp_riscv_load_binary(ulp_bin, ulp_bin_len) == ESP_OK); + TEST_ASSERT(ulp_set_wakeup_period(0, ULP_WAKEUP_PERIOD) == ESP_OK); + TEST_ASSERT(ulp_riscv_run() == ESP_OK); + + firmware_loaded = true; + printf("New ULP firmware loaded\n"); + } +} + +TEST_CASE("ULP-RISC-V and main CPU are able to exchange data", "[ulp]") +{ + const uint32_t test_data = 0x12345678; + struct timeval start, end; + + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Setup test data */ + ulp_riscv_test_data_in = test_data ^ XOR_MASK; + ulp_main_cpu_command = RISCV_READ_WRITE_TEST; + + /* Enter Light Sleep */ + esp_light_sleep_start(); + + /* Wait till we receive the correct command response */ + gettimeofday(&start, NULL); + while (ulp_command_resp != RISCV_READ_WRITE_TEST) + ; + gettimeofday(&end, NULL); + printf("Response time %jd ms\n", ((intmax_t)end.tv_sec - (intmax_t)start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000); + + /* Verify test data */ + TEST_ASSERT(ulp_command_resp == RISCV_READ_WRITE_TEST); + TEST_ASSERT(ulp_main_cpu_reply == RISCV_COMMAND_OK); + printf("data out: 0x%" PRIx32 ", expected: 0x%" PRIx32 " \n", ulp_riscv_test_data_out, test_data); + TEST_ASSERT(test_data == ulp_riscv_test_data_out); + + /* Clear test data */ + ulp_main_cpu_command = RISCV_NO_COMMAND; +} + +TEST_CASE("ULP-RISC-V is able to wakeup main CPU from light sleep", "[ulp]") +{ + struct timeval start, end; + + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Setup test data */ + ulp_main_cpu_command = RISCV_LIGHT_SLEEP_WAKEUP_TEST; + + /* Enter Light Sleep */ + esp_light_sleep_start(); + + /* Wait till we receive the correct command response */ + gettimeofday(&start, NULL); + while (ulp_command_resp != RISCV_LIGHT_SLEEP_WAKEUP_TEST) + ; + gettimeofday(&end, NULL); + printf("Response time 1st: %jd ms\n", ((intmax_t)end.tv_sec - (intmax_t)start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000); + + /* Verify test data */ + TEST_ASSERT(ulp_command_resp == RISCV_LIGHT_SLEEP_WAKEUP_TEST); + TEST_ASSERT(ulp_main_cpu_reply == RISCV_COMMAND_OK); + + /* Enter Light Sleep again */ + esp_light_sleep_start(); + + /* Wait till we receive the correct command response */ + gettimeofday(&start, NULL); + while (ulp_command_resp != RISCV_LIGHT_SLEEP_WAKEUP_TEST) + ; + gettimeofday(&end, NULL); + printf("Response time 2nd: %jd ms\n", ((intmax_t)end.tv_sec - (intmax_t)start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000); + + /* Verify test data */ + TEST_ASSERT(ulp_command_resp == RISCV_LIGHT_SLEEP_WAKEUP_TEST); + TEST_ASSERT(ulp_main_cpu_reply == RISCV_COMMAND_OK); + + /* Clear test data */ + ulp_main_cpu_command = RISCV_NO_COMMAND; +} + +static bool ulp_riscv_is_running(uint32_t *counter_variable) +{ + uint32_t start_cnt = *counter_variable; + + /* Wait a few ULP wakeup cycles to ensure ULP has run */ + vTaskDelay((5 * ULP_WAKEUP_PERIOD / 1000) / portTICK_PERIOD_MS); + + uint32_t end_cnt = *counter_variable; + printf("start run count: %" PRIu32 ", end run count %" PRIu32 "\n", start_cnt, end_cnt); + + /* If the ulp is running the counter should have been incremented */ + return (start_cnt != end_cnt); +} + +TEST_CASE("ULP-RISC-V can be stopped and resumed from main CPU", "[ulp]") +{ + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); + + printf("Stopping the ULP\n"); + ulp_riscv_timer_stop(); + ulp_riscv_halt(); + + TEST_ASSERT(!ulp_riscv_is_running(&ulp_riscv_counter)); + + printf("Resuming the ULP\n"); + ulp_riscv_timer_resume(); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); +} + +TEST_CASE("ULP-RISC-V can be loaded with and run multiple firmwares", "[ulp]") +{ + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); + + printf("Stopping the ULP\n"); + ulp_riscv_timer_stop(); + ulp_riscv_halt(); + + TEST_ASSERT(!ulp_riscv_is_running(&ulp_riscv_counter)); + + printf("Loading second firmware on the ULP\n"); + firmware_loaded = false; + load_and_start_ulp_firmware(ulp_test_app2_bin_start, ulp_test_app2_bin_length); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter2)); + + printf("Stopping the ULP\n"); + ulp_riscv_timer_stop(); + ulp_riscv_halt(); + + TEST_ASSERT(!ulp_riscv_is_running(&ulp_riscv_counter2)); + + printf("Loading the first firmware again on the ULP\n"); + firmware_loaded = false; + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); +} + +TEST_CASE("ULP-RISC-V can be reloaded with a good fimware after a crash", "[ulp]") +{ + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); + + printf("Stopping the ULP\n"); + ulp_riscv_timer_stop(); + ulp_riscv_halt(); + + TEST_ASSERT(!ulp_riscv_is_running(&ulp_riscv_counter)); + + /* Enable ULP wakeup */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + printf("Loading faulty firmware on the ULP and go into light sleep\n"); + firmware_loaded = false; + load_and_start_ulp_firmware(ulp_test_app3_bin_start, ulp_test_app3_bin_length); + esp_light_sleep_start(); + + /* Verify that main CPU wakes up by a COCPU trap signal trigger */ + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + TEST_ASSERT(cause == ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG); + + printf("Resetting the ULP\n"); + ulp_riscv_reset(); + + esp_rom_delay_us(20); + + printf("Loading the good firmware on ULP\n"); + firmware_loaded = false; + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); +} + +TEST_CASE("ULP-RISC-V can stop itself and be resumed from the main CPU", "[ulp]") +{ + volatile riscv_test_commands_t *command_resp = (riscv_test_commands_t*)&ulp_command_resp; + + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); + + printf("Stopping the ULP\n"); + /* Setup test data */ + ulp_main_cpu_command = RISCV_STOP_TEST; + + while (*command_resp != RISCV_STOP_TEST) { + } + + /* Wait a bit to ensure ULP finished shutting down */ + vTaskDelay(100 / portTICK_PERIOD_MS); + + TEST_ASSERT(!ulp_riscv_is_running(&ulp_riscv_counter)); + + printf("Resuming the ULP\n"); + ulp_main_cpu_command = RISCV_NO_COMMAND; + ulp_riscv_timer_resume(); + + TEST_ASSERT(ulp_riscv_is_running(&ulp_riscv_counter)); +} + +TEST_CASE("ULP-RISC-V mutex", "[ulp]") +{ + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + /* Setup test data */ + ulp_riscv_incrementer = 0; + ulp_main_cpu_reply = RISCV_NO_COMMAND; + ulp_main_cpu_command = RISCV_MUTEX_TEST; + + ulp_riscv_lock_t *lock = (ulp_riscv_lock_t*)&ulp_lock; + + for (int i = 0; i < MUTEX_TEST_ITERATIONS; i++) { + ulp_riscv_lock_acquire(lock); + ulp_riscv_incrementer++; + ulp_riscv_lock_release(lock); + } + + while (ulp_main_cpu_reply != RISCV_COMMAND_OK) { + // Wait for ULP to finish + } + + /* If the variable is protected there should be no race conditions + results should be the sum of increments made by ULP and by main CPU + */ + TEST_ASSERT_EQUAL(2 * MUTEX_TEST_ITERATIONS, ulp_riscv_incrementer); +} + +static void do_ulp_wakeup_deepsleep(riscv_test_commands_t ulp_cmd, bool rtc_periph_pd) +{ + if (!rtc_periph_pd) { + // Force RTC peripheral power domain to be on + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + } + + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Setup test data */ + ulp_main_cpu_command = ulp_cmd; + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} + +static void check_reset_reason_ulp_wakeup(void) +{ + TEST_ASSERT_EQUAL(ESP_SLEEP_WAKEUP_ULP, esp_sleep_get_wakeup_cause()); +} + +static void do_ulp_wakeup_after_long_delay_deepsleep(void) +{ + do_ulp_wakeup_deepsleep(RISCV_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST, true); +} + +/* Certain erroneous wake-up triggers happen only after sleeping for a few seconds */ +TEST_CASE_MULTIPLE_STAGES("ULP-RISC-V is able to wakeup main CPU from deep sleep after a long delay", "[ulp]", + do_ulp_wakeup_after_long_delay_deepsleep, + check_reset_reason_ulp_wakeup); + +static void do_ulp_wakeup_after_long_delay_deepsleep_rtc_perip_on(void) +{ + do_ulp_wakeup_deepsleep(RISCV_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST, false); +} + +TEST_CASE_MULTIPLE_STAGES("ULP-RISC-V is able to wakeup main CPU from deep sleep after a long delay, RTC periph powerup", "[ulp]", + do_ulp_wakeup_after_long_delay_deepsleep_rtc_perip_on, + check_reset_reason_ulp_wakeup); + +static void do_ulp_wakeup_after_short_delay_deepsleep(void) +{ + do_ulp_wakeup_deepsleep(RISCV_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST, true); +} + +TEST_CASE_MULTIPLE_STAGES("ULP-RISC-V is able to wakeup main CPU from deep sleep after a short delay", "[ulp]", + do_ulp_wakeup_after_short_delay_deepsleep, + check_reset_reason_ulp_wakeup); + +static void do_ulp_wakeup_after_short_delay_deepsleep_rtc_perip_on(void) +{ + do_ulp_wakeup_deepsleep(RISCV_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST, false); +} + +TEST_CASE_MULTIPLE_STAGES("ULP-RISC-V is able to wakeup main CPU from deep sleep after a short delay, RTC periph powerup", "[ulp]", + do_ulp_wakeup_after_short_delay_deepsleep_rtc_perip_on, + check_reset_reason_ulp_wakeup); + +typedef struct { + SemaphoreHandle_t ulp_isr_sw_sem; + SemaphoreHandle_t ulp_isr_trap_sem; +} TestSemaphore_t; + +static void ulp_riscv_isr(void *arg) +{ + BaseType_t yield = 0; + TestSemaphore_t *sem = (TestSemaphore_t *)arg; + + uint32_t status = READ_PERI_REG(RTC_CNTL_INT_ST_REG); + + if (status & ULP_RISCV_SW_INT) { + xSemaphoreGiveFromISR(sem->ulp_isr_sw_sem, &yield); + } else if (status & ULP_RISCV_TRAP_INT) { + xSemaphoreGiveFromISR(sem->ulp_isr_trap_sem, &yield); + } + + if (yield) { + portYIELD_FROM_ISR(); + } +} + +TEST_CASE("ULP-RISC-V interrupt signals can be handled via ISRs on the main core", "[ulp]") +{ + /* Create test semaphores */ + TestSemaphore_t test_sem_cfg; + + test_sem_cfg.ulp_isr_sw_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(test_sem_cfg.ulp_isr_sw_sem); + + test_sem_cfg.ulp_isr_trap_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(test_sem_cfg.ulp_isr_trap_sem); + + /* Register ULP RISC-V signal ISR */ + TEST_ASSERT_EQUAL(ESP_OK, ulp_riscv_isr_register(ulp_riscv_isr, (void *)&test_sem_cfg, + (ULP_RISCV_SW_INT | ULP_RISCV_TRAP_INT))); + + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + printf("Loading good ULP firmware\n"); + firmware_loaded = false; + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); + + /* Setup test data. We only need the ULP to send a wakeup signal to the main CPU. */ + ulp_main_cpu_command = RISCV_READ_WRITE_TEST; + + /* Wait for the ISR to be called */ + printf("Waiting for ULP wakeup signal ...\n"); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(test_sem_cfg.ulp_isr_sw_sem, portMAX_DELAY)); + printf("ULP wakeup signal interrupt triggered\n"); + + /* Reset the ULP command */ + ulp_main_cpu_command = RISCV_NO_COMMAND; + + /* Load ULP RISC-V with faulty firmware and restart the ULP RISC-V Coprocessor. + * This should cause a cocpu trap signal interrupt. + */ + printf("Loading faulty ULP firmware\n"); + firmware_loaded = false; + load_and_start_ulp_firmware(ulp_test_app3_bin_start, ulp_test_app3_bin_length); + + /* Wait for the ISR to be called */ + printf("Waiting for ULP trap signal ...\n"); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(test_sem_cfg.ulp_isr_trap_sem, portMAX_DELAY)); + printf("ULP trap signal interrupt triggered\n"); + + /* Deregister the ISR */ + TEST_ASSERT_EQUAL(ESP_OK, ulp_riscv_isr_deregister(ulp_riscv_isr, (void *)&test_sem_cfg, + (ULP_RISCV_SW_INT | ULP_RISCV_TRAP_INT))); + + /* Delete test semaphores */ + vSemaphoreDelete(test_sem_cfg.ulp_isr_sw_sem); + vSemaphoreDelete(test_sem_cfg.ulp_isr_trap_sem); + + /* Make sure ULP has a good firmware running before exiting the test */ + ulp_riscv_reset(); + firmware_loaded = false; + load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length); +} + +#define ATTEN 3 +#define WIDTH 0 +#define CHANNEL 6 +#define ADC_UNIT ADC_UNIT_1 + +TEST_CASE("ULP ADC can init-deinit-init", "[ulp]") +{ + + ulp_adc_cfg_t riscv_adc_cfg = { + .adc_n = ADC_UNIT, + .channel = CHANNEL, + .width = WIDTH, + .atten = ATTEN, + .ulp_mode = ADC_ULP_MODE_FSM, + }; + + TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_init(&riscv_adc_cfg)); + TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_deinit()); + + /* Check that we can init one-shot ADC */ + adc_oneshot_unit_handle_t adc1_handle; + adc_oneshot_unit_init_cfg_t init_config1 = { + .unit_id = ADC_UNIT, + }; + TEST_ASSERT_EQUAL(ESP_OK, adc_oneshot_new_unit(&init_config1, &adc1_handle)); + + adc_oneshot_chan_cfg_t config = { + .bitwidth = WIDTH, + .atten = ATTEN, + }; + TEST_ASSERT_EQUAL(ESP_OK, adc_oneshot_config_channel(adc1_handle, CHANNEL, &config)); + TEST_ASSERT_EQUAL(ESP_OK, adc_oneshot_del_unit(adc1_handle)); + + /* Re-init ADC for ULP */ + TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_init(&riscv_adc_cfg)); + TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_deinit()); +} diff --git a/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv_i2c.c b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv_i2c.c new file mode 100644 index 0000000000..2be2e701c2 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv_i2c.c @@ -0,0 +1,166 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_riscv.h" +#include "ulp_riscv_i2c.h" +#include "ulp_test_app_i2c.h" +#include "ulp_test_shared.h" +#include "unity.h" +#include "test_utils.h" +#include "esp_log.h" +#include "driver/i2c.h" + +#define ULP_WAKEUP_PERIOD 1000000 // 1 second +static const char* TAG = "ulp_riscv_i2c_test"; + +// ULP RISC-V RTC I2C firmware +extern const uint8_t ulp_test_app_i2c_bin_start[] asm("_binary_ulp_test_app_i2c_bin_start"); +extern const size_t ulp_test_app_i2c_bin_length asm("ulp_test_app_i2c_bin_length"); + +static void load_and_start_ulp_riscv_firmware(const uint8_t* ulp_bin, size_t ulp_bin_len) +{ + TEST_ASSERT(ulp_riscv_load_binary(ulp_bin, ulp_bin_len) == ESP_OK); + TEST_ASSERT(ulp_set_wakeup_period(0, ULP_WAKEUP_PERIOD) == ESP_OK); + TEST_ASSERT(ulp_riscv_run() == ESP_OK); +} + +#define I2C_SLAVE_SCL_IO 7 /*! +#include +#include +#include "ulp_riscv_utils.h" +#include "ulp_riscv_gpio.h" +#include "ulp_riscv_lock_ulp_core.h" +#include "ulp_test_shared.h" + +volatile riscv_test_commands_t main_cpu_command = RISCV_NO_COMMAND; +volatile riscv_test_command_reply_t main_cpu_reply = RISCV_COMMAND_INVALID; +volatile riscv_test_commands_t command_resp = RISCV_NO_COMMAND; +volatile uint32_t riscv_test_data_in = 0; +volatile uint32_t riscv_test_data_out = 0; +volatile uint32_t riscv_counter = 0; + +volatile uint32_t riscv_incrementer = 0; +ulp_riscv_lock_t lock; + +void handle_commands(riscv_test_commands_t cmd) +{ + riscv_counter++; + + switch (cmd) { + case RISCV_READ_WRITE_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = RISCV_READ_WRITE_TEST; + + /* Process test data */ + riscv_test_data_out = riscv_test_data_in ^ XOR_MASK; + + /* Set the command reply status */ + main_cpu_reply = RISCV_COMMAND_OK; + + /* Wakeup the main CPU */ + ulp_riscv_wakeup_main_processor(); + break; + + case RISCV_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = RISCV_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST; + + /* Set the command reply status */ + main_cpu_reply = RISCV_COMMAND_OK; + + ulp_riscv_delay_cycles(1000 * ULP_RISCV_CYCLES_PER_MS); + + /* Wakeup the main CPU */ + ulp_riscv_wakeup_main_processor(); + break; + + case RISCV_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = RISCV_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST; + + /* Set the command reply status */ + main_cpu_reply = RISCV_COMMAND_OK; + + ulp_riscv_delay_cycles(10000 * ULP_RISCV_CYCLES_PER_MS); + + /* Wakeup the main CPU */ + ulp_riscv_wakeup_main_processor(); + break; + + case RISCV_LIGHT_SLEEP_WAKEUP_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = RISCV_LIGHT_SLEEP_WAKEUP_TEST; + + /* Set the command reply status */ + main_cpu_reply = RISCV_COMMAND_OK; + + /* Wakeup the main CPU */ + ulp_riscv_wakeup_main_processor(); + break; + + case RISCV_STOP_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = RISCV_STOP_TEST; + + /* Set the command reply status */ + main_cpu_reply = RISCV_COMMAND_OK; + + /* Will never return from here */ + ulp_riscv_timer_stop(); + ulp_riscv_halt(); + + break; + + case RISCV_MUTEX_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = RISCV_MUTEX_TEST; + + for (int i = 0; i < MUTEX_TEST_ITERATIONS; i++) { + ulp_riscv_lock_acquire(&lock); + riscv_incrementer++; + ulp_riscv_lock_release(&lock); + } + /* Set the command reply status */ + main_cpu_reply = RISCV_COMMAND_OK; + main_cpu_command = RISCV_NO_COMMAND; + + break; + + case RISCV_NO_COMMAND: + main_cpu_reply = RISCV_COMMAND_OK; + break; + + default: + main_cpu_reply = RISCV_COMMAND_NOK; + break; + } +} + +int main(void) +{ + + while (1) { + handle_commands(main_cpu_command); + break; + } + + /* ulp_riscv_halt() is called automatically when main exits */ + return 0; +} diff --git a/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_cocpu_crash.c b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_cocpu_crash.c new file mode 100644 index 0000000000..4b9e243bae --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_cocpu_crash.c @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "ulp_riscv_utils.h" + +int main(void) +{ + // Wait for the main core in the test case to enter lightsleep + ulp_riscv_delay_cycles(100 * ULP_RISCV_CYCLES_PER_MS); + /* Make sure ULP core crashes by doing a NULL pointer access */ + uint32_t *null_ptr = NULL; + *null_ptr = 1; + + /* ulp_riscv_halt() is called automatically when main exits */ + return 0; +} diff --git a/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_i2c.c b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_i2c.c new file mode 100644 index 0000000000..8ab1394df8 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_i2c.c @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_test_shared.h" +// #include "ulp_riscv.h" +#include "ulp_riscv_utils.h" +#include "ulp_riscv_i2c_ulp_core.h" + +volatile riscv_test_command_reply_t read_test_reply = RISCV_COMMAND_INVALID; +volatile riscv_test_command_reply_t write_test_cmd = RISCV_COMMAND_INVALID; + +uint8_t data_rd[DATA_LENGTH] = {}; +uint8_t data_wr[DATA_LENGTH] = {}; + +int main(void) +{ + /* Set I2C slave device address */ + ulp_riscv_i2c_master_set_slave_addr(I2C_SLAVE_ADDRESS); + + /* Read from the I2C slave device */ + ulp_riscv_i2c_master_read_from_device(data_rd, RW_TEST_LENGTH); + + /* Signal the main CPU once read is done */ + read_test_reply = RISCV_COMMAND_OK; + + /* Wait for write command from main CPU */ + while (write_test_cmd != RISCV_COMMAND_OK) { + } + + /* Write to the I2C slave device */ + ulp_riscv_i2c_master_write_to_device(data_wr, RW_TEST_LENGTH); + + /* Signal the main CPU once write is done */ + write_test_cmd = RISCV_COMMAND_NOK; + + while (1) { + } +} diff --git a/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_second_cocpu_firmware.c b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_second_cocpu_firmware.c new file mode 100644 index 0000000000..aae8d58aa3 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main_second_cocpu_firmware.c @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +volatile uint32_t riscv_counter2 = 0; + +int main(void) +{ + riscv_counter2++; + + /* ulp_riscv_halt() is called automatically when main exits */ + return 0; +} diff --git a/components/ulp/test_apps/ulp_riscv/main/ulp/ulp_test_shared.h b/components/ulp/test_apps/ulp_riscv/main/ulp/ulp_test_shared.h new file mode 100644 index 0000000000..33ef18e67a --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/ulp/ulp_test_shared.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#define MUTEX_TEST_ITERATIONS 100000 +#define XOR_MASK 0xDEADBEEF + +/* I2C test params */ +#define I2C_SLAVE_ADDRESS 0x28 +#define DATA_LENGTH 200 +// TODO: Updated the test to perform multi-byte read/write (IDFGH-11056) +#define RW_TEST_LENGTH 1 /*! None: # type: ignore + dut.run_all_single_board_cases() + + +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.generic_multi_device +@pytest.mark.parametrize( + 'count', [2], indirect=True +) +def test_ulp_riscv_multi_device(case_tester) -> None: # type: ignore + for case in case_tester.test_menu: + if case.attributes.get('test_env', 'generic_multi_device') == 'generic_multi_device': + case_tester.run_multi_dev_case(case=case, reset=True) diff --git a/components/ulp/test_apps/ulp_riscv/sdkconfig.defaults b/components/ulp/test_apps/ulp_riscv/sdkconfig.defaults new file mode 100644 index 0000000000..73c24b77f1 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/sdkconfig.defaults @@ -0,0 +1,5 @@ +CONFIG_ESP_TASK_WDT_EN=n + +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_RISCV=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 diff --git a/components/ulp/ulp_common/include/ulp_adc.h b/components/ulp/ulp_common/include/ulp_adc.h new file mode 100644 index 0000000000..e838d4cdbb --- /dev/null +++ b/components/ulp/ulp_common/include/ulp_adc.h @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "hal/adc_types.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + adc_unit_t adc_n; // ADC Unit + adc_channel_t channel; // ADC channel + adc_atten_t atten; // ADC channel attenuation + adc_bitwidth_t width; // ADC bit width, only used for ADC unit 1 + adc_ulp_mode_t ulp_mode; // ADC ULP Mode +} ulp_adc_cfg_t; // ULP FSM ADC configuration parameters + +/** + * @brief Initialize and calibrate the ADC for use by ULP FSM + * + * @param cfg Configuration parameters + * @return esp_err_t ESP_OK for successful. + */ +esp_err_t ulp_adc_init(const ulp_adc_cfg_t *cfg); + +/** + * @brief Deinitialize ADC after use with ULP, allowing it to be reclaimed + * + * @return esp_err_t ESP_OK for successful. +*/ +esp_err_t ulp_adc_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_common/include/ulp_common.h b/components/ulp/ulp_common/include/ulp_common.h new file mode 100644 index 0000000000..bcc9ff36c3 --- /dev/null +++ b/components/ulp/ulp_common/include/ulp_common.h @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __ULP_COMMON_H__ +#define __ULP_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "esp_err.h" +#include "ulp_common_defs.h" + +/** + * @brief Set one of ULP wakeup period values + * + * ULP coprocessor starts running the program when the wakeup timer counts up + * to a given value (called period). There are 5 period values which can be + * programmed into SENS_ULP_CP_SLEEP_CYCx_REG registers, x = 0..4 for ESP32, and + * one period value which can be programmed into RTC_CNTL_ULP_CP_TIMER_1_REG register for ESP32-S2/S3. + * By default, for ESP32, wakeup timer will use the period set into SENS_ULP_CP_SLEEP_CYC0_REG, + * i.e. period number 0. ULP program code can use SLEEP instruction to select + * which of the SENS_ULP_CP_SLEEP_CYCx_REG should be used for subsequent wakeups. + * + * However, please note that SLEEP instruction issued (from ULP program) while the system + * is in deep sleep mode does not have effect, and sleep cycle count 0 is used. + * + * For ESP32-S2/S3 the SLEEP instruction not exist. Instead a WAKE instruction will be used. + * + * @param period_index wakeup period setting number (0 - 4) + * @param period_us wakeup period, us + * @note The ULP FSM requires two clock cycles to wakeup before being able to run the program. + * Then additional 16 cycles are reserved after wakeup waiting until the 8M clock is stable. + * The FSM also requires two more clock cycles to go to sleep after the program execution is halted. + * The minimum wakeup period that may be set up for the ULP + * is equal to the total number of cycles spent on the above internal tasks. + * For a default configuration of the ULP running at 150kHz it makes about 133us. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if period_index is out of range + */ +esp_err_t ulp_set_wakeup_period(size_t period_index, uint32_t period_us); + +/** + * @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_timer_stop(void); + +/** + * @brief Resume the ULP timer + * + * @note This will resume an already configured timer, but does no other configuration + * + */ +void ulp_timer_resume(void); + +#ifdef __cplusplus +} +#endif + +#endif // __ULP_COMMON_H__ diff --git a/components/ulp/ulp_common/include/ulp_common_defs.h b/components/ulp/ulp_common/include/ulp_common_defs.h new file mode 100644 index 0000000000..c96a9a3a85 --- /dev/null +++ b/components/ulp/ulp_common/include/ulp_common_defs.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __ULP_COMMON_DEFS_H__ +#define __ULP_COMMON_DEFS_H__ + +#include "soc/soc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define RTC_SLOW_MEM ((uint32_t*) SOC_RTC_DATA_LOW) + +#ifdef __cplusplus +} +#endif + +#endif // __ULP_COMMON_DEFS_H__ diff --git a/components/ulp/ulp_common/ulp_adc.c b/components/ulp/ulp_common/ulp_adc.c new file mode 100644 index 0000000000..cf9ffa1c6f --- /dev/null +++ b/components/ulp/ulp_common/ulp_adc.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "ulp_adc.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_adc/adc_oneshot.h" +#include "hal/adc_hal_common.h" +#include "esp_private/esp_sleep_internal.h" +#include "esp_private/adc_share_hw_ctrl.h" + +static const char *TAG = "ulp_adc"; +static adc_oneshot_unit_handle_t s_adc1_handle = NULL; + +esp_err_t ulp_adc_init(const ulp_adc_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + + ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, TAG, "cfg == NULL"); + ESP_RETURN_ON_FALSE(cfg->adc_n == ADC_UNIT_1, ESP_ERR_INVALID_ARG, TAG, "Only ADC_UNIT_1 is supported for now"); + + //-------------ADC1 Init---------------// + adc_oneshot_unit_init_cfg_t init_config1 = { + .unit_id = cfg->adc_n, + .ulp_mode = cfg->ulp_mode, + }; + + if (init_config1.ulp_mode == ADC_ULP_MODE_DISABLE) { + /* Default to RISCV for backward compatibility */ + ESP_LOGI(TAG, "No ulp mode specified in cfg struct, default to riscv"); + init_config1.ulp_mode = ADC_ULP_MODE_RISCV; + } + + ret = adc_oneshot_new_unit(&init_config1, &s_adc1_handle); + if (ret != ESP_OK) { + return ret; + } + + //-------------ADC1 Config---------------// + adc_oneshot_chan_cfg_t config = { + .bitwidth = cfg->width, + .atten = cfg->atten, + }; + ret = adc_oneshot_config_channel(s_adc1_handle, cfg->channel, &config); + if (ret != ESP_OK) { + return ret; + } + + //Calibrate the ADC +#if SOC_ADC_CALIBRATION_V1_SUPPORTED + adc_set_hw_calibration_code(cfg->adc_n, cfg->atten); +#endif + + return ret; +} + +esp_err_t ulp_adc_deinit(void) +{ + // No need to check for null-pointer and stuff, oneshot driver already does that + return adc_oneshot_del_unit(s_adc1_handle); +} diff --git a/components/ulp/ulp_common/ulp_common.c b/components/ulp/ulp_common/ulp_common.c new file mode 100644 index 0000000000..b290645e70 --- /dev/null +++ b/components/ulp/ulp_common/ulp_common.c @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_err.h" +#include "esp_log.h" +#include "ulp_common.h" +#include "esp_private/esp_clk.h" +#include "soc/rtc.h" +#include "soc/rtc_cntl_periph.h" + +#if CONFIG_IDF_TARGET_ESP32 +#include "soc/sens_reg.h" +#define ULP_FSM_PREPARE_SLEEP_CYCLES 2 /*!< Cycles spent by FSM preparing ULP for sleep */ +#define ULP_FSM_WAKEUP_SLEEP_CYCLES 2 /*!< Cycles spent by FSM waking up ULP from sleep */ +#endif + +esp_err_t ulp_set_wakeup_period(size_t period_index, uint32_t period_us) +{ + if (period_index > 4) { + return ESP_ERR_INVALID_ARG; + } + + uint64_t period_us_64 = period_us; + +#if CONFIG_IDF_TARGET_ESP32 + uint64_t period_cycles = (period_us_64 << RTC_CLK_CAL_FRACT) / esp_clk_slowclk_cal_get(); + uint64_t min_sleep_period_cycles = ULP_FSM_PREPARE_SLEEP_CYCLES + + ULP_FSM_WAKEUP_SLEEP_CYCLES + + REG_GET_FIELD(RTC_CNTL_TIMER2_REG, RTC_CNTL_ULPCP_TOUCH_START_WAIT); + if (period_cycles < min_sleep_period_cycles) { + period_cycles = min_sleep_period_cycles; + ESP_LOGW("ulp", "Sleep period clipped to minimum of %"PRIu32" cycles", (uint32_t) min_sleep_period_cycles); + } else { + period_cycles -= min_sleep_period_cycles; + } + REG_SET_FIELD(SENS_ULP_CP_SLEEP_CYC0_REG + period_index * sizeof(uint32_t), + SENS_SLEEP_CYCLES_S0, (uint32_t) period_cycles); +#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) + soc_rtc_slow_clk_src_t slow_clk_src = rtc_clk_slow_src_get(); + rtc_cal_sel_t cal_clk = RTC_CAL_RTC_MUX; + if (slow_clk_src == SOC_RTC_SLOW_CLK_SRC_XTAL32K) { + cal_clk = RTC_CAL_32K_XTAL; + } else if (slow_clk_src == SOC_RTC_SLOW_CLK_SRC_RC_FAST_D256) { + cal_clk = RTC_CAL_8MD256; + } + uint32_t slow_clk_period = rtc_clk_cal(cal_clk, 100); + uint64_t period_cycles = rtc_time_us_to_slowclk(period_us_64, slow_clk_period); + + REG_SET_FIELD(RTC_CNTL_ULP_CP_TIMER_1_REG, RTC_CNTL_ULP_CP_TIMER_SLP_CYCLE, ((uint32_t)period_cycles)); +#endif + return ESP_OK; +} + +void ulp_timer_stop(void) +{ +#if CONFIG_IDF_TARGET_ESP32 + CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); +#else + CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); +#endif +} + +void ulp_timer_resume(void) +{ +#if CONFIG_IDF_TARGET_ESP32 + SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); +#else + SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); +#endif +} diff --git a/components/ulp/ulp_fsm/include/esp32/ulp.h b/components/ulp/ulp_fsm/include/esp32/ulp.h new file mode 100644 index 0000000000..5eb33fd683 --- /dev/null +++ b/components/ulp/ulp_fsm/include/esp32/ulp.h @@ -0,0 +1,1023 @@ +/* + * SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include +#include +#include "esp_err.h" +#include "ulp_common.h" +#include "ulp_fsm_common.h" +#include "soc/reg_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup ulp_registers ULP coprocessor registers + * @{ + */ + +#define R0 0 /*!< general purpose register 0 */ +#define R1 1 /*!< general purpose register 1 */ +#define R2 2 /*!< general purpose register 2 */ +#define R3 3 /*!< general purpose register 3 */ +/**@}*/ + +/** @defgroup ulp_opcodes ULP coprocessor opcodes, sub opcodes, and various modifiers/flags + * + * These definitions are not intended to be used directly. + * They are used in definitions of instructions later on. + * + * @{ + */ + +#define OPCODE_WR_REG 1 /*!< Instruction: write peripheral register (RTC_CNTL/RTC_IO/SARADC) */ + +#define OPCODE_RD_REG 2 /*!< Instruction: read peripheral register (RTC_CNTL/RTC_IO/SARADC) */ + +#define RD_REG_PERIPH_RTC_CNTL 0 /*!< Identifier of RTC_CNTL peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_RTC_IO 1 /*!< Identifier of RTC_IO peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_SENS 2 /*!< Identifier of SARADC peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_RTC_I2C 3 /*!< Identifier of RTC_I2C peripheral for RD_REG and WR_REG instructions */ + +#define OPCODE_I2C 3 /*!< Instruction: read/write I2C */ +#define SUB_OPCODE_I2C_RD 0 /*!< I2C read */ +#define SUB_OPCODE_I2C_WR 1 /*!< I2C write */ + +#define OPCODE_DELAY 4 /*!< Instruction: delay (nop) for a given number of cycles */ + +#define OPCODE_ADC 5 /*!< Instruction: SAR ADC measurement */ + +#define OPCODE_ST 6 /*!< Instruction: store indirect to RTC memory */ +#define SUB_OPCODE_ST 4 /*!< Store 32 bits, 16 MSBs contain PC, 16 LSBs contain value from source register */ + +#define OPCODE_ALU 7 /*!< Arithmetic instructions */ +#define SUB_OPCODE_ALU_REG 0 /*!< Arithmetic instruction, both source values are in register */ +#define SUB_OPCODE_ALU_IMM 1 /*!< Arithmetic instruction, one source value is an immediate */ +#define SUB_OPCODE_ALU_CNT 2 /*!< Arithmetic instruction, stage counter and an immediate */ +#define ALU_SEL_ADD 0 /*!< Addition */ +#define ALU_SEL_SUB 1 /*!< Subtraction */ +#define ALU_SEL_AND 2 /*!< Logical AND */ +#define ALU_SEL_OR 3 /*!< Logical OR */ +#define ALU_SEL_MOV 4 /*!< Copy value (immediate to destination register or source register to destination register */ +#define ALU_SEL_LSH 5 /*!< Shift left by given number of bits */ +#define ALU_SEL_RSH 6 /*!< Shift right by given number of bits */ +#define ALU_SEL_SINC 0 /*!< Increment the stage counter */ +#define ALU_SEL_SDEC 1 /*!< Decrement the stage counter */ +#define ALU_SEL_SRST 2 /*!< Reset the stage counter */ + +#define OPCODE_BRANCH 8 /*!< Branch instructions */ +#define SUB_OPCODE_BX 0 /*!< Branch to absolute PC (immediate or in register) */ +#define SUB_OPCODE_BR 1 /*!< Branch to relative PC, conditional on R0 */ +#define SUB_OPCODE_BS 2 /*!< Branch to relative PC, conditional on the stage counter */ +#define BX_JUMP_TYPE_DIRECT 0 /*!< Unconditional jump */ +#define BX_JUMP_TYPE_ZERO 1 /*!< Branch if last ALU result is zero */ +#define BX_JUMP_TYPE_OVF 2 /*!< Branch if last ALU operation caused and overflow */ +#define SUB_OPCODE_B 1 /*!< Branch to a relative offset */ +#define B_CMP_L 0 /*!< Branch if R0 is less than an immediate */ +#define B_CMP_GE 1 /*!< Branch if R0 is greater than or equal to an immediate */ +#define JUMPS_LT 0 /*!< Branch if the stage counter < */ +#define JUMPS_GE 1 /*!< Branch if the stage counter >= */ +#define JUMPS_LE 2 /*!< Branch if the stage counter <= */ + +#define OPCODE_END 9 /*!< Stop executing the program */ +#define SUB_OPCODE_END 0 /*!< Stop executing the program and optionally wake up the chip */ +#define SUB_OPCODE_SLEEP 1 /*!< Stop executing the program and run it again after selected interval */ + +#define OPCODE_TSENS 10 /*!< Instruction: temperature sensor measurement. Poor accuracy, not recommended for most use-cases */ + +#define OPCODE_HALT 11 /*!< Halt the coprocessor */ + +#define OPCODE_LD 13 /*!< Indirect load lower 16 bits from RTC memory */ + +#define OPCODE_MACRO 15 /*!< Not a real opcode. Used to identify labels and branches in the program */ +#define SUB_OPCODE_MACRO_LABEL 0 /*!< Label macro */ +#define SUB_OPCODE_MACRO_BRANCH 1 /*!< Branch macro */ +#define SUB_OPCODE_MACRO_LABELPC 2 /*!< Label pointer macro */ +/**@}*/ + +/** + * @brief Instruction format structure + * + * All ULP instructions are 32 bit long. + * This union contains field layouts used by all of the supported instructions. + * This union also includes a special "macro" instruction layout. + * This is not a real instruction which can be executed by the CPU. It acts + * as a token which is removed from the program by the + * ulp_process_macros_and_load function. + * + * These structures are not intended to be used directly. + * Preprocessor definitions provided below fill the fields of these structure with + * the right arguments. + */ +union ulp_insn { + + struct { + uint32_t cycles : 16; /*!< Number of cycles to sleep */ + uint32_t unused : 12; /*!< Unused */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_DELAY) */ + } delay; /*!< Format of DELAY instruction */ + + struct { + uint32_t dreg : 2; /*!< Register which contains data to store */ + uint32_t sreg : 2; /*!< Register which contains address in RTC memory (expressed in words) */ + uint32_t unused1 : 6; /*!< Unused */ + uint32_t offset : 11; /*!< Offset to add to sreg */ + uint32_t unused2 : 4; /*!< Unused */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_ST) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ST) */ + } st; /*!< Format of ST instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where the data should be loaded to */ + uint32_t sreg : 2; /*!< Register which contains address in RTC memory (expressed in words) */ + uint32_t unused1 : 6; /*!< Unused */ + uint32_t offset : 11; /*!< Offset to add to sreg */ + uint32_t unused2 : 7; /*!< Unused */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_LD) */ + } ld; /*!< Format of LD instruction */ + + struct { + uint32_t unused : 28; /*!< Unused */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_HALT) */ + } halt; /*!< Format of HALT instruction */ + + struct { + uint32_t dreg : 2; /*!< Register which contains target PC, expressed in words (used if .reg == 1) */ + uint32_t addr : 11; /*!< Target PC, expressed in words (used if .reg == 0) */ + uint32_t unused : 8; /*!< Unused */ + uint32_t reg : 1; /*!< Target PC in register (1) or immediate (0) */ + uint32_t type : 3; /*!< Jump condition (BX_JUMP_TYPE_xxx) */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_BX) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_BRANCH) */ + } bx; /*!< Format of BRANCH instruction (absolute address) */ + + struct { + uint32_t imm : 16; /*!< Immediate value to compare against */ + uint32_t cmp : 1; /*!< Comparison to perform: B_CMP_L or B_CMP_GE */ + uint32_t offset : 7; /*!< Absolute value of target PC offset w.r.t. current PC, expressed in words */ + uint32_t sign : 1; /*!< Sign of target PC offset: 0: positive, 1: negative */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_B) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_BRANCH) */ + } b; /*!< Format of BRANCH instruction (relative address, conditional on R0) */ + + struct { + uint32_t imm : 8; /*!< Immediate value to compare against */ + uint32_t unused : 7; /*!< Unused */ + uint32_t cmp : 2; /*!< Comparison to perform: JUMPS_LT, JUMPS_GE or JUMPS_LE */ + uint32_t offset : 7; /*!< Absolute value of target PC offset w.r.t. current PC, expressed in words */ + uint32_t sign : 1; /*!< Sign of target PC offset: 0: positive, 1: negative */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_BS) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_BRANCH) */ + } bs; /*!< Format of BRANCH instruction (relative address, conditional on the stage counter) */ + + struct { + uint32_t dreg : 2; /*!< Destination register */ + uint32_t sreg : 2; /*!< Register with operand A */ + uint32_t treg : 2; /*!< Register with operand B */ + uint32_t unused : 15; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_ALU_REG) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_reg; /*!< Format of ALU instruction (both sources are registers) */ + + struct { + uint32_t unused1 : 4; /*!< Unused */ + uint32_t imm : 8; /*!< Immediate value of operand */ + uint32_t unused2 : 9; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_Sxxx */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_ALU_CNT) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_reg_s; /*!< Format of ALU instruction (stage counter and an immediate) */ + + struct { + uint32_t dreg : 2; /*!< Destination register */ + uint32_t sreg : 2; /*!< Register with operand A */ + uint32_t imm : 16; /*!< Immediate value of operand B */ + uint32_t unused : 1; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_ALU_IMM) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_imm; /*!< Format of ALU instruction (one source is an immediate) */ + + struct { + uint32_t addr : 8; /*!< Address within either RTC_CNTL, RTC_IO, or SARADC */ + uint32_t periph_sel : 2; /*!< Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) */ + uint32_t data : 8; /*!< 8 bits of data to write */ + uint32_t low : 5; /*!< Low bit */ + uint32_t high : 5; /*!< High bit */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_WR_REG) */ + } wr_reg; /*!< Format of WR_REG instruction */ + + struct { + uint32_t addr : 8; /*!< Address within either RTC_CNTL, RTC_IO, or SARADC */ + uint32_t periph_sel : 2; /*!< Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) */ + uint32_t unused : 8; /*!< Unused */ + uint32_t low : 5; /*!< Low bit */ + uint32_t high : 5; /*!< High bit */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_RD_REG) */ + } rd_reg; /*!< Format of RD_REG instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where to store ADC result */ + uint32_t mux : 4; /*!< Select SARADC pad (mux + 1) */ + uint32_t sar_sel : 1; /*!< Select SARADC0 (0) or SARADC1 (1) */ + uint32_t unused1 : 1; /*!< Unused */ + uint32_t cycles : 16; /*!< TBD, cycles used for measurement */ + uint32_t unused2 : 4; /*!< Unused */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_ADC) */ + } adc; /*!< Format of ADC instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where to store temperature measurement result */ + uint32_t wait_delay: 14; /*!< Cycles to wait after measurement is done */ + uint32_t reserved: 12; /*!< Reserved, set to 0 */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_TSENS) */ + } tsens; /*!< Format of TSENS instruction */ + + struct { + uint32_t i2c_addr : 8; /*!< I2C slave address */ + uint32_t data : 8; /*!< 8 bits of data for write operation */ + uint32_t low_bits : 3; /*!< low bit of range for write operation (lower bits are masked) */ + uint32_t high_bits : 3; /*!< high bit of range for write operation (higher bits are masked) */ + uint32_t i2c_sel : 4; /*!< index of slave address register [7:0] */ + uint32_t unused : 1; /*!< Unused */ + uint32_t rw : 1; /*!< Write (1) or read (0) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_I2C) */ + } i2c; /*!< Format of I2C instruction */ + + struct { + uint32_t wakeup : 1; /*!< Set to 1 to wake up chip */ + uint32_t unused : 24; /*!< Unused */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_WAKEUP) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_END) */ + } end; /*!< Format of END instruction with wakeup */ + + struct { + uint32_t cycle_sel : 4; /*!< Select which one of SARADC_ULP_CP_SLEEP_CYCx_REG to get the sleep duration from */ + uint32_t unused : 21; /*!< Unused */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_SLEEP) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_END) */ + } sleep; /*!< Format of END instruction with sleep */ + + struct { + uint32_t dreg : 2; /*!< Destination register (for SUB_OPCODE_MACRO_LABELPC) > */ + uint32_t label : 16; /*!< Label number */ + uint32_t unused : 6; /*!< Unused */ + uint32_t sub_opcode : 4; /*!< SUB_OPCODE_MACRO_LABEL or SUB_OPCODE_MACRO_BRANCH or SUB_OPCODE_MACRO_LABELPC */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_MACRO) */ + } macro; /*!< Format of tokens used by MACROs */ + + uint32_t instruction; /*!< Encoded instruction for ULP coprocessor */ + +}; + +/** + * Delay (nop) for a given number of cycles + */ +#define I_DELAY(cycles_) { .delay = {\ + .cycles = cycles_, \ + .unused = 0, \ + .opcode = OPCODE_DELAY } } + +/** + * Halt the coprocessor. + * + * This instruction halts the coprocessor, but keeps ULP timer active. + * As such, ULP program will be restarted again by timer. + * To stop the program and prevent the timer from restarting the program, + * use I_END(0) instruction. + */ +#define I_HALT() { .halt = {\ + .unused = 0, \ + .opcode = OPCODE_HALT } } + +/** + * Map SoC peripheral register to periph_sel field of RD_REG and WR_REG + * instructions. + * + * @param reg peripheral register in RTC_CNTL_, RTC_IO_, SENS_, RTC_I2C peripherals. + * @return periph_sel value for the peripheral to which this register belongs. + */ +static inline uint32_t SOC_REG_TO_ULP_PERIPH_SEL(uint32_t reg) +{ + uint32_t ret = 3; + if (reg < DR_REG_RTCCNTL_BASE) { + assert(0 && "invalid register base"); + } else if (reg < DR_REG_RTCIO_BASE) { + ret = RD_REG_PERIPH_RTC_CNTL; + } else if (reg < DR_REG_SENS_BASE) { + ret = RD_REG_PERIPH_RTC_IO; + } else if (reg < DR_REG_RTC_I2C_BASE) { + ret = RD_REG_PERIPH_SENS; + } else if (reg < DR_REG_IO_MUX_BASE) { + ret = RD_REG_PERIPH_RTC_I2C; + } else { + assert(0 && "invalid register base"); + } + return ret; +} + +/** + * Write literal value to a peripheral register + * + * reg[high_bit : low_bit] = val + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_WR_REG(reg, low_bit, high_bit, val) {.wr_reg = {\ + .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ + .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ + .data = val, \ + .low = low_bit, \ + .high = high_bit, \ + .opcode = OPCODE_WR_REG } } + +/** + * Read from peripheral register into R0 + * + * R0 = reg[high_bit : low_bit] + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_RD_REG(reg, low_bit, high_bit) {.rd_reg = {\ + .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ + .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ + .unused = 0, \ + .low = low_bit, \ + .high = high_bit, \ + .opcode = OPCODE_RD_REG } } + +/** + * Set or clear a bit in the peripheral register. + * + * Sets bit (1 << shift) of register reg to value val. + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_WR_REG_BIT(reg, shift, val) I_WR_REG(reg, shift, shift, val) + +/** + * Wake the SoC from deep sleep. + * + * This instruction initiates wake up from deep sleep. + * Use esp_deep_sleep_enable_ulp_wakeup to enable deep sleep wakeup + * triggered by the ULP before going into deep sleep. + * Note that ULP program will still keep running until the I_HALT + * instruction, and it will still be restarted by timer at regular + * intervals, even when the SoC is woken up. + * + * To stop the ULP program, use I_HALT instruction. + * + * To disable the timer which start ULP program, use I_END() + * instruction. I_END instruction clears the + * RTC_CNTL_ULP_CP_SLP_TIMER_EN_S bit of RTC_CNTL_STATE0_REG + * register, which controls the ULP timer. + */ +#define I_WAKE() { .end = { \ + .wakeup = 1, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_END, \ + .opcode = OPCODE_END } } + +/** + * Stop ULP program timer. + * + * This is a convenience macro which disables the ULP program timer. + * Once this instruction is used, ULP program will not be restarted + * anymore until ulp_run function is called. + * + * ULP program will continue running after this instruction. To stop + * the currently running program, use I_HALT(). + */ +#define I_END() \ + I_WR_REG_BIT(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN_S, 0) +/** + * Select the time interval used to run ULP program. + * + * This instructions selects which of the SENS_SLEEP_CYCLES_Sx + * registers' value is used by the ULP program timer. + * When the ULP program stops at I_HALT instruction, ULP program + * timer start counting. When the counter reaches the value of + * the selected SENS_SLEEP_CYCLES_Sx register, ULP program + * start running again from the start address (passed to the ulp_run + * function). + * There are 5 SENS_SLEEP_CYCLES_Sx registers, so 0 <= timer_idx < 5. + * + * By default, SENS_SLEEP_CYCLES_S0 register is used by the ULP + * program timer. + */ +#define I_SLEEP_CYCLE_SEL(timer_idx) { .sleep = { \ + .cycle_sel = timer_idx, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_SLEEP, \ + .opcode = OPCODE_END } } + +/** + * Perform temperature sensor measurement and store it into reg_dest. + * + * Delay can be set between 1 and ((1 << 14) - 1). Higher values give + * higher measurement resolution. + */ +#define I_TSENS(reg_dest, delay) { .tsens = { \ + .dreg = reg_dest, \ + .wait_delay = delay, \ + .reserved = 0, \ + .opcode = OPCODE_TSENS } } + +/** + * Perform ADC measurement and store result in reg_dest. + * + * adc_idx selects ADC (0 or 1). + * pad_idx selects ADC pad (0 - 7). + */ +#define I_ADC(reg_dest, adc_idx, pad_idx) { .adc = {\ + .dreg = reg_dest, \ + .mux = pad_idx + 1, \ + .sar_sel = adc_idx, \ + .unused1 = 0, \ + .cycles = 0, \ + .unused2 = 0, \ + .opcode = OPCODE_ADC } } + +/** + * Store value from register reg_val into RTC memory. + * + * The value is written to an offset calculated by adding value of + * reg_addr register and offset_ field (this offset is expressed in 32-bit words). + * 32 bits written to RTC memory are built as follows: + * - bits [31:21] hold the PC of current instruction, expressed in 32-bit words + * - bits [20:18] = 3'b0 + * - bits [17:16] reg_addr (0..3) + * - bits [15:0] are assigned the contents of reg_val + * + * RTC_SLOW_MEM[addr + offset_] = { insn_PC[10:0], 3'b0, reg_addr, reg_val[15:0] } + */ +#define I_ST(reg_val, reg_addr, offset_) { .st = { \ + .dreg = reg_val, \ + .sreg = reg_addr, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ST, \ + .opcode = OPCODE_ST } } + +/** + * Load value from RTC memory into reg_dest register. + * + * Loads 16 LSBs from RTC memory word given by the sum of value in reg_addr and + * value of offset_. + */ +#define I_LD(reg_dest, reg_addr, offset_) { .ld = { \ + .dreg = reg_dest, \ + .sreg = reg_addr, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .opcode = OPCODE_LD } } + +/** + * Branch relative if R0 less than immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BL(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_L, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if R0 greater or equal than immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BGE(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_GE, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Unconditional branch to absolute PC, address in register. + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_DIRECT, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Unconditional branch to absolute PC, immediate address. + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_DIRECT, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU result is zero, address in register. + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXZR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_ZERO, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU result is zero, immediate address. + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXZI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_ZERO, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU overflow, address in register + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXFR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_OVF, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU overflow, immediate address + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXFI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_OVF, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Addition: dest = src1 + src2 + */ +#define I_ADDR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused = 0, \ + .sel = ALU_SEL_ADD, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Subtraction: dest = src1 - src2 + */ +#define I_SUBR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused = 0, \ + .sel = ALU_SEL_SUB, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical AND: dest = src1 & src2 + */ +#define I_ANDR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused = 0, \ + .sel = ALU_SEL_AND, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical OR: dest = src1 | src2 + */ +#define I_ORR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused = 0, \ + .sel = ALU_SEL_OR, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Copy: dest = src + */ +#define I_MOVR(reg_dest, reg_src) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = 0, \ + .unused = 0, \ + .sel = ALU_SEL_MOV, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift left: dest = src << shift + */ +#define I_LSHR(reg_dest, reg_src, reg_shift) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = reg_shift, \ + .unused = 0, \ + .sel = ALU_SEL_LSH, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift right: dest = src >> shift + */ +#define I_RSHR(reg_dest, reg_src, reg_shift) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = reg_shift, \ + .unused = 0, \ + .sel = ALU_SEL_RSH, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Add register and an immediate value: dest = src1 + imm + */ +#define I_ADDI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused = 0, \ + .sel = ALU_SEL_ADD, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Subtract register and an immediate value: dest = src - imm + */ +#define I_SUBI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused = 0, \ + .sel = ALU_SEL_SUB, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical AND register and an immediate value: dest = src & imm + */ +#define I_ANDI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused = 0, \ + .sel = ALU_SEL_AND, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical OR register and an immediate value: dest = src | imm + */ +#define I_ORI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused = 0, \ + .sel = ALU_SEL_OR, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Copy an immediate value into register: dest = imm + */ +#define I_MOVI(reg_dest, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = 0, \ + .imm = imm_, \ + .unused = 0, \ + .sel = ALU_SEL_MOV, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift left register value by an immediate: dest = src << imm + */ +#define I_LSHI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused = 0, \ + .sel = ALU_SEL_LSH, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift right register value by an immediate: dest = val >> imm + */ +#define I_RSHI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused = 0, \ + .sel = ALU_SEL_RSH, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Define a label with number label_num. + * + * This is a macro which doesn't generate a real instruction. + * The token generated by this macro is removed by ulp_process_macros_and_load + * function. Label defined using this macro can be used in branch macros defined + * below. + */ +#define M_LABEL(label_num) { .macro = { \ + .dreg = 0, \ + .label = label_num, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_MACRO_LABEL, \ + .opcode = OPCODE_MACRO } } + +/** + * Token macro used by M_B and M_BX macros. Not to be used directly. + */ +#define M_BRANCH(label_num) { .macro = { \ + .dreg = 0, \ + .label = label_num, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_MACRO_BRANCH, \ + .opcode = OPCODE_MACRO } } + +/** + * Token macro used by M_MOVL macro. Not to be used directly. + */ +#define M_LABELPC(label_num) { .macro = { \ + .dreg = 0, \ + .label = label_num, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_MACRO_LABELPC, \ + .opcode = OPCODE_MACRO } } + +/** + * Macro: Move the program counter at the given label into the register. + * This address can then be used with I_BXR, I_BXZR, I_BXFR, etc. + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_MOVL(reg_dest, label_num) \ + M_LABELPC(label_num), \ + I_MOVI(reg_dest, 0) + +/** + * Macro: branch to label label_num if R0 is less than immediate value. + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BL(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BL(0, imm_value) + +/** + * Macro: branch to label label_num if R0 is greater or equal than immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BGE(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BGE(0, imm_value) + +/** + * Macro: unconditional branch to label + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BX(label_num) \ + M_BRANCH(label_num), \ + I_BXI(0) + +/** + * Macro: branch to label if ALU result is zero + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BXZ(label_num) \ + M_BRANCH(label_num), \ + I_BXZI(0) + +/** + * Macro: branch to label if ALU overflow + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BXF(label_num) \ + M_BRANCH(label_num), \ + I_BXFI(0) + +/** + * Increment the stage counter by immediate value + */ +#define I_STAGE_INC(imm_) { .alu_reg_s = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_SINC, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Decrement the stage counter by immediate value + */ +#define I_STAGE_DEC(imm_) { .alu_reg_s = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_SDEC, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Reset the stage counter + */ +#define I_STAGE_RST() { .alu_reg_s = { \ + .unused1 = 0, \ + .imm = 0, \ + .unused2 = 0, \ + .sel = ALU_SEL_SRST, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Macro: branch to label if the stage counter is less than immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BSLT(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_JUMPS(0, imm_value, JUMPS_LT) + +/** + * Macro: branch to label if the stage counter is greater than or equal to immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BSGE(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_JUMPS(0, imm_value, JUMPS_GE) + +/** + * Macro: branch to label if the stage counter is less than or equal to immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BSLE(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_JUMPS(0, imm_value, JUMPS_LE) + +/** + * Macro: branch to label if the stage counter is equal to immediate value. + * Implemented using two JUMPS instructions: + * JUMPS next, imm_value, LT + * JUMPS label_num, imm_value, LE + * + * This macro generates three ulp_insn_t values separated by commas, and should + * be used when defining contents of ulp_insn_t arrays. Second value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BSEQ(label_num, imm_value) \ + I_JUMPS(2, imm_value, JUMPS_LT), \ + M_BRANCH(label_num), \ + I_JUMPS(0, imm_value, JUMPS_LE) + +/** + * Macro: branch to label if the stage counter is greater than immediate value. + * Implemented using two instructions: + * JUMPS next, imm_value, LE + * JUMPS label_num, imm_value, GE + * + * This macro generates three ulp_insn_t values separated by commas, and should + * be used when defining contents of ulp_insn_t arrays. Second value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BSGT(label_num, imm_value) \ + I_JUMPS(2, imm_value, JUMPS_LE), \ + M_BRANCH(label_num), \ + I_JUMPS(0, imm_value, JUMPS_GE) + +/** + * Branch relative if (stage counter [comp_type] [imm_value]) evaluates to true. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is an 8-bit value to compare the stage counter against + * comp_type is the type of comparison to perform: JUMPS_LT (<), JUMPS_GE (>=) or JUMPS_LE (<=) + */ +#define I_JUMPS(pc_offset, imm_value, comp_type) { .bs = { \ + .imm = imm_value, \ + .unused = 0, \ + .cmp = comp_type, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_BS, \ + .opcode = OPCODE_BRANCH } } + +/** + * Perform an I2C transaction with a slave device. + * I_I2C_READ and I_I2C_WRITE are provided for convenience, instead of using this directly. + * + * Slave address (in 7-bit format) has to be set in advance into SENS_I2C_SLAVE_ADDRx register field, where x == slave_sel. + * For read operations, 8 bits of read result is stored into R0 register. + * For write operations, val will be written to sub_addr at [high_bit:low_bit]. Bits outside of this range are masked. + */ +#define I_I2C_RW(sub_addr, val, low_bit, high_bit, slave_sel, rw_bit) { .i2c = {\ + .i2c_addr = sub_addr, \ + .data = val, \ + .low_bits = low_bit, \ + .high_bits = high_bit, \ + .i2c_sel = slave_sel, \ + .unused = 0, \ + .rw = rw_bit, \ + .opcode = OPCODE_I2C } } + +/** + * Read a byte from the sub address of an I2C slave, and store the result in R0. + * + * Slave address (in 7-bit format) has to be set in advance into SENS_I2C_SLAVE_ADDRx register field, where x == slave_sel. + */ +#define I_I2C_READ(slave_sel, sub_addr) I_I2C_RW(sub_addr, 0, 0, 0, slave_sel, SUB_OPCODE_I2C_RD) + +/** + * Write a byte to the sub address of an I2C slave. + * + * Slave address (in 7-bit format) has to be set in advance into SENS_I2C_SLAVE_ADDRx register field, where x == slave_sel. + */ +#define I_I2C_WRITE(slave_sel, sub_addr, val) I_I2C_RW(sub_addr, val, 0, 7, slave_sel, SUB_OPCODE_I2C_WR) + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_fsm/include/esp32s2/ulp.h b/components/ulp/ulp_fsm/include/esp32s2/ulp.h new file mode 100644 index 0000000000..67cdc19e40 --- /dev/null +++ b/components/ulp/ulp_fsm/include/esp32s2/ulp.h @@ -0,0 +1,1145 @@ +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include +#include +#include "esp_err.h" +#include "ulp_common.h" +#include "ulp_fsm_common.h" +#include "soc/reg_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup ulp_registers ULP coprocessor registers + * @{ + */ + +#define R0 0 /*!< general purpose register 0 */ +#define R1 1 /*!< general purpose register 1 */ +#define R2 2 /*!< general purpose register 2 */ +#define R3 3 /*!< general purpose register 3 */ +/**@}*/ + +/** @defgroup ulp_opcodes ULP coprocessor opcodes, sub opcodes, and various modifiers/flags + * + * These definitions are not intended to be used directly. + * They are used in definitions of instructions later on. + * + * @{ + */ + +#define OPCODE_WR_REG 1 /*!< Instruction: write peripheral register (RTC_CNTL/RTC_IO/SARADC) (not implemented yet) */ + +#define OPCODE_RD_REG 2 /*!< Instruction: read peripheral register (RTC_CNTL/RTC_IO/SARADC) (not implemented yet) */ + +#define RD_REG_PERIPH_RTC_CNTL 0 /*!< Identifier of RTC_CNTL peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_RTC_IO 1 /*!< Identifier of RTC_IO peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_SENS 2 /*!< Identifier of SARADC peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_RTC_I2C 3 /*!< Identifier of RTC_I2C peripheral for RD_REG and WR_REG instructions */ + +#define OPCODE_I2C 3 /*!< Instruction: read/write I2C (not implemented yet) */ + +#define OPCODE_DELAY 4 /*!< Instruction: delay (nop) for a given number of cycles */ + +#define OPCODE_ADC 5 /*!< Instruction: SAR ADC measurement (not implemented yet) */ + +#define OPCODE_ST 6 /*!< Instruction: store indirect to RTC memory */ +#define SUB_OPCODE_ST_AUTO 1 /*!< Automatic Storage Mode - Access continuous addresses. Use SUB_OPCODE_ST_OFFSET to configure the initial address before using this instruction. */ +#define SUB_OPCODE_ST_OFFSET 3 /*!< Automatic Storage Mode - Configure the initial address. */ +#define SUB_OPCODE_ST 4 /*!< Manual Storage Mode. Store 32 bits, 16 MSBs contain PC, 16 LSBs contain value from source register */ + +#define OPCODE_ALU 7 /*!< Arithmetic instructions */ +#define SUB_OPCODE_ALU_REG 0 /*!< Arithmetic instruction, both source values are in register */ +#define SUB_OPCODE_ALU_IMM 1 /*!< Arithmetic instruction, one source value is an immediate */ +#define SUB_OPCODE_ALU_CNT 2 /*!< Arithmetic instruction between counter register and an immediate (not implemented yet)*/ +#define ALU_SEL_ADD 0 /*!< Addition */ +#define ALU_SEL_SUB 1 /*!< Subtraction */ +#define ALU_SEL_AND 2 /*!< Logical AND */ +#define ALU_SEL_OR 3 /*!< Logical OR */ +#define ALU_SEL_MOV 4 /*!< Copy value (immediate to destination register or source register to destination register */ +#define ALU_SEL_LSH 5 /*!< Shift left by given number of bits */ +#define ALU_SEL_RSH 6 /*!< Shift right by given number of bits */ +#define ALU_SEL_STAGE_INC 0 /*!< Increment stage count register */ +#define ALU_SEL_STAGE_DEC 1 /*!< Decrement stage count register */ +#define ALU_SEL_STAGE_RST 2 /*!< Reset stage count register */ + +#define OPCODE_BRANCH 8 /*!< Branch instructions */ +#define SUB_OPCODE_B 0 /*!< Branch to a relative offset */ +#define SUB_OPCODE_BX 1 /*!< Branch to absolute PC (immediate or in register) */ +#define SUB_OPCODE_BS 2 /*!< Branch to a relative offset by comparing the stage_cnt register */ +#define BX_JUMP_TYPE_DIRECT 0 /*!< Unconditional jump */ +#define BX_JUMP_TYPE_ZERO 1 /*!< Branch if last ALU result is zero */ +#define BX_JUMP_TYPE_OVF 2 /*!< Branch if last ALU operation caused and overflow */ +#define B_CMP_L 0 /*!< Branch if R0 is less than an immediate */ +#define B_CMP_G 1 /*!< Branch if R0 is greater than an immediate */ +#define B_CMP_E 2 /*!< Branch if R0 is equal to an immediate */ +#define BS_CMP_L 0 /*!< Branch if stage_cnt is less than an immediate */ +#define BS_CMP_GE 1 /*!< Branch if stage_cnt is greater than or equal to an immediate */ +#define BS_CMP_LE 2 /*!< Branch if stage_cnt is less than or equal to an immediate */ + +#define OPCODE_END 9 /*!< Stop executing the program */ +#define SUB_OPCODE_END 0 /*!< Stop executing the program and optionally wake up the chip */ +#define SUB_OPCODE_SLEEP 1 /*!< Stop executing the program and run it again after selected interval */ + +#define OPCODE_TSENS 10 /*!< Instruction: temperature sensor measurement (not implemented yet) */ + +#define OPCODE_HALT 11 /*!< Halt the coprocessor */ + +#define OPCODE_LD 13 /*!< Indirect load lower 16 bits from RTC memory */ + +#define OPCODE_MACRO 15 /*!< Not a real opcode. Used to identify labels and branches in the program */ +#define SUB_OPCODE_MACRO_LABEL 0 /*!< Label macro */ +#define SUB_OPCODE_MACRO_BRANCH 1 /*!< Branch macro */ +#define SUB_OPCODE_MACRO_LABELPC 2 /*!< Label pointer macro */ +/**@}*/ + +/** + * @brief Instruction format structure + * + * All ULP instructions are 32 bit long. + * This union contains field layouts used by all of the supported instructions. + * This union also includes a special "macro" instruction layout. + * This is not a real instruction which can be executed by the CPU. It acts + * as a token which is removed from the program by the + * ulp_process_macros_and_load function. + * + * These structures are not intended to be used directly. + * Preprocessor definitions provided below fill the fields of these structure with + * the right arguments. + */ +union ulp_insn { + + struct { + uint32_t cycles : 16; /*!< Number of cycles to sleep */ + uint32_t unused : 12; /*!< Unused */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_DELAY) */ + } delay; /*!< Format of DELAY instruction */ + + struct { + uint32_t dreg : 2; /*!< Register which contains data to store */ + uint32_t sreg : 2; /*!< Register which contains address in RTC memory (expressed in words) */ + uint32_t label: 2; /*!< Data label, 2-bit user defined unsigned value */ + uint32_t upper: 1; /*!< 0: write the low half-word; 1: write the high half-word */ + uint32_t wr_way: 2; /*!< 0: write the full-word; 1: with the label; 3: without the label */ + uint32_t unused1 : 1; /*!< Unused */ + uint32_t offset : 11; /*!< Offset to add to sreg */ + uint32_t unused2 : 4; /*!< Unused */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_ST) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ST) */ + } st; /*!< Format of ST instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where the data should be loaded to */ + uint32_t sreg : 2; /*!< Register which contains address in RTC memory (expressed in words) */ + uint32_t unused1 : 6; /*!< Unused */ + uint32_t offset : 11; /*!< Offset to add to sreg */ + uint32_t unused2 : 6; /*!< Unused */ + uint32_t rd_upper: 1; /*!< 0: read the high half-word; 1: read the low half-word*/ + uint32_t opcode : 4; /*!< Opcode (OPCODE_LD) */ + } ld; /*!< Format of LD instruction */ + + struct { + uint32_t unused : 28; /*!< Unused */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_HALT) */ + } halt; /*!< Format of HALT instruction */ + + struct { + uint32_t dreg : 2; /*!< Register which contains target PC, expressed in words (used if .reg == 1) */ + uint32_t addr : 11; /*!< Target PC, expressed in words (used if .reg == 0) */ + uint32_t unused1 : 8; /*!< Unused */ + uint32_t reg : 1; /*!< Target PC in register (1) or immediate (0) */ + uint32_t type : 3; /*!< Jump condition (BX_JUMP_TYPE_xxx) */ + uint32_t unused2 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_BX) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_BRANCH) */ + } bx; /*!< Format of BRANCH instruction (absolute address) */ + + struct { + uint32_t imm : 16; /*!< Immediate value to compare against */ + uint32_t cmp : 2; /*!< Comparison to perform: B_CMP_L or B_CMP_GE */ + uint32_t offset : 7; /*!< Absolute value of target PC offset w.r.t. current PC, expressed in words */ + uint32_t sign : 1; /*!< Sign of target PC offset: 0: positive, 1: negative */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_B) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_BRANCH) */ + } b; /*!< Format of BRANCH instruction (relative address) */ + + struct { + uint32_t dreg : 2; /*!< Destination register */ + uint32_t sreg : 2; /*!< Register with operand A */ + uint32_t treg : 2; /*!< Register with operand B */ + uint32_t unused1 : 15; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t unused2 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_ALU_REG) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_reg; /*!< Format of ALU instruction (both sources are registers) */ + + struct { + uint32_t dreg : 2; /*!< Destination register */ + uint32_t sreg : 2; /*!< Register with operand A */ + uint32_t imm : 16; /*!< Immediate value of operand B */ + uint32_t unused1: 1; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t unused2 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_ALU_IMM) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_imm; /*!< Format of ALU instruction (one source is an immediate) */ + + struct { + uint32_t unused1: 4; /*!< Unused */ + uint32_t imm : 8; /*!< Immediate value */ + uint32_t unused2: 9; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t unused3 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_ALU_CNT) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_cnt; /*!< Format of ALU instruction with stage count register and an immediate */ + + struct { + uint32_t addr : 8; /*!< Address within either RTC_CNTL, RTC_IO, or SARADC */ + uint32_t periph_sel : 2; /*!< Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) */ + uint32_t data : 8; /*!< 8 bits of data to write */ + uint32_t low : 5; /*!< Low bit */ + uint32_t high : 5; /*!< High bit */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_WR_REG) */ + } wr_reg; /*!< Format of WR_REG instruction */ + + struct { + uint32_t addr : 8; /*!< Address within either RTC_CNTL, RTC_IO, or SARADC */ + uint32_t periph_sel : 2; /*!< Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) */ + uint32_t unused : 8; /*!< Unused */ + uint32_t low : 5; /*!< Low bit */ + uint32_t high : 5; /*!< High bit */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_RD_REG) */ + } rd_reg; /*!< Format of RD_REG instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where to store ADC result */ + uint32_t mux : 4; /*!< Select SARADC pad (mux + 1) */ + uint32_t sar_sel : 1; /*!< Select SARADC0 (0) or SARADC1 (1) */ + uint32_t unused1 : 1; /*!< Unused */ + uint32_t cycles : 16; /*!< TBD, cycles used for measurement */ + uint32_t unused2 : 4; /*!< Unused */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_ADC) */ + } adc; /*!< Format of ADC instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where to store temperature measurement result */ + uint32_t wait_delay: 14; /*!< Cycles to wait after measurement is done */ + uint32_t reserved: 12; /*!< Reserved, set to 0 */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_TSENS) */ + } tsens; /*!< Format of TSENS instruction */ + + struct { + uint32_t i2c_addr : 8; /*!< I2C slave address */ + uint32_t data : 8; /*!< Data to read or write */ + uint32_t low_bits : 3; /*!< TBD */ + uint32_t high_bits : 3; /*!< TBD */ + uint32_t i2c_sel : 4; /*!< TBD, select reg_i2c_slave_address[7:0] */ + uint32_t unused : 1; /*!< Unused */ + uint32_t rw : 1; /*!< Write (1) or read (0) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_I2C) */ + } i2c; /*!< Format of I2C instruction */ + + struct { + uint32_t wakeup : 1; /*!< Set to 1 to wake up chip */ + uint32_t unused : 25; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_WAKEUP) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_END) */ + } end; /*!< Format of END instruction with wakeup */ + + struct { + uint32_t label : 16; /*!< Label number */ + uint32_t unused : 8; /*!< Unused */ + uint32_t sub_opcode : 4; /*!< SUB_OPCODE_MACRO_LABEL or SUB_OPCODE_MACRO_BRANCH */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_MACRO) */ + } macro; /*!< Format of tokens used by LABEL and BRANCH macros */ + +}; + +/** + * Delay (nop) for a given number of cycles + */ +#define I_DELAY(cycles_) { .delay = {\ + .cycles = cycles_, \ + .unused = 0, \ + .opcode = OPCODE_DELAY } } + +/** + * Halt the coprocessor. + * + * This instruction halts the coprocessor, but keeps ULP timer active. + * As such, ULP program will be restarted again by timer. + * To stop the program and prevent the timer from restarting the program, + * use I_END(0) instruction. + */ +#define I_HALT() { .halt = {\ + .unused = 0, \ + .opcode = OPCODE_HALT } } + +/** + * Map SoC peripheral register to periph_sel field of RD_REG and WR_REG + * instructions. + * + * @param reg peripheral register in RTC_CNTL_, RTC_IO_, SENS_, RTC_I2C peripherals. + * @return periph_sel value for the peripheral to which this register belongs. + */ +static inline uint32_t SOC_REG_TO_ULP_PERIPH_SEL(uint32_t reg) +{ + uint32_t ret = 3; + if (reg < DR_REG_RTCCNTL_BASE) { + assert(0 && "invalid register base"); + } else if (reg < DR_REG_RTCIO_BASE) { + ret = RD_REG_PERIPH_RTC_CNTL; + } else if (reg < DR_REG_SENS_BASE) { + ret = RD_REG_PERIPH_RTC_IO; + } else if (reg < DR_REG_RTC_I2C_BASE) { + ret = RD_REG_PERIPH_SENS; + } else if (reg < DR_REG_IO_MUX_BASE) { + ret = RD_REG_PERIPH_RTC_I2C; + } else { + assert(0 && "invalid register base"); + } + return ret; +} + +/** + * Write literal value to a peripheral register + * + * reg[high_bit : low_bit] = val + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_WR_REG(reg, low_bit, high_bit, val) {.wr_reg = {\ + .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ + .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ + .data = val, \ + .low = low_bit, \ + .high = high_bit, \ + .opcode = OPCODE_WR_REG } } + +/** + * Read from peripheral register into R0 + * + * R0 = reg[high_bit : low_bit] + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_RD_REG(reg, low_bit, high_bit) {.rd_reg = {\ + .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ + .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ + .unused = 0, \ + .low = low_bit, \ + .high = high_bit, \ + .opcode = OPCODE_RD_REG } } + +/** + * Set or clear a bit in the peripheral register. + * + * Sets bit (1 << shift) of register reg to value val. + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_WR_REG_BIT(reg, shift, val) I_WR_REG(reg, shift, shift, val) + +/** + * Wake the SoC from deep sleep. + * + * This instruction initiates wake up from deep sleep. + * Use esp_deep_sleep_enable_ulp_wakeup to enable deep sleep wakeup + * triggered by the ULP before going into deep sleep. + * Note that ULP program will still keep running until the I_HALT + * instruction, and it will still be restarted by timer at regular + * intervals, even when the SoC is woken up. + * + * To stop the ULP program, use I_HALT instruction. + * + * To disable the timer which start ULP program, use I_END() + * instruction. I_END instruction clears the + * RTC_CNTL_ULP_CP_SLP_TIMER_EN_S bit of RTC_CNTL_ULP_CP_TIMER_REG + * register, which controls the ULP timer. + */ +#define I_WAKE() { .end = { \ + .wakeup = 1, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_END, \ + .opcode = OPCODE_END } } + +/** + * Stop ULP program timer. + * + * This is a convenience macro which disables the ULP program timer. + * Once this instruction is used, ULP program will not be restarted + * anymore until ulp_run function is called. + * + * ULP program will continue running after this instruction. To stop + * the currently running program, use I_HALT(). + */ +#define I_END() \ + I_WR_REG_BIT(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN_S, 0) + +/** + * Perform temperature sensor measurement and store it into reg_dest. + * + * Delay can be set between 1 and ((1 << 14) - 1). Higher values give + * higher measurement resolution. + */ +#define I_TSENS(reg_dest, delay) { .tsens = { \ + .dreg = reg_dest, \ + .wait_delay = delay, \ + .reserved = 0, \ + .opcode = OPCODE_TSENS } } + +/** + * Perform ADC measurement and store result in reg_dest. + * + * adc_idx selects ADC (0 or 1). + * pad_idx selects ADC pad (0 - 7). + */ +#define I_ADC(reg_dest, adc_idx, pad_idx) { .adc = {\ + .dreg = reg_dest, \ + .mux = pad_idx + 1, \ + .sar_sel = adc_idx, \ + .unused1 = 0, \ + .cycles = 0, \ + .unused2 = 0, \ + .opcode = OPCODE_ADC } } + +/** + * Store lower half-word, upper half-word or full-word data from register reg_val into RTC memory address. + * + * This instruction can be used to write data to discontinuous addresses in the RTC_SLOW_MEM. + * The value is written to an offset calculated by adding the value of + * reg_addr register and offset_ field (this offset is expressed in 32-bit words). + * The storage method is dictated by the wr_way and upper field settings as summarized in the following table: + * + * @verbatim + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | wr_way | upper | data | operation | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Write full-word, including | + * | 0 | X | RTC_SLOW_MEM[addr + offset_]{31:0} = {insn_PC[10:0], 3’b0, label_[1:0], reg_val[15:0]} | the PC and the data | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | 0 | RTC_SLOW_MEM[addr + offset_]{15:0} = {label_[1:0], reg_val[13:0]} | in the low half-word | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | 1 | RTC_SLOW_MEM[addr + offset_]{31:16} = {label_[1:0], reg_val[13:0]} | in the high half-word | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | 0 | RTC_SLOW_MEM[addr + offset_]{15:0} = reg_val[15:0] | label in the low half-word | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | 1 | RTC_SLOW_MEM[addr + offset_]{31:16} = reg_val[15:0] | label in the high half-word| + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * @endverbatim + * + * SUB_OPCODE_ST = manual_en:1, offset_set:0, wr_auto:0 + */ +#define I_ST_MANUAL(reg_val, reg_addr, offset_, label_, upper_, wr_way_) { .st = { \ + .dreg = reg_val, \ + .sreg = reg_addr, \ + .label = label_, \ + .upper = upper_, \ + .wr_way = wr_way_, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ST, \ + .opcode = OPCODE_ST } } + +/** + * Store value from register reg_val into RTC memory. + * + * I_ST() instruction provides backward compatibility for code written for esp32 to be run on esp32s2. + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = 0, upper = 0 and wr_way = 3. + */ +#define I_ST(reg_val, reg_addr, offset_) I_ST_MANUAL(reg_val, reg_addr, offset_, 0, 0, 3) + +/** + * Store value from register reg_val to lower 16 bits of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = 0, upper = 0 and wr_way = 3. + */ +#define I_STL(reg_val, reg_addr, offset_) I_ST_MANUAL(reg_val, reg_addr, offset_, 0, 0, 3) + +/** + * Store value from register reg_val to upper 16 bits of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = 0, upper = 1 and wr_way = 3. + */ +#define I_STH(reg_val, reg_addr, offset_) I_ST_MANUAL(reg_val, reg_addr, offset_, 0, 1, 3) + +/** + * Store value from register reg_val to full 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with wr_way = 0. + */ +#define I_ST32(reg_val, reg_addr, offset_, label_) I_ST_MANUAL(reg_val, reg_addr, offset_, label_, 0, 0) + +/** + * Store value from register reg_val with label to lower 16 bits of RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = label_, upper = 0 and wr_way = 1. + */ +#define I_STL_LABEL(reg_val, reg_addr, offset_, label_) I_ST_MANUAL(reg_val, reg_addr, offset_, label_, 0, 1) + +/** + * Store value from register reg_val with label to upper 16 bits of RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = label_, upper = 1 and wr_way = 1. + */ +#define I_STH_LABEL(reg_val, reg_addr, offset_, label_) I_ST_MANUAL(reg_val, reg_addr, offset_, label_, 1, 1) + +/** + * Store lower half-word, upper half-word or full-word data from register reg_val into RTC memory address with auto-increment of the offset value. + * + * This instruction can be used to write data to continuous addresses in the RTC_SLOW_MEM. + * The initial address must be set using the SUB_OPCODE_ST_OFFSET instruction before the auto store instruction is called. + * The data written to the RTC memory address could be written to the full 32 bit word or to the lower half-word or the + * upper half-word. The storage method is dictated by the wr_way field and the number of times the SUB_OPCODE_ST_AUTO instruction is called. + * write_cnt indicates the later. The following table summarizes the storage method: + * + * @verbatim + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | wr_way | write_cnt | data | operation | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Write full-word, including | + * | 0 | X | RTC_SLOW_MEM[addr + offset_]{31:0} = {insn_PC[10:0], 3’b0, label_[1:0], reg_val[15:0]} | the PC and the data | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | odd | RTC_SLOW_MEM[addr + offset_]{15:0} = {label_[1:0], reg_val[13:0]} | in the low half-word | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | even | RTC_SLOW_MEM[addr + offset_]{31:16} = {label_[1:0], reg_val[13:0]} | in the high half-word | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | odd | RTC_SLOW_MEM[addr + offset_]{15:0} = reg_val[15:0] | label in the low half-word | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | even | RTC_SLOW_MEM[addr + offset_]{31:16} = reg_val[15:0] | label in the high half-word| + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * @endverbatim + * + * The initial address offset is incremented after each store operation as follows: + * - When a full-word is written, the offset is automatically incremented by 1 after each SUB_OPCODE_ST_AUTO operation. + * - When a half-word is written (lower half-word first), the offset is automatically incremented by 1 after two + * SUB_OPCODE_ST_AUTO operations. + * + * SUB_OPCODE_ST_AUTO = manual_en:0, offset_set:0, wr_auto:1 + */ +#define I_ST_AUTO(reg_val, reg_addr, label_, wr_way_) { .st = { \ + .dreg = reg_addr, \ + .sreg = reg_val, \ + .label = label_, \ + .upper = 0, \ + .wr_way = wr_way_, \ + .unused1 = 0, \ + .offset = 0, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ST_AUTO, \ + .opcode = OPCODE_ST } } + +/** + * Set the initial address offset for auto-store operation + * + * This instruction sets the initial address of the RTC_SLOW_MEM to be used by the auto-store operation. + * The offset is incremented automatically. + * Refer I_ST_AUTO() for detailed explaination. + * + * SUB_OPCODE_ST_OFFSET = manual_en:0, offset_set:1, wr_auto:1 + */ +#define I_STO(offset_) { .st = { \ + .dreg = 0, \ + .sreg = 0, \ + .label = 0, \ + .upper = 0, \ + .wr_way = 0, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ST_OFFSET, \ + .opcode = OPCODE_ST } } + +/** + * Store value from register reg_val to 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_AUTO() instruction with label = 0 and wr_way = 3. + * The data in reg_val will be either written to the lower half-word or the upper half-word of the RTC memory address + * depending on the count of the number of times the I_STI() instruction is called. + * The initial offset is automatically incremented with I_STI() is called twice. + * Refer I_ST_AUTO() for detailed explaination. + */ +#define I_STI(reg_val, reg_addr) I_ST_AUTO(reg_val, reg_addr, 0, 3) + +/** + * Store value from register reg_val with label to 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_AUTO() instruction with label = label_ and wr_way = 1. + * The data in reg_val will be either written to the lower half-word or the upper half-word of the RTC memory address + * depending on the count of the number of times the I_STI_LABEL() instruction is called. + * The initial offset is automatically incremented with I_STI_LABEL() is called twice. + * Refer I_ST_AUTO() for detailed explaination. + */ +#define I_STI_LABEL(reg_val, reg_addr, label_) I_ST_AUTO(reg_val, reg_addr, label_, 1) + +/** + * Store value from register reg_val to full 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_AUTO() instruction with label = label_ and wr_way = 0. + * The data in reg_val will be written to the RTC memory address along with the label and the PC. + * The initial offset is automatically incremented each time the I_STI32() instruction is called. + * Refer I_ST_AUTO() for detailed explaination. + */ +#define I_STI32(reg_val, reg_addr, label_) I_ST_AUTO(reg_val, reg_addr, label_, 0) + +/** + * Load lower half-word, upper half-word or full-word data from RTC memory address into the register reg_dest. + * + * This instruction reads the lower half-word or upper half-word of the RTC memory address depending on the value + * of rd_upper_. The following table summarizes the loading method: + * + * @verbatim + * |----------|------------------------------------------------------|-------------------------| + * | rd_upper | data | operation | + * |----------|------------------------------------------------------|-------------------------| + * | | | Read lower half-word of | + * | 0 | reg_dest{15:0} = RTC_SLOW_MEM[addr + offset_]{31:16} | the memory | + * |----------|------------------------------------------------------|-------------------------| + * | | | Read upper half-word of | + * | 1 | reg_dest{15:0} = RTC_SLOW_MEM[addr + offset_]{15:0} | the memory | + * |----------|------------------------------------------------------|-------------------------| + * @endverbatim + * + */ +#define I_LD_MANUAL(reg_dest, reg_addr, offset_, rd_upper_) { .ld = { \ + .dreg = reg_dest, \ + .sreg = reg_addr, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .rd_upper = rd_upper_, \ + .opcode = OPCODE_LD } } + +/** + * Load lower 16 bits value from RTC memory into reg_dest register. + * + * Loads 16 LSBs (rd_upper = 1) from RTC memory word given by the sum of value in reg_addr and + * value of offset_. + * I_LD() instruction provides backward compatibility for code written for esp32 to be run on esp32s2. + */ +#define I_LD(reg_dest, reg_addr, offset_) I_LD_MANUAL(reg_dest, reg_addr, offset_, 0) + +/** + * Load lower 16 bits value from RTC memory into reg_dest register. + * + * I_LDL() instruction and I_LD() instruction can be used interchangably. + */ +#define I_LDL(reg_dest, reg_addr, offset_) I_LD(reg_dest, reg_addr, offset_) + +/** + * Load upper 16 bits value from RTC memory into reg_dest register. + * + * Loads 16 MSBs (rd_upper = 0) from RTC memory word given by the sum of value in reg_addr and + * value of offset_. + */ +#define I_LDH(reg_dest, reg_addr, offset_) I_LD_MANUAL(reg_dest, reg_addr, offset_, 1) + +/** + * Branch relative if R0 register less than the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BL(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_L, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if R0 register greater than the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BG(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_G, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if R0 register is equal to the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BE(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_E, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Unconditional branch to absolute PC, address in register. + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused1 = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_DIRECT, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Unconditional branch to absolute PC, immediate address. + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused1 = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_DIRECT, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU result is zero, address in register. + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXZR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused1 = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_ZERO, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU result is zero, immediate address. + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXZI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused1 = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_ZERO, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU overflow, address in register + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXFR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused1 = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_OVF, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU overflow, immediate address + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXFI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused1 = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_OVF, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if stage_cnt is less than or equal to the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BSLE(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = BS_CMP_LE, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_BS, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if stage_cnt register is greater than or equal to the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BSGE(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = BS_CMP_GE, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_BS, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if stage_cnt register is less than the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BSL(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = BS_CMP_L, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_BS, \ + .opcode = OPCODE_BRANCH } } + +/** + * Addition: dest = src1 + src2 + */ +#define I_ADDR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_ADD, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Subtraction: dest = src1 - src2 + */ +#define I_SUBR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_SUB, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical AND: dest = src1 & src2 + */ +#define I_ANDR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_AND, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical OR: dest = src1 | src2 + */ +#define I_ORR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_OR, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Copy: dest = src + */ +#define I_MOVR(reg_dest, reg_src) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = 0, \ + .unused1 = 0, \ + .sel = ALU_SEL_MOV, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift left: dest = src << shift + */ +#define I_LSHR(reg_dest, reg_src, reg_shift) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = reg_shift, \ + .unused1 = 0, \ + .sel = ALU_SEL_LSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift right: dest = src >> shift + */ +#define I_RSHR(reg_dest, reg_src, reg_shift) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = reg_shift, \ + .unused1 = 0, \ + .sel = ALU_SEL_RSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Add register and an immediate value: dest = src1 + imm + */ +#define I_ADDI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_ADD, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Subtract register and an immediate value: dest = src - imm + */ +#define I_SUBI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_SUB, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical AND register and an immediate value: dest = src & imm + */ +#define I_ANDI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_AND, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical OR register and an immediate value: dest = src | imm + */ +#define I_ORI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_OR, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Copy an immediate value into register: dest = imm + */ +#define I_MOVI(reg_dest, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = 0, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_MOV, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift left register value by an immediate: dest = src << imm + */ +#define I_LSHI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_LSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift right register value by an immediate: dest = val >> imm + */ +#define I_RSHI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_RSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Increment stage_cnt register by an immediate: stage_cnt = stage_cnt + imm + */ +#define I_STAGE_INC(reg_dest, reg_src, imm_) { .alu_cnt = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_STAGE_INC, \ + .unused3 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Decrement stage_cnt register by an immediate: stage_cnt = stage_cnt - imm + */ +#define I_STAGE_DEC(reg_dest, reg_src, imm_) { .alu_cnt = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_STAGE_DEC, \ + .unused3 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Reset stage_cnt register by an immediate: stage_cnt = 0 + */ +#define I_STAGE_RST(reg_dest, reg_src, imm_) { .alu_cnt = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_STAGE_RST, \ + .unused3 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Define a label with number label_num. + * + * This is a macro which doesn't generate a real instruction. + * The token generated by this macro is removed by ulp_process_macros_and_load + * function. Label defined using this macro can be used in branch macros defined + * below. + */ +#define M_LABEL(label_num) { .macro = { \ + .label = label_num, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_MACRO_LABEL, \ + .opcode = OPCODE_MACRO } } + +/** + * Token macro used by M_B and M_BX macros. Not to be used directly. + */ +#define M_BRANCH(label_num) { .macro = { \ + .label = label_num, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_MACRO_BRANCH, \ + .opcode = OPCODE_MACRO } } + +/** + * Macro: branch to label label_num if R0 is less than immediate value. + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BL(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BL(0, imm_value) + +/** + * Macro: branch to label label_num if R0 is greater than immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BG(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BG(0, imm_value) + +/** + * Macro: branch to label label_num if R0 equal to the immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BE(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BE(0, imm_value) + +/** + * Macro: unconditional branch to label + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BX(label_num) \ + M_BRANCH(label_num), \ + I_BXI(0) + +/** + * Macro: branch to label if ALU result is zero + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BXZ(label_num) \ + M_BRANCH(label_num), \ + I_BXZI(0) + +/** + * Macro: branch to label if ALU overflow + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BXF(label_num) \ + M_BRANCH(label_num), \ + I_BXFI(0) + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_fsm/include/esp32s3/ulp.h b/components/ulp/ulp_fsm/include/esp32s3/ulp.h new file mode 100644 index 0000000000..67cdc19e40 --- /dev/null +++ b/components/ulp/ulp_fsm/include/esp32s3/ulp.h @@ -0,0 +1,1145 @@ +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include +#include +#include "esp_err.h" +#include "ulp_common.h" +#include "ulp_fsm_common.h" +#include "soc/reg_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup ulp_registers ULP coprocessor registers + * @{ + */ + +#define R0 0 /*!< general purpose register 0 */ +#define R1 1 /*!< general purpose register 1 */ +#define R2 2 /*!< general purpose register 2 */ +#define R3 3 /*!< general purpose register 3 */ +/**@}*/ + +/** @defgroup ulp_opcodes ULP coprocessor opcodes, sub opcodes, and various modifiers/flags + * + * These definitions are not intended to be used directly. + * They are used in definitions of instructions later on. + * + * @{ + */ + +#define OPCODE_WR_REG 1 /*!< Instruction: write peripheral register (RTC_CNTL/RTC_IO/SARADC) (not implemented yet) */ + +#define OPCODE_RD_REG 2 /*!< Instruction: read peripheral register (RTC_CNTL/RTC_IO/SARADC) (not implemented yet) */ + +#define RD_REG_PERIPH_RTC_CNTL 0 /*!< Identifier of RTC_CNTL peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_RTC_IO 1 /*!< Identifier of RTC_IO peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_SENS 2 /*!< Identifier of SARADC peripheral for RD_REG and WR_REG instructions */ +#define RD_REG_PERIPH_RTC_I2C 3 /*!< Identifier of RTC_I2C peripheral for RD_REG and WR_REG instructions */ + +#define OPCODE_I2C 3 /*!< Instruction: read/write I2C (not implemented yet) */ + +#define OPCODE_DELAY 4 /*!< Instruction: delay (nop) for a given number of cycles */ + +#define OPCODE_ADC 5 /*!< Instruction: SAR ADC measurement (not implemented yet) */ + +#define OPCODE_ST 6 /*!< Instruction: store indirect to RTC memory */ +#define SUB_OPCODE_ST_AUTO 1 /*!< Automatic Storage Mode - Access continuous addresses. Use SUB_OPCODE_ST_OFFSET to configure the initial address before using this instruction. */ +#define SUB_OPCODE_ST_OFFSET 3 /*!< Automatic Storage Mode - Configure the initial address. */ +#define SUB_OPCODE_ST 4 /*!< Manual Storage Mode. Store 32 bits, 16 MSBs contain PC, 16 LSBs contain value from source register */ + +#define OPCODE_ALU 7 /*!< Arithmetic instructions */ +#define SUB_OPCODE_ALU_REG 0 /*!< Arithmetic instruction, both source values are in register */ +#define SUB_OPCODE_ALU_IMM 1 /*!< Arithmetic instruction, one source value is an immediate */ +#define SUB_OPCODE_ALU_CNT 2 /*!< Arithmetic instruction between counter register and an immediate (not implemented yet)*/ +#define ALU_SEL_ADD 0 /*!< Addition */ +#define ALU_SEL_SUB 1 /*!< Subtraction */ +#define ALU_SEL_AND 2 /*!< Logical AND */ +#define ALU_SEL_OR 3 /*!< Logical OR */ +#define ALU_SEL_MOV 4 /*!< Copy value (immediate to destination register or source register to destination register */ +#define ALU_SEL_LSH 5 /*!< Shift left by given number of bits */ +#define ALU_SEL_RSH 6 /*!< Shift right by given number of bits */ +#define ALU_SEL_STAGE_INC 0 /*!< Increment stage count register */ +#define ALU_SEL_STAGE_DEC 1 /*!< Decrement stage count register */ +#define ALU_SEL_STAGE_RST 2 /*!< Reset stage count register */ + +#define OPCODE_BRANCH 8 /*!< Branch instructions */ +#define SUB_OPCODE_B 0 /*!< Branch to a relative offset */ +#define SUB_OPCODE_BX 1 /*!< Branch to absolute PC (immediate or in register) */ +#define SUB_OPCODE_BS 2 /*!< Branch to a relative offset by comparing the stage_cnt register */ +#define BX_JUMP_TYPE_DIRECT 0 /*!< Unconditional jump */ +#define BX_JUMP_TYPE_ZERO 1 /*!< Branch if last ALU result is zero */ +#define BX_JUMP_TYPE_OVF 2 /*!< Branch if last ALU operation caused and overflow */ +#define B_CMP_L 0 /*!< Branch if R0 is less than an immediate */ +#define B_CMP_G 1 /*!< Branch if R0 is greater than an immediate */ +#define B_CMP_E 2 /*!< Branch if R0 is equal to an immediate */ +#define BS_CMP_L 0 /*!< Branch if stage_cnt is less than an immediate */ +#define BS_CMP_GE 1 /*!< Branch if stage_cnt is greater than or equal to an immediate */ +#define BS_CMP_LE 2 /*!< Branch if stage_cnt is less than or equal to an immediate */ + +#define OPCODE_END 9 /*!< Stop executing the program */ +#define SUB_OPCODE_END 0 /*!< Stop executing the program and optionally wake up the chip */ +#define SUB_OPCODE_SLEEP 1 /*!< Stop executing the program and run it again after selected interval */ + +#define OPCODE_TSENS 10 /*!< Instruction: temperature sensor measurement (not implemented yet) */ + +#define OPCODE_HALT 11 /*!< Halt the coprocessor */ + +#define OPCODE_LD 13 /*!< Indirect load lower 16 bits from RTC memory */ + +#define OPCODE_MACRO 15 /*!< Not a real opcode. Used to identify labels and branches in the program */ +#define SUB_OPCODE_MACRO_LABEL 0 /*!< Label macro */ +#define SUB_OPCODE_MACRO_BRANCH 1 /*!< Branch macro */ +#define SUB_OPCODE_MACRO_LABELPC 2 /*!< Label pointer macro */ +/**@}*/ + +/** + * @brief Instruction format structure + * + * All ULP instructions are 32 bit long. + * This union contains field layouts used by all of the supported instructions. + * This union also includes a special "macro" instruction layout. + * This is not a real instruction which can be executed by the CPU. It acts + * as a token which is removed from the program by the + * ulp_process_macros_and_load function. + * + * These structures are not intended to be used directly. + * Preprocessor definitions provided below fill the fields of these structure with + * the right arguments. + */ +union ulp_insn { + + struct { + uint32_t cycles : 16; /*!< Number of cycles to sleep */ + uint32_t unused : 12; /*!< Unused */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_DELAY) */ + } delay; /*!< Format of DELAY instruction */ + + struct { + uint32_t dreg : 2; /*!< Register which contains data to store */ + uint32_t sreg : 2; /*!< Register which contains address in RTC memory (expressed in words) */ + uint32_t label: 2; /*!< Data label, 2-bit user defined unsigned value */ + uint32_t upper: 1; /*!< 0: write the low half-word; 1: write the high half-word */ + uint32_t wr_way: 2; /*!< 0: write the full-word; 1: with the label; 3: without the label */ + uint32_t unused1 : 1; /*!< Unused */ + uint32_t offset : 11; /*!< Offset to add to sreg */ + uint32_t unused2 : 4; /*!< Unused */ + uint32_t sub_opcode : 3; /*!< Sub opcode (SUB_OPCODE_ST) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ST) */ + } st; /*!< Format of ST instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where the data should be loaded to */ + uint32_t sreg : 2; /*!< Register which contains address in RTC memory (expressed in words) */ + uint32_t unused1 : 6; /*!< Unused */ + uint32_t offset : 11; /*!< Offset to add to sreg */ + uint32_t unused2 : 6; /*!< Unused */ + uint32_t rd_upper: 1; /*!< 0: read the high half-word; 1: read the low half-word*/ + uint32_t opcode : 4; /*!< Opcode (OPCODE_LD) */ + } ld; /*!< Format of LD instruction */ + + struct { + uint32_t unused : 28; /*!< Unused */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_HALT) */ + } halt; /*!< Format of HALT instruction */ + + struct { + uint32_t dreg : 2; /*!< Register which contains target PC, expressed in words (used if .reg == 1) */ + uint32_t addr : 11; /*!< Target PC, expressed in words (used if .reg == 0) */ + uint32_t unused1 : 8; /*!< Unused */ + uint32_t reg : 1; /*!< Target PC in register (1) or immediate (0) */ + uint32_t type : 3; /*!< Jump condition (BX_JUMP_TYPE_xxx) */ + uint32_t unused2 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_BX) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_BRANCH) */ + } bx; /*!< Format of BRANCH instruction (absolute address) */ + + struct { + uint32_t imm : 16; /*!< Immediate value to compare against */ + uint32_t cmp : 2; /*!< Comparison to perform: B_CMP_L or B_CMP_GE */ + uint32_t offset : 7; /*!< Absolute value of target PC offset w.r.t. current PC, expressed in words */ + uint32_t sign : 1; /*!< Sign of target PC offset: 0: positive, 1: negative */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_B) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_BRANCH) */ + } b; /*!< Format of BRANCH instruction (relative address) */ + + struct { + uint32_t dreg : 2; /*!< Destination register */ + uint32_t sreg : 2; /*!< Register with operand A */ + uint32_t treg : 2; /*!< Register with operand B */ + uint32_t unused1 : 15; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t unused2 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_ALU_REG) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_reg; /*!< Format of ALU instruction (both sources are registers) */ + + struct { + uint32_t dreg : 2; /*!< Destination register */ + uint32_t sreg : 2; /*!< Register with operand A */ + uint32_t imm : 16; /*!< Immediate value of operand B */ + uint32_t unused1: 1; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t unused2 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_ALU_IMM) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_imm; /*!< Format of ALU instruction (one source is an immediate) */ + + struct { + uint32_t unused1: 4; /*!< Unused */ + uint32_t imm : 8; /*!< Immediate value */ + uint32_t unused2: 9; /*!< Unused */ + uint32_t sel : 4; /*!< Operation to perform, one of ALU_SEL_xxx */ + uint32_t unused3 : 1; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_ALU_CNT) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_ALU) */ + } alu_cnt; /*!< Format of ALU instruction with stage count register and an immediate */ + + struct { + uint32_t addr : 8; /*!< Address within either RTC_CNTL, RTC_IO, or SARADC */ + uint32_t periph_sel : 2; /*!< Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) */ + uint32_t data : 8; /*!< 8 bits of data to write */ + uint32_t low : 5; /*!< Low bit */ + uint32_t high : 5; /*!< High bit */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_WR_REG) */ + } wr_reg; /*!< Format of WR_REG instruction */ + + struct { + uint32_t addr : 8; /*!< Address within either RTC_CNTL, RTC_IO, or SARADC */ + uint32_t periph_sel : 2; /*!< Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) */ + uint32_t unused : 8; /*!< Unused */ + uint32_t low : 5; /*!< Low bit */ + uint32_t high : 5; /*!< High bit */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_RD_REG) */ + } rd_reg; /*!< Format of RD_REG instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where to store ADC result */ + uint32_t mux : 4; /*!< Select SARADC pad (mux + 1) */ + uint32_t sar_sel : 1; /*!< Select SARADC0 (0) or SARADC1 (1) */ + uint32_t unused1 : 1; /*!< Unused */ + uint32_t cycles : 16; /*!< TBD, cycles used for measurement */ + uint32_t unused2 : 4; /*!< Unused */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_ADC) */ + } adc; /*!< Format of ADC instruction */ + + struct { + uint32_t dreg : 2; /*!< Register where to store temperature measurement result */ + uint32_t wait_delay: 14; /*!< Cycles to wait after measurement is done */ + uint32_t reserved: 12; /*!< Reserved, set to 0 */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_TSENS) */ + } tsens; /*!< Format of TSENS instruction */ + + struct { + uint32_t i2c_addr : 8; /*!< I2C slave address */ + uint32_t data : 8; /*!< Data to read or write */ + uint32_t low_bits : 3; /*!< TBD */ + uint32_t high_bits : 3; /*!< TBD */ + uint32_t i2c_sel : 4; /*!< TBD, select reg_i2c_slave_address[7:0] */ + uint32_t unused : 1; /*!< Unused */ + uint32_t rw : 1; /*!< Write (1) or read (0) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_I2C) */ + } i2c; /*!< Format of I2C instruction */ + + struct { + uint32_t wakeup : 1; /*!< Set to 1 to wake up chip */ + uint32_t unused : 25; /*!< Unused */ + uint32_t sub_opcode : 2; /*!< Sub opcode (SUB_OPCODE_WAKEUP) */ + uint32_t opcode : 4; /*!< Opcode (OPCODE_END) */ + } end; /*!< Format of END instruction with wakeup */ + + struct { + uint32_t label : 16; /*!< Label number */ + uint32_t unused : 8; /*!< Unused */ + uint32_t sub_opcode : 4; /*!< SUB_OPCODE_MACRO_LABEL or SUB_OPCODE_MACRO_BRANCH */ + uint32_t opcode: 4; /*!< Opcode (OPCODE_MACRO) */ + } macro; /*!< Format of tokens used by LABEL and BRANCH macros */ + +}; + +/** + * Delay (nop) for a given number of cycles + */ +#define I_DELAY(cycles_) { .delay = {\ + .cycles = cycles_, \ + .unused = 0, \ + .opcode = OPCODE_DELAY } } + +/** + * Halt the coprocessor. + * + * This instruction halts the coprocessor, but keeps ULP timer active. + * As such, ULP program will be restarted again by timer. + * To stop the program and prevent the timer from restarting the program, + * use I_END(0) instruction. + */ +#define I_HALT() { .halt = {\ + .unused = 0, \ + .opcode = OPCODE_HALT } } + +/** + * Map SoC peripheral register to periph_sel field of RD_REG and WR_REG + * instructions. + * + * @param reg peripheral register in RTC_CNTL_, RTC_IO_, SENS_, RTC_I2C peripherals. + * @return periph_sel value for the peripheral to which this register belongs. + */ +static inline uint32_t SOC_REG_TO_ULP_PERIPH_SEL(uint32_t reg) +{ + uint32_t ret = 3; + if (reg < DR_REG_RTCCNTL_BASE) { + assert(0 && "invalid register base"); + } else if (reg < DR_REG_RTCIO_BASE) { + ret = RD_REG_PERIPH_RTC_CNTL; + } else if (reg < DR_REG_SENS_BASE) { + ret = RD_REG_PERIPH_RTC_IO; + } else if (reg < DR_REG_RTC_I2C_BASE) { + ret = RD_REG_PERIPH_SENS; + } else if (reg < DR_REG_IO_MUX_BASE) { + ret = RD_REG_PERIPH_RTC_I2C; + } else { + assert(0 && "invalid register base"); + } + return ret; +} + +/** + * Write literal value to a peripheral register + * + * reg[high_bit : low_bit] = val + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_WR_REG(reg, low_bit, high_bit, val) {.wr_reg = {\ + .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ + .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ + .data = val, \ + .low = low_bit, \ + .high = high_bit, \ + .opcode = OPCODE_WR_REG } } + +/** + * Read from peripheral register into R0 + * + * R0 = reg[high_bit : low_bit] + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_RD_REG(reg, low_bit, high_bit) {.rd_reg = {\ + .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ + .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ + .unused = 0, \ + .low = low_bit, \ + .high = high_bit, \ + .opcode = OPCODE_RD_REG } } + +/** + * Set or clear a bit in the peripheral register. + * + * Sets bit (1 << shift) of register reg to value val. + * This instruction can access RTC_CNTL_, RTC_IO_, SENS_, and RTC_I2C peripheral registers. + */ +#define I_WR_REG_BIT(reg, shift, val) I_WR_REG(reg, shift, shift, val) + +/** + * Wake the SoC from deep sleep. + * + * This instruction initiates wake up from deep sleep. + * Use esp_deep_sleep_enable_ulp_wakeup to enable deep sleep wakeup + * triggered by the ULP before going into deep sleep. + * Note that ULP program will still keep running until the I_HALT + * instruction, and it will still be restarted by timer at regular + * intervals, even when the SoC is woken up. + * + * To stop the ULP program, use I_HALT instruction. + * + * To disable the timer which start ULP program, use I_END() + * instruction. I_END instruction clears the + * RTC_CNTL_ULP_CP_SLP_TIMER_EN_S bit of RTC_CNTL_ULP_CP_TIMER_REG + * register, which controls the ULP timer. + */ +#define I_WAKE() { .end = { \ + .wakeup = 1, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_END, \ + .opcode = OPCODE_END } } + +/** + * Stop ULP program timer. + * + * This is a convenience macro which disables the ULP program timer. + * Once this instruction is used, ULP program will not be restarted + * anymore until ulp_run function is called. + * + * ULP program will continue running after this instruction. To stop + * the currently running program, use I_HALT(). + */ +#define I_END() \ + I_WR_REG_BIT(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN_S, 0) + +/** + * Perform temperature sensor measurement and store it into reg_dest. + * + * Delay can be set between 1 and ((1 << 14) - 1). Higher values give + * higher measurement resolution. + */ +#define I_TSENS(reg_dest, delay) { .tsens = { \ + .dreg = reg_dest, \ + .wait_delay = delay, \ + .reserved = 0, \ + .opcode = OPCODE_TSENS } } + +/** + * Perform ADC measurement and store result in reg_dest. + * + * adc_idx selects ADC (0 or 1). + * pad_idx selects ADC pad (0 - 7). + */ +#define I_ADC(reg_dest, adc_idx, pad_idx) { .adc = {\ + .dreg = reg_dest, \ + .mux = pad_idx + 1, \ + .sar_sel = adc_idx, \ + .unused1 = 0, \ + .cycles = 0, \ + .unused2 = 0, \ + .opcode = OPCODE_ADC } } + +/** + * Store lower half-word, upper half-word or full-word data from register reg_val into RTC memory address. + * + * This instruction can be used to write data to discontinuous addresses in the RTC_SLOW_MEM. + * The value is written to an offset calculated by adding the value of + * reg_addr register and offset_ field (this offset is expressed in 32-bit words). + * The storage method is dictated by the wr_way and upper field settings as summarized in the following table: + * + * @verbatim + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | wr_way | upper | data | operation | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Write full-word, including | + * | 0 | X | RTC_SLOW_MEM[addr + offset_]{31:0} = {insn_PC[10:0], 3’b0, label_[1:0], reg_val[15:0]} | the PC and the data | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | 0 | RTC_SLOW_MEM[addr + offset_]{15:0} = {label_[1:0], reg_val[13:0]} | in the low half-word | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | 1 | RTC_SLOW_MEM[addr + offset_]{31:16} = {label_[1:0], reg_val[13:0]} | in the high half-word | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | 0 | RTC_SLOW_MEM[addr + offset_]{15:0} = reg_val[15:0] | label in the low half-word | + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | 1 | RTC_SLOW_MEM[addr + offset_]{31:16} = reg_val[15:0] | label in the high half-word| + * |--------|-------|----------------------------------------------------------------------------------------|----------------------------| + * @endverbatim + * + * SUB_OPCODE_ST = manual_en:1, offset_set:0, wr_auto:0 + */ +#define I_ST_MANUAL(reg_val, reg_addr, offset_, label_, upper_, wr_way_) { .st = { \ + .dreg = reg_val, \ + .sreg = reg_addr, \ + .label = label_, \ + .upper = upper_, \ + .wr_way = wr_way_, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ST, \ + .opcode = OPCODE_ST } } + +/** + * Store value from register reg_val into RTC memory. + * + * I_ST() instruction provides backward compatibility for code written for esp32 to be run on esp32s2. + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = 0, upper = 0 and wr_way = 3. + */ +#define I_ST(reg_val, reg_addr, offset_) I_ST_MANUAL(reg_val, reg_addr, offset_, 0, 0, 3) + +/** + * Store value from register reg_val to lower 16 bits of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = 0, upper = 0 and wr_way = 3. + */ +#define I_STL(reg_val, reg_addr, offset_) I_ST_MANUAL(reg_val, reg_addr, offset_, 0, 0, 3) + +/** + * Store value from register reg_val to upper 16 bits of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = 0, upper = 1 and wr_way = 3. + */ +#define I_STH(reg_val, reg_addr, offset_) I_ST_MANUAL(reg_val, reg_addr, offset_, 0, 1, 3) + +/** + * Store value from register reg_val to full 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with wr_way = 0. + */ +#define I_ST32(reg_val, reg_addr, offset_, label_) I_ST_MANUAL(reg_val, reg_addr, offset_, label_, 0, 0) + +/** + * Store value from register reg_val with label to lower 16 bits of RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = label_, upper = 0 and wr_way = 1. + */ +#define I_STL_LABEL(reg_val, reg_addr, offset_, label_) I_ST_MANUAL(reg_val, reg_addr, offset_, label_, 0, 1) + +/** + * Store value from register reg_val with label to upper 16 bits of RTC memory address. + * + * This instruction is equivalent to calling I_ST_MANUAL() instruction with label = label_, upper = 1 and wr_way = 1. + */ +#define I_STH_LABEL(reg_val, reg_addr, offset_, label_) I_ST_MANUAL(reg_val, reg_addr, offset_, label_, 1, 1) + +/** + * Store lower half-word, upper half-word or full-word data from register reg_val into RTC memory address with auto-increment of the offset value. + * + * This instruction can be used to write data to continuous addresses in the RTC_SLOW_MEM. + * The initial address must be set using the SUB_OPCODE_ST_OFFSET instruction before the auto store instruction is called. + * The data written to the RTC memory address could be written to the full 32 bit word or to the lower half-word or the + * upper half-word. The storage method is dictated by the wr_way field and the number of times the SUB_OPCODE_ST_AUTO instruction is called. + * write_cnt indicates the later. The following table summarizes the storage method: + * + * @verbatim + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | wr_way | write_cnt | data | operation | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Write full-word, including | + * | 0 | X | RTC_SLOW_MEM[addr + offset_]{31:0} = {insn_PC[10:0], 3’b0, label_[1:0], reg_val[15:0]} | the PC and the data | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | odd | RTC_SLOW_MEM[addr + offset_]{15:0} = {label_[1:0], reg_val[13:0]} | in the low half-word | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data with label | + * | 1 | even | RTC_SLOW_MEM[addr + offset_]{31:16} = {label_[1:0], reg_val[13:0]} | in the high half-word | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | odd | RTC_SLOW_MEM[addr + offset_]{15:0} = reg_val[15:0] | label in the low half-word | + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * | | | | Store the data without | + * | 3 | even | RTC_SLOW_MEM[addr + offset_]{31:16} = reg_val[15:0] | label in the high half-word| + * |--------|-----------|----------------------------------------------------------------------------------------|----------------------------| + * @endverbatim + * + * The initial address offset is incremented after each store operation as follows: + * - When a full-word is written, the offset is automatically incremented by 1 after each SUB_OPCODE_ST_AUTO operation. + * - When a half-word is written (lower half-word first), the offset is automatically incremented by 1 after two + * SUB_OPCODE_ST_AUTO operations. + * + * SUB_OPCODE_ST_AUTO = manual_en:0, offset_set:0, wr_auto:1 + */ +#define I_ST_AUTO(reg_val, reg_addr, label_, wr_way_) { .st = { \ + .dreg = reg_addr, \ + .sreg = reg_val, \ + .label = label_, \ + .upper = 0, \ + .wr_way = wr_way_, \ + .unused1 = 0, \ + .offset = 0, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ST_AUTO, \ + .opcode = OPCODE_ST } } + +/** + * Set the initial address offset for auto-store operation + * + * This instruction sets the initial address of the RTC_SLOW_MEM to be used by the auto-store operation. + * The offset is incremented automatically. + * Refer I_ST_AUTO() for detailed explaination. + * + * SUB_OPCODE_ST_OFFSET = manual_en:0, offset_set:1, wr_auto:1 + */ +#define I_STO(offset_) { .st = { \ + .dreg = 0, \ + .sreg = 0, \ + .label = 0, \ + .upper = 0, \ + .wr_way = 0, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ST_OFFSET, \ + .opcode = OPCODE_ST } } + +/** + * Store value from register reg_val to 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_AUTO() instruction with label = 0 and wr_way = 3. + * The data in reg_val will be either written to the lower half-word or the upper half-word of the RTC memory address + * depending on the count of the number of times the I_STI() instruction is called. + * The initial offset is automatically incremented with I_STI() is called twice. + * Refer I_ST_AUTO() for detailed explaination. + */ +#define I_STI(reg_val, reg_addr) I_ST_AUTO(reg_val, reg_addr, 0, 3) + +/** + * Store value from register reg_val with label to 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_AUTO() instruction with label = label_ and wr_way = 1. + * The data in reg_val will be either written to the lower half-word or the upper half-word of the RTC memory address + * depending on the count of the number of times the I_STI_LABEL() instruction is called. + * The initial offset is automatically incremented with I_STI_LABEL() is called twice. + * Refer I_ST_AUTO() for detailed explaination. + */ +#define I_STI_LABEL(reg_val, reg_addr, label_) I_ST_AUTO(reg_val, reg_addr, label_, 1) + +/** + * Store value from register reg_val to full 32 bit word of the RTC memory address. + * + * This instruction is equivalent to calling I_ST_AUTO() instruction with label = label_ and wr_way = 0. + * The data in reg_val will be written to the RTC memory address along with the label and the PC. + * The initial offset is automatically incremented each time the I_STI32() instruction is called. + * Refer I_ST_AUTO() for detailed explaination. + */ +#define I_STI32(reg_val, reg_addr, label_) I_ST_AUTO(reg_val, reg_addr, label_, 0) + +/** + * Load lower half-word, upper half-word or full-word data from RTC memory address into the register reg_dest. + * + * This instruction reads the lower half-word or upper half-word of the RTC memory address depending on the value + * of rd_upper_. The following table summarizes the loading method: + * + * @verbatim + * |----------|------------------------------------------------------|-------------------------| + * | rd_upper | data | operation | + * |----------|------------------------------------------------------|-------------------------| + * | | | Read lower half-word of | + * | 0 | reg_dest{15:0} = RTC_SLOW_MEM[addr + offset_]{31:16} | the memory | + * |----------|------------------------------------------------------|-------------------------| + * | | | Read upper half-word of | + * | 1 | reg_dest{15:0} = RTC_SLOW_MEM[addr + offset_]{15:0} | the memory | + * |----------|------------------------------------------------------|-------------------------| + * @endverbatim + * + */ +#define I_LD_MANUAL(reg_dest, reg_addr, offset_, rd_upper_) { .ld = { \ + .dreg = reg_dest, \ + .sreg = reg_addr, \ + .unused1 = 0, \ + .offset = offset_, \ + .unused2 = 0, \ + .rd_upper = rd_upper_, \ + .opcode = OPCODE_LD } } + +/** + * Load lower 16 bits value from RTC memory into reg_dest register. + * + * Loads 16 LSBs (rd_upper = 1) from RTC memory word given by the sum of value in reg_addr and + * value of offset_. + * I_LD() instruction provides backward compatibility for code written for esp32 to be run on esp32s2. + */ +#define I_LD(reg_dest, reg_addr, offset_) I_LD_MANUAL(reg_dest, reg_addr, offset_, 0) + +/** + * Load lower 16 bits value from RTC memory into reg_dest register. + * + * I_LDL() instruction and I_LD() instruction can be used interchangably. + */ +#define I_LDL(reg_dest, reg_addr, offset_) I_LD(reg_dest, reg_addr, offset_) + +/** + * Load upper 16 bits value from RTC memory into reg_dest register. + * + * Loads 16 MSBs (rd_upper = 0) from RTC memory word given by the sum of value in reg_addr and + * value of offset_. + */ +#define I_LDH(reg_dest, reg_addr, offset_) I_LD_MANUAL(reg_dest, reg_addr, offset_, 1) + +/** + * Branch relative if R0 register less than the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BL(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_L, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if R0 register greater than the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BG(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_G, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if R0 register is equal to the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BE(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = B_CMP_E, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_B, \ + .opcode = OPCODE_BRANCH } } + +/** + * Unconditional branch to absolute PC, address in register. + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused1 = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_DIRECT, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Unconditional branch to absolute PC, immediate address. + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused1 = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_DIRECT, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU result is zero, address in register. + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXZR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused1 = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_ZERO, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU result is zero, immediate address. + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXZI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused1 = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_ZERO, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU overflow, address in register + * + * reg_pc is the register which contains address to jump to. + * Address is expressed in 32-bit words. + */ +#define I_BXFR(reg_pc) { .bx = { \ + .dreg = reg_pc, \ + .addr = 0, \ + .unused1 = 0, \ + .reg = 1, \ + .type = BX_JUMP_TYPE_OVF, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch to absolute PC if ALU overflow, immediate address + * + * Address imm_pc is expressed in 32-bit words. + */ +#define I_BXFI(imm_pc) { .bx = { \ + .dreg = 0, \ + .addr = imm_pc, \ + .unused1 = 0, \ + .reg = 0, \ + .type = BX_JUMP_TYPE_OVF, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_BX, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if stage_cnt is less than or equal to the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BSLE(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = BS_CMP_LE, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_BS, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if stage_cnt register is greater than or equal to the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BSGE(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = BS_CMP_GE, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_BS, \ + .opcode = OPCODE_BRANCH } } + +/** + * Branch relative if stage_cnt register is less than the immediate value. + * + * pc_offset is expressed in words, and can be from -127 to 127 + * imm_value is a 16-bit value to compare R0 against + */ +#define I_BSL(pc_offset, imm_value) { .b = { \ + .imm = imm_value, \ + .cmp = BS_CMP_L, \ + .offset = abs(pc_offset), \ + .sign = (pc_offset >= 0) ? 0 : 1, \ + .sub_opcode = SUB_OPCODE_BS, \ + .opcode = OPCODE_BRANCH } } + +/** + * Addition: dest = src1 + src2 + */ +#define I_ADDR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_ADD, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Subtraction: dest = src1 - src2 + */ +#define I_SUBR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_SUB, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical AND: dest = src1 & src2 + */ +#define I_ANDR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_AND, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical OR: dest = src1 | src2 + */ +#define I_ORR(reg_dest, reg_src1, reg_src2) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src1, \ + .treg = reg_src2, \ + .unused1 = 0, \ + .sel = ALU_SEL_OR, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Copy: dest = src + */ +#define I_MOVR(reg_dest, reg_src) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = 0, \ + .unused1 = 0, \ + .sel = ALU_SEL_MOV, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift left: dest = src << shift + */ +#define I_LSHR(reg_dest, reg_src, reg_shift) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = reg_shift, \ + .unused1 = 0, \ + .sel = ALU_SEL_LSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift right: dest = src >> shift + */ +#define I_RSHR(reg_dest, reg_src, reg_shift) { .alu_reg = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .treg = reg_shift, \ + .unused1 = 0, \ + .sel = ALU_SEL_RSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_REG, \ + .opcode = OPCODE_ALU } } + +/** + * Add register and an immediate value: dest = src1 + imm + */ +#define I_ADDI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_ADD, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Subtract register and an immediate value: dest = src - imm + */ +#define I_SUBI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_SUB, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical AND register and an immediate value: dest = src & imm + */ +#define I_ANDI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_AND, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical OR register and an immediate value: dest = src | imm + */ +#define I_ORI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_OR, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Copy an immediate value into register: dest = imm + */ +#define I_MOVI(reg_dest, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = 0, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_MOV, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift left register value by an immediate: dest = src << imm + */ +#define I_LSHI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_LSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Logical shift right register value by an immediate: dest = val >> imm + */ +#define I_RSHI(reg_dest, reg_src, imm_) { .alu_imm = { \ + .dreg = reg_dest, \ + .sreg = reg_src, \ + .imm = imm_, \ + .unused1 = 0, \ + .sel = ALU_SEL_RSH, \ + .unused2 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_IMM, \ + .opcode = OPCODE_ALU } } + +/** + * Increment stage_cnt register by an immediate: stage_cnt = stage_cnt + imm + */ +#define I_STAGE_INC(reg_dest, reg_src, imm_) { .alu_cnt = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_STAGE_INC, \ + .unused3 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Decrement stage_cnt register by an immediate: stage_cnt = stage_cnt - imm + */ +#define I_STAGE_DEC(reg_dest, reg_src, imm_) { .alu_cnt = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_STAGE_DEC, \ + .unused3 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Reset stage_cnt register by an immediate: stage_cnt = 0 + */ +#define I_STAGE_RST(reg_dest, reg_src, imm_) { .alu_cnt = { \ + .unused1 = 0, \ + .imm = imm_, \ + .unused2 = 0, \ + .sel = ALU_SEL_STAGE_RST, \ + .unused3 = 0, \ + .sub_opcode = SUB_OPCODE_ALU_CNT, \ + .opcode = OPCODE_ALU } } + +/** + * Define a label with number label_num. + * + * This is a macro which doesn't generate a real instruction. + * The token generated by this macro is removed by ulp_process_macros_and_load + * function. Label defined using this macro can be used in branch macros defined + * below. + */ +#define M_LABEL(label_num) { .macro = { \ + .label = label_num, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_MACRO_LABEL, \ + .opcode = OPCODE_MACRO } } + +/** + * Token macro used by M_B and M_BX macros. Not to be used directly. + */ +#define M_BRANCH(label_num) { .macro = { \ + .label = label_num, \ + .unused = 0, \ + .sub_opcode = SUB_OPCODE_MACRO_BRANCH, \ + .opcode = OPCODE_MACRO } } + +/** + * Macro: branch to label label_num if R0 is less than immediate value. + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BL(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BL(0, imm_value) + +/** + * Macro: branch to label label_num if R0 is greater than immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BG(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BG(0, imm_value) + +/** + * Macro: branch to label label_num if R0 equal to the immediate value + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BE(label_num, imm_value) \ + M_BRANCH(label_num), \ + I_BE(0, imm_value) + +/** + * Macro: unconditional branch to label + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BX(label_num) \ + M_BRANCH(label_num), \ + I_BXI(0) + +/** + * Macro: branch to label if ALU result is zero + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BXZ(label_num) \ + M_BRANCH(label_num), \ + I_BXZI(0) + +/** + * Macro: branch to label if ALU overflow + * + * This macro generates two ulp_insn_t values separated by a comma, and should + * be used when defining contents of ulp_insn_t arrays. First value is not a + * real instruction; it is a token which is removed by ulp_process_macros_and_load + * function. + */ +#define M_BXF(label_num) \ + M_BRANCH(label_num), \ + I_BXFI(0) + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_fsm/include/ulp_fsm_common.h b/components/ulp/ulp_fsm/include/ulp_fsm_common.h new file mode 100644 index 0000000000..306eb59ea0 --- /dev/null +++ b/components/ulp/ulp_fsm/include/ulp_fsm_common.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +/* This file contains definitions that are common between esp32/ulp.h, + esp32s2/ulp.h and esp32s3/ulp.h +*/ + +#include "esp_intr_alloc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/**@{*/ +#define ESP_ERR_ULP_BASE 0x1200 /*!< Offset for ULP-related error codes */ +#define ESP_ERR_ULP_SIZE_TOO_BIG (ESP_ERR_ULP_BASE + 1) /*!< Program doesn't fit into RTC memory reserved for the ULP */ +#define ESP_ERR_ULP_INVALID_LOAD_ADDR (ESP_ERR_ULP_BASE + 2) /*!< Load address is outside of RTC memory reserved for the ULP */ +#define ESP_ERR_ULP_DUPLICATE_LABEL (ESP_ERR_ULP_BASE + 3) /*!< More than one label with the same number was defined */ +#define ESP_ERR_ULP_UNDEFINED_LABEL (ESP_ERR_ULP_BASE + 4) /*!< Branch instructions references an undefined label */ +#define ESP_ERR_ULP_BRANCH_OUT_OF_RANGE (ESP_ERR_ULP_BASE + 5) /*!< Branch target is out of range of B instruction (try replacing with BX) */ +/**@}*/ + +union ulp_insn; // Declared in the chip-specific ulp.h header + +typedef union ulp_insn ulp_insn_t; + +/** + * @brief Register ULP wakeup signal ISR + * + * @note The ISR routine will only be active if the main CPU is not in deepsleep + * + * @param fn ISR callback function + * @param arg ISR callback function arguments + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if callback function is NULL + * - ESP_ERR_NO_MEM if heap memory cannot be allocated for the interrupt + */ +esp_err_t ulp_isr_register(intr_handler_t fn, void *arg); + +/** + * @brief Deregister ULP wakeup signal ISR + * + * @param fn ISR callback function + * @param arg ISR callback function arguments + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if callback function is NULL + * - ESP_ERR_INVALID_STATE if a handler matching both callback function and its arguments isn't registered + */ +esp_err_t ulp_isr_deregister(intr_handler_t fn, void *arg); + +/** + * @brief Resolve all macro references in a program and load it into RTC memory + * @param load_addr address where the program should be loaded, expressed in 32-bit words + * @param program ulp_insn_t array with the program + * @param psize size of the program, expressed in 32-bit words + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if auxiliary temporary structure can not be allocated + * - one of ESP_ERR_ULP_xxx if program is not valid or can not be loaded + */ +esp_err_t ulp_process_macros_and_load(uint32_t load_addr, const ulp_insn_t* program, size_t* psize); + +/** + * @brief Load ULP program binary into RTC memory + * + * ULP program binary should have the following format (all values little-endian): + * + * 1. MAGIC, (value 0x00706c75, 4 bytes) + * 2. TEXT_OFFSET, offset of .text section from binary start (2 bytes) + * 3. TEXT_SIZE, size of .text section (2 bytes) + * 4. DATA_SIZE, size of .data section (2 bytes) + * 5. BSS_SIZE, size of .bss section (2 bytes) + * 6. (TEXT_OFFSET - 12) bytes of arbitrary data (will not be loaded into RTC memory) + * 7. .text section + * 8. .data section + * + * Linker script in components/ulp/ld/esp32.ulp.ld produces ELF files which + * correspond to this format. This linker script produces binaries with load_addr == 0. + * + * @param load_addr address where the program should be loaded, expressed in 32-bit words + * @param program_binary pointer to program binary + * @param program_size size of the program binary + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if load_addr is out of range + * - ESP_ERR_INVALID_SIZE if program_size doesn't match (TEXT_OFFSET + TEXT_SIZE + DATA_SIZE) + * - ESP_ERR_NOT_SUPPORTED if the magic number is incorrect + */ +esp_err_t ulp_load_binary(uint32_t load_addr, const uint8_t* program_binary, size_t program_size); + +/** + * @brief Run the program loaded into RTC memory + * @param entry_point entry point, expressed in 32-bit words + * @return ESP_OK on success + */ +esp_err_t ulp_run(uint32_t entry_point); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_fsm/ulp.c b/components/ulp/ulp_fsm/ulp.c new file mode 100644 index 0000000000..e03751b0eb --- /dev/null +++ b/components/ulp/ulp_fsm/ulp.c @@ -0,0 +1,153 @@ +/* + * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_private/esp_clk.h" +#if CONFIG_IDF_TARGET_ESP32 +#include "esp32/ulp.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/ulp.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/ulp.h" +#endif + +#include "soc/soc.h" +#include "soc/rtc.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" + +#include "ulp_common.h" +#include "esp_rom_sys.h" + +#include "esp_check.h" +#include "esp_private/rtc_ctrl.h" + +typedef struct { + uint32_t magic; + uint16_t text_offset; + uint16_t text_size; + uint16_t data_size; + uint16_t bss_size; +} ulp_binary_header_t; + +#define ULP_BINARY_MAGIC_ESP32 (0x00706c75) + +static const char* TAG = "ulp"; + +esp_err_t ulp_isr_register(intr_handler_t fn, void *arg) +{ + ESP_RETURN_ON_FALSE(fn, ESP_ERR_INVALID_ARG, TAG, "ULP ISR is NULL"); + REG_SET_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA_M); +#if CONFIG_IDF_TARGET_ESP32 + return rtc_isr_register(fn, arg, RTC_CNTL_SAR_INT_ST_M, 0); +#else + return rtc_isr_register(fn, arg, RTC_CNTL_ULP_CP_INT_ST_M, 0); +#endif /* CONFIG_IDF_TARGET_ESP32 */ +} + +esp_err_t ulp_isr_deregister(intr_handler_t fn, void *arg) +{ + ESP_RETURN_ON_FALSE(fn, ESP_ERR_INVALID_ARG, TAG, "ULP ISR is NULL"); + REG_CLR_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA_M); + return rtc_isr_deregister(fn, arg); +} + +esp_err_t ulp_run(uint32_t entry_point) +{ +#if CONFIG_IDF_TARGET_ESP32 + // disable ULP timer + CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); + // wait for at least 1 RTC_SLOW_CLK cycle + esp_rom_delay_us(10); + // set entry point + REG_SET_FIELD(SENS_SAR_START_FORCE_REG, SENS_PC_INIT, entry_point); + // disable force start + CLEAR_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_ULP_CP_FORCE_START_TOP_M); + // set time until wakeup is allowed to the smallest possible + REG_SET_FIELD(RTC_CNTL_TIMER5_REG, RTC_CNTL_MIN_SLP_VAL, RTC_CNTL_MIN_SLP_VAL_MIN); + // make sure voltage is raised when RTC 8MCLK is enabled + SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_I2C_FOLW_8M); + SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_CORE_FOLW_8M); + SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_SLEEP_FOLW_8M); + // enable ULP timer + SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); +#else + /* Reset COCPU when power on. */ + SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_CTRL_REG, RTC_CNTL_ULP_CP_RESET); + esp_rom_delay_us(20); + CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_CTRL_REG, RTC_CNTL_ULP_CP_RESET); + // disable ULP timer + CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); + // wait for at least 1 RTC_SLOW_CLK cycle + esp_rom_delay_us(10); + // set entry point + REG_SET_FIELD(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_PC_INIT, entry_point); + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SEL); // Select ULP_TIMER trigger target for ULP. + // start ULP clock gate. + SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_CTRL_REG, RTC_CNTL_ULP_CP_CLK_FO); + // ULP FSM sends the DONE signal. + CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE_FORCE); +#if CONFIG_IDF_TARGET_ESP32S3 + /* Set the CLKGATE_EN signal on esp32s3 */ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_CLKGATE_EN); +#endif + /* Clear interrupt COCPU status */ + REG_WRITE(RTC_CNTL_INT_CLR_REG, RTC_CNTL_COCPU_INT_CLR | RTC_CNTL_COCPU_TRAP_INT_CLR | RTC_CNTL_ULP_CP_INT_CLR); + // 1: start with timer. wait ULP_TIMER cnt timer. + CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_CTRL_REG, RTC_CNTL_ULP_CP_FORCE_START_TOP); // Select ULP_TIMER timer as COCPU trigger source + SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); // Software to turn on the ULP_TIMER timer +#endif + return ESP_OK; +} + +esp_err_t ulp_load_binary(uint32_t load_addr, const uint8_t* program_binary, size_t program_size) +{ + size_t program_size_bytes = program_size * sizeof(uint32_t); + size_t load_addr_bytes = load_addr * sizeof(uint32_t); + + if (program_size_bytes < sizeof(ulp_binary_header_t)) { + return ESP_ERR_INVALID_SIZE; + } + if (load_addr_bytes > CONFIG_ULP_COPROC_RESERVE_MEM) { + return ESP_ERR_INVALID_ARG; + } + if (load_addr_bytes + program_size_bytes > CONFIG_ULP_COPROC_RESERVE_MEM) { + return ESP_ERR_INVALID_SIZE; + } + + // Make a copy of a header in case program_binary isn't aligned + ulp_binary_header_t header; + memcpy(&header, program_binary, sizeof(header)); + + if (header.magic != ULP_BINARY_MAGIC_ESP32) { + return ESP_ERR_NOT_SUPPORTED; + } + + size_t total_size = (size_t) header.text_offset + (size_t) header.text_size + + (size_t) header.data_size; + + ESP_LOGD(TAG, "program_size_bytes: %d total_size: %d offset: %d .text: %d, .data: %d, .bss: %d", + program_size_bytes, total_size, header.text_offset, + header.text_size, header.data_size, header.bss_size); + + if (total_size != program_size_bytes) { + return ESP_ERR_INVALID_SIZE; + } + + size_t text_data_size = header.text_size + header.data_size; + uint8_t* base = (uint8_t*) RTC_SLOW_MEM; + + memcpy(base + load_addr_bytes, program_binary + header.text_offset, text_data_size); + memset(base + load_addr_bytes + text_data_size, 0, header.bss_size); + + return ESP_OK; +} diff --git a/components/ulp/ulp_fsm/ulp_macro.c b/components/ulp/ulp_fsm/ulp_macro.c new file mode 100644 index 0000000000..f7d436f7ab --- /dev/null +++ b/components/ulp/ulp_fsm/ulp_macro.c @@ -0,0 +1,289 @@ +/* + * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "ulp.h" +#include "ulp_common.h" + +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" + +#include "sdkconfig.h" + +static const char* TAG = "ulp"; + +typedef struct { + uint32_t label : 16; + uint32_t addr : 11; + uint32_t unused : 1; + uint32_t type : 4; +} reloc_info_t; + +#define RELOC_TYPE_LABEL 0 +#define RELOC_TYPE_BRANCH 1 +#define RELOC_TYPE_LABELPC 2 + +/* This record means: there is a label at address + * insn_addr, with number label_num. + */ +#define RELOC_INFO_LABEL(label_num, insn_addr) (reloc_info_t) { \ + .label = label_num, \ + .addr = insn_addr, \ + .unused = 0, \ + .type = RELOC_TYPE_LABEL } + +/* This record means: there is a branch instruction at + * insn_addr, it needs to be changed to point to address + * of label label_num. + */ +#define RELOC_INFO_BRANCH(label_num, insn_addr) (reloc_info_t) { \ + .label = label_num, \ + .addr = insn_addr, \ + .unused = 0, \ + .type = RELOC_TYPE_BRANCH } + +/* This record means: there is a move instruction at insn_addr, + * imm needs to be changed to the program counter of the instruction + * at label label_num. + */ +#define RELOC_INFO_LABELPC(label_num, insn_addr) (reloc_info_t) { \ + .label = label_num, \ + .addr = insn_addr, \ + .unused = 0, \ + .type = RELOC_TYPE_LABELPC } + +/* Comparison function used to sort the relocations array */ +static int reloc_sort_func(const void* p_lhs, const void* p_rhs) +{ + const reloc_info_t lhs = *(const reloc_info_t*) p_lhs; + const reloc_info_t rhs = *(const reloc_info_t*) p_rhs; + if (lhs.label < rhs.label) { + return -1; + } else if (lhs.label > rhs.label) { + return 1; + } + // label numbers are equal + if (lhs.type < rhs.type) { + return -1; + } else if (lhs.type > rhs.type) { + return 1; + } + + // both label number and type are equal + return 0; +} + +/* Processing branch and label macros involves four steps: + * + * 1. Iterate over program and count all instructions + * with "macro" opcode. Allocate relocations array + * with number of entries equal to number of macro + * instructions. + * + * 2. Remove all fake instructions with "macro" opcode + * and record their locations into relocations array. + * Removal is done using two pointers. Instructions + * are read from read_ptr, and written to write_ptr. + * When a macro instruction is encountered, + * its contents are recorded into the appropriate + * table, and then read_ptr is advanced again. + * When a real instruction is encountered, it is + * read via read_ptr and written to write_ptr. + * In the end, all macro instructions are removed, + * size of the program (expressed in words) is + * reduced by the total number of macro instructions + * which were present. + * + * 3. Sort relocations array by label number, and then + * by type ("label" or "branch") if label numbers + * match. This is done to simplify lookup on the next + * step. + * + * 4. Iterate over entries of relocations table. + * For each label number, label entry comes first + * because the array was sorted at the previous step. + * Label address is recorded, and all subsequent + * entries which point to the same label number + * are processed. For each entry, correct offset + * or absolute address is calculated, depending on + * type and subtype, and written into the appropriate + * field of the instruction. + * + */ + +static esp_err_t do_single_reloc(ulp_insn_t* program, uint32_t load_addr, + reloc_info_t label_info, reloc_info_t the_reloc) +{ + size_t insn_offset = the_reloc.addr - load_addr; + ulp_insn_t* insn = &program[insn_offset]; + + switch (the_reloc.type) { + case RELOC_TYPE_BRANCH: { + // B, BS and BX have the same layout of opcode/sub_opcode fields, + // and share the same opcode. B and BS also have the same layout of + // offset and sign fields. + assert(insn->b.opcode == OPCODE_BRANCH + && "branch macro was applied to a non-branch instruction"); + switch (insn->b.sub_opcode) { + case SUB_OPCODE_B: + case SUB_OPCODE_BS: { + int32_t offset = ((int32_t) label_info.addr) - ((int32_t) the_reloc.addr); + uint32_t abs_offset = abs(offset); + uint32_t sign = (offset >= 0) ? 0 : 1; + if (abs_offset > 127) { + ESP_LOGW(TAG, "target out of range: branch from %x to %x", + the_reloc.addr, label_info.addr); + return ESP_ERR_ULP_BRANCH_OUT_OF_RANGE; + } + insn->b.offset = abs_offset; //== insn->bs.offset = abs_offset; + insn->b.sign = sign; //== insn->bs.sign = sign; + break; + } + case SUB_OPCODE_BX: { + assert(insn->bx.reg == 0 && + "relocation applied to a jump with offset in register"); + insn->bx.addr = label_info.addr; + break; + } + default: + assert(false && "unexpected branch sub-opcode"); + } + break; + } + case RELOC_TYPE_LABELPC: { + assert((insn->alu_imm.opcode == OPCODE_ALU && insn->alu_imm.sub_opcode == SUB_OPCODE_ALU_IMM && insn->alu_imm.sel == ALU_SEL_MOV) + && "pc macro was applied to an incompatible instruction"); + insn->alu_imm.imm = label_info.addr; + break; + } + default: + assert(false && "unknown reloc type"); + } + return ESP_OK; +} + +esp_err_t ulp_process_macros_and_load(uint32_t load_addr, const ulp_insn_t* program, size_t* psize) +{ + const ulp_insn_t* read_ptr = program; + const ulp_insn_t* end = program + *psize; + size_t macro_count = 0; + // step 1: calculate number of macros + while (read_ptr < end) { + ulp_insn_t r_insn = *read_ptr; + if (r_insn.macro.opcode == OPCODE_MACRO) { + ++macro_count; + } + ++read_ptr; + } + size_t real_program_size = *psize - macro_count; + const size_t ulp_mem_end = CONFIG_ULP_COPROC_RESERVE_MEM / sizeof(ulp_insn_t); + if (load_addr > ulp_mem_end) { + ESP_LOGW(TAG, "invalid load address %"PRIx32", max is %x", + load_addr, ulp_mem_end); + return ESP_ERR_ULP_INVALID_LOAD_ADDR; + } + if (real_program_size + load_addr > ulp_mem_end) { + ESP_LOGE(TAG, "program too big: %d words, max is %d words", + real_program_size, ulp_mem_end); + return ESP_ERR_ULP_SIZE_TOO_BIG; + } + // If no macros found, copy the program and return. + if (macro_count == 0) { + memcpy(((ulp_insn_t*) RTC_SLOW_MEM) + load_addr, program, *psize * sizeof(ulp_insn_t)); + return ESP_OK; + } + reloc_info_t* reloc_info = + (reloc_info_t*) malloc(sizeof(reloc_info_t) * macro_count); + if (reloc_info == NULL) { + return ESP_ERR_NO_MEM; + } + + // step 2: record macros into reloc_info array + // and remove them from then program + read_ptr = program; + ulp_insn_t* output_program = ((ulp_insn_t*) RTC_SLOW_MEM) + load_addr; + ulp_insn_t* write_ptr = output_program; + uint32_t cur_insn_addr = load_addr; + reloc_info_t* cur_reloc = reloc_info; + while (read_ptr < end) { + ulp_insn_t r_insn = *read_ptr; + if (r_insn.macro.opcode == OPCODE_MACRO) { + switch (r_insn.macro.sub_opcode) { + case SUB_OPCODE_MACRO_LABEL: + *cur_reloc = RELOC_INFO_LABEL(r_insn.macro.label, + cur_insn_addr); + break; + case SUB_OPCODE_MACRO_BRANCH: + *cur_reloc = RELOC_INFO_BRANCH(r_insn.macro.label, + cur_insn_addr); + break; + case SUB_OPCODE_MACRO_LABELPC: + *cur_reloc = RELOC_INFO_LABELPC(r_insn.macro.label, + cur_insn_addr); + break; + default: + assert(0 && "invalid sub_opcode for macro insn"); + } + ++read_ptr; + assert(read_ptr != end && "program can not end with macro insn"); + ++cur_reloc; + } else { + // normal instruction (not a macro) + *write_ptr = *read_ptr; + ++read_ptr; + ++write_ptr; + ++cur_insn_addr; + } + } + + // step 3: sort relocations array + qsort(reloc_info, macro_count, sizeof(reloc_info_t), + reloc_sort_func); + + // step 4: walk relocations array and fix instructions + reloc_info_t* reloc_end = reloc_info + macro_count; + cur_reloc = reloc_info; + while (cur_reloc < reloc_end) { + reloc_info_t label_info = *cur_reloc; + assert(label_info.type == RELOC_TYPE_LABEL); + ++cur_reloc; + while (cur_reloc < reloc_end) { + if (cur_reloc->type == RELOC_TYPE_LABEL) { + if (cur_reloc->label == label_info.label) { + ESP_LOGE(TAG, "duplicate label definition: %d", + label_info.label); + free(reloc_info); + return ESP_ERR_ULP_DUPLICATE_LABEL; + } + break; + } + if (cur_reloc->label != label_info.label) { + ESP_LOGE(TAG, "branch to an inexistent label: %d", + cur_reloc->label); + free(reloc_info); + return ESP_ERR_ULP_UNDEFINED_LABEL; + } + esp_err_t rc = do_single_reloc(output_program, load_addr, + label_info, *cur_reloc); + if (rc != ESP_OK) { + free(reloc_info); + return rc; + } + ++cur_reloc; + } + } + free(reloc_info); + *psize = real_program_size; + return ESP_OK; +} diff --git a/components/ulp/ulp_riscv/include/esp32s2/ulp_riscv.h b/components/ulp/ulp_riscv/include/esp32s2/ulp_riscv.h new file mode 100644 index 0000000000..c312b6c698 --- /dev/null +++ b/components/ulp/ulp_riscv/include/esp32s2/ulp_riscv.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#warning Contents of esp32s2/ulp_riscv.h have been merged with ulp_riscv.h. Please include the later to avoid the warning. +#include "../ulp_riscv.h" diff --git a/components/ulp/ulp_riscv/include/ulp_riscv.h b/components/ulp/ulp_riscv/include/ulp_riscv.h new file mode 100644 index 0000000000..39836a58b6 --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv.h @@ -0,0 +1,142 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include +#include +#include "esp_err.h" +#include "ulp_common.h" +#include "esp_intr_alloc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ULP_RISCV_WAKEUP_SOURCE_TIMER, + ULP_RISCV_WAKEUP_SOURCE_GPIO, +} ulp_riscv_wakeup_source_t; + +/** + * @brief ULP riscv init parameters + * + */ +typedef struct { + ulp_riscv_wakeup_source_t wakeup_source; /*!< ULP wakeup source */ +} ulp_riscv_cfg_t; + +#define ULP_RISCV_DEFAULT_CONFIG() \ + { \ + .wakeup_source = ULP_RISCV_WAKEUP_SOURCE_TIMER, \ + } + +/* ULP RISC-V interrupt signals for the main CPU */ +#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) +#define ULP_RISCV_SW_INT (BIT(13)) // Corresponds to RTC_CNTL_COCPU_INT_ST_M interrupt status bit +#define ULP_RISCV_TRAP_INT (BIT(17)) // Corresponds to RTC_CNTL_COCPU_TRAP_INT_ST_M interrupt status bit +#else +#error "ULP_RISCV_SW_INT and ULP_RISCV_TRAP_INT are undefined. Please check soc/rtc_cntl_reg.h for the correct bitmap on your target SoC." +#endif /* (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) */ + +/** + * @brief Register ULP signal ISR + * + * @note The ISR routine will only be active if the main CPU is not in deepsleep + * + * @param fn ISR callback function + * @param arg ISR callback function arguments + * @param mask Bit mask to enable the required ULP RISC-V interrupt signals + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if callback function is NULL or if the interrupt bits are invalid + * - ESP_ERR_NO_MEM if heap memory cannot be allocated for the interrupt + * - other errors returned by esp_intr_alloc + */ +esp_err_t ulp_riscv_isr_register(intr_handler_t fn, void *arg, uint32_t mask); + +/** + * @brief Deregister ULP signal ISR + * + * @param fn ISR callback function + * @param arg ISR callback function arguments + * @param mask Bit mask to enable the required ULP RISC-V interrupt signals + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if callback function is NULL or if the interrupt bits are invalid + * - ESP_ERR_INVALID_STATE if a handler matching both callback function and its arguments isn't registered + */ +esp_err_t ulp_riscv_isr_deregister(intr_handler_t fn, void *arg, uint32_t mask); + +/** + * @brief Configure the ULP and run the program loaded into RTC memory + * + * @param cfg pointer to the config struct + * @return ESP_OK on success + */ +esp_err_t ulp_riscv_config_and_run(ulp_riscv_cfg_t* cfg); + +/** + * @brief Configure the ULP with default settings + * and run the program loaded into RTC memory + * + * @return ESP_OK on success + */ +esp_err_t ulp_riscv_run(void); + +/** + * @brief Load ULP-RISC-V program binary into RTC memory + * + * Different than ULP FSM, the binary program has no special format, it is the ELF + * file generated by RISC-V toolchain converted to binary format using objcopy. + * + * Linker script in components/ulp/ld/ulp_riscv.ld produces ELF files which + * correspond to this format. This linker script produces binaries with load_addr == 0. + * + * @param program_binary pointer to program binary + * @param program_size_bytes size of the program binary + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_SIZE if program_size_bytes is more than 8KiB + */ +esp_err_t ulp_riscv_load_binary(const uint8_t* program_binary, size_t program_size_bytes); + +/** + * @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); + +/** + * @brief Halts the program currently running on the ULP-RISC-V + * + * @note Program will restart at the next ULP timer trigger if timer is still running. + * If you want to stop the ULP from waking up then call ulp_riscv_timer_stop() first. + */ +void ulp_riscv_halt(void); + +/** + * @brief Resets the ULP-RISC-V core from the main CPU + * + * @note This will reset the ULP core from the main CPU. It is intended to be used when the + * ULP is in a bad state and cannot run as intended due to a corrupt firmware or any other reason. + * The main core can reset the ULP core with this API and then re-initilialize the ULP. + */ +void ulp_riscv_reset(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv.h b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv.h new file mode 100644 index 0000000000..d6da5cb199 --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#warning Contents of ulp_riscv/ulp_riscv.h have been deprecated. Please include the header which contains the actual definitions you are trying to use, e.g. "ulp_riscv_register_ops.h". +#include "../../ulp_core/include/ulp_riscv_register_ops.h" diff --git a/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_gpio.h b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_gpio.h new file mode 100644 index 0000000000..9f7b1b5c86 --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_gpio.h @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#warning "ulp_riscv_gpio.h has been moved. Please include the file without the ulp_riscv prefix." +#include "../../ulp_core/include/ulp_riscv_gpio.h" diff --git a/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_register_ops.h b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_register_ops.h new file mode 100644 index 0000000000..fe07242542 --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_register_ops.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#warning ulp_riscv_register_ops.h has been moved. Please include the file without the ulp_riscv prefix. +#include "../../ulp_core/include/ulp_riscv_register_ops.h" diff --git a/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_utils.h b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_utils.h new file mode 100644 index 0000000000..ce8c6b3c3c --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_utils.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#warning ulp_riscv_utils.h has been moved. Please include the file without the ulp_riscv prefix. +#include "../../ulp_core/include/ulp_riscv_utils.h" diff --git a/components/ulp/ulp_riscv/include/ulp_riscv_adc.h b/components/ulp/ulp_riscv/include/ulp_riscv_adc.h new file mode 100644 index 0000000000..098b7af513 --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv_adc.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "hal/adc_types.h" +#include "esp_err.h" + +#include "ulp_adc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Kept for backwards compatibilty */ +#define ulp_riscv_adc_cfg_t ulp_adc_cfg_t +#define ulp_riscv_adc_init ulp_adc_init + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/include/ulp_riscv_i2c.h b/components/ulp/ulp_riscv/include/ulp_riscv_i2c.h new file mode 100644 index 0000000000..503fa7b20c --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv_i2c.h @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "hal/gpio_types.h" +#include "esp_err.h" + +/** + * @brief ULP RISC-V RTC I2C pin config + */ +typedef struct { + uint32_t sda_io_num; /*!< GPIO pin for SDA signal. Only GPIO#1 or GPIO#3 can be used as the SDA pin. */ + uint32_t scl_io_num; /*!< GPIO pin for SCL signal. Only GPIO#0 or GPIO#2 can be used as the SCL pin. */ + bool sda_pullup_en; /*!< SDA line enable internal pullup. Can be configured if external pullup is not used. */ + bool scl_pullup_en; /*!< SCL line enable internal pullup. Can be configured if external pullup is not used. */ +} ulp_riscv_i2c_pin_cfg_t; + +/** + * @brief ULP RISC-V RTC I2C timing config + */ +typedef struct { + float scl_low_period; /*!< SCL low period in micro seconds */ + float scl_high_period; /*!< SCL high period in micro seconds */ + float sda_duty_period; /*!< Period between the SDA switch and the falling edge of SCL in micro seconds */ + float scl_start_period; /*!< Waiting time after the START condition in micro seconds */ + float scl_stop_period; /*!< Waiting time before the END condition in micro seconds */ + float i2c_trans_timeout; /*!< I2C transaction timeout in micro seconds */ +} ulp_riscv_i2c_timing_cfg_t; + +/** + * @brief ULP RISC-V RTC I2C init parameters + */ +typedef struct { + ulp_riscv_i2c_pin_cfg_t i2c_pin_cfg; /*!< RTC I2C pin configuration */ + ulp_riscv_i2c_timing_cfg_t i2c_timing_cfg; /*!< RTC I2C timing configuration */ +} ulp_riscv_i2c_cfg_t; + +/* Default RTC I2C GPIO settings */ +#define ULP_RISCV_I2C_DEFAULT_GPIO_CONFIG() \ + .i2c_pin_cfg.sda_io_num = GPIO_NUM_3, \ + .i2c_pin_cfg.scl_io_num = GPIO_NUM_2, \ + .i2c_pin_cfg.sda_pullup_en = true, \ + .i2c_pin_cfg.scl_pullup_en = true, \ + +#if CONFIG_IDF_TARGET_ESP32S3 +/* Nominal I2C bus timing parameters for I2C fast mode. Max SCL freq of 400 KHz. */ +#define ULP_RISCV_I2C_FAST_MODE_CONFIG() \ + .i2c_timing_cfg.scl_low_period = 1.4, \ + .i2c_timing_cfg.scl_high_period = 0.3, \ + .i2c_timing_cfg.sda_duty_period = 1, \ + .i2c_timing_cfg.scl_start_period = 2, \ + .i2c_timing_cfg.scl_stop_period = 1.3, \ + .i2c_timing_cfg.i2c_trans_timeout = 20, +#elif CONFIG_IDF_TARGET_ESP32S2 +/* Nominal I2C bus timing parameters for I2C fast mode. Max SCL freq on S2 is about 233 KHz due to timing constraints. */ +#define ULP_RISCV_I2C_FAST_MODE_CONFIG() \ + .i2c_timing_cfg.scl_low_period = 2, \ + .i2c_timing_cfg.scl_high_period = 0.7, \ + .i2c_timing_cfg.sda_duty_period = 1.7, \ + .i2c_timing_cfg.scl_start_period = 2.4, \ + .i2c_timing_cfg.scl_stop_period = 1.3, \ + .i2c_timing_cfg.i2c_trans_timeout = 20, +#endif + +/* Nominal I2C bus timing parameters for I2C standard mode. Max SCL freq of 100 KHz. */ +#define ULP_RISCV_I2C_STANDARD_MODE_CONFIG() \ + .i2c_timing_cfg.scl_low_period = 5, \ + .i2c_timing_cfg.scl_high_period = 5, \ + .i2c_timing_cfg.sda_duty_period = 2, \ + .i2c_timing_cfg.scl_start_period = 3, \ + .i2c_timing_cfg.scl_stop_period = 6, \ + .i2c_timing_cfg.i2c_trans_timeout = 20, \ + +/* Default RTC I2C configuration settings. Uses I2C fast mode. */ +//TODO: Move to smaller units of time in the future like nano seconds to avoid floating point operations. +#define ULP_RISCV_I2C_DEFAULT_CONFIG() \ + { \ + ULP_RISCV_I2C_DEFAULT_GPIO_CONFIG() \ + ULP_RISCV_I2C_FAST_MODE_CONFIG() \ + } + +/** + * @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 + * + * @note The RTC I2C peripheral always expects a slave sub register address to be programmed. If it is not, the I2C + * peripheral uses the SENS_SAR_I2C_CTRL_REG[18:11] as the sub register address for the subsequent read or write + * operation. + * + * @param slave_reg_addr I2C slave sub 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 + */ +void 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 + */ +void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size); + +/** + * @brief Initialize and configure the RTC I2C for use by ULP RISC-V + * Currently RTC I2C can only be used in master mode + * + * @param cfg Configuration parameters + * @return esp_err_t ESP_OK when successful + */ +esp_err_t ulp_riscv_i2c_master_init(const ulp_riscv_i2c_cfg_t *cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/include/ulp_riscv_lock.h b/components/ulp/ulp_riscv/include/ulp_riscv_lock.h new file mode 100644 index 0000000000..597e3ed463 --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv_lock.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ulp_riscv_lock_shared.h" + +/** + * @brief Locks are based on the Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm + * + */ + +/** + * @brief Acquire the lock, preventing the ULP from taking until released. Spins until lock is acquired. + * + * @note The lock is only designed for being used by a single thread on the main CPU, + * it is not safe to try to acquire it from multiple threads. + * + * @param lock Pointer to lock struct, shared with ULP + */ +void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock); + +/** + * @brief Release the lock + * + * @param lock Pointer to lock struct, shared with ULP + */ +void ulp_riscv_lock_release(ulp_riscv_lock_t *lock); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h b/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h new file mode 100644 index 0000000000..67c12dac8c --- /dev/null +++ b/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enum representing which processor is allowed to enter the critical section + * + */ +typedef enum { + ULP_RISCV_LOCK_TURN_ULP, /*!< ULP's turn to enter the critical section */ + ULP_RISCV_LOCK_TURN_MAIN_CPU, /*!< Main CPU's turn to enter the critical section */ +} ulp_riscv_lock_turn_t; + +/** + * @brief Structure representing a lock shared between ULP and main CPU + * + */ +typedef struct { + volatile bool critical_section_flag_ulp; /*!< ULP wants to enter the critical sections */ + volatile bool critical_section_flag_main_cpu; /*!< Main CPU wants to enter the critical sections */ + volatile ulp_riscv_lock_turn_t turn; /*!< Which CPU is allowed to enter the critical section */ +} ulp_riscv_lock_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_adc_ulp_core.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_adc_ulp_core.h new file mode 100644 index 0000000000..5d189109f6 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_adc_ulp_core.h @@ -0,0 +1,33 @@ +/* + * 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 initilized for ULP by main CPU by calling ulp_riscv_adc_init() + * before calling this. + * + * @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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_gpio.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_gpio.h new file mode 100644 index 0000000000..d48e5fc817 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_gpio.h @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sdkconfig.h" +#include "soc/rtc_io_reg.h" +#include "soc/sens_reg.h" +#include "hal/gpio_types.h" +#include "ulp_riscv_register_ops.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 CONFIG_IDF_TARGET_ESP32S2 + SET_PERI_REG_MASK(SENS_SAR_IO_MUX_CONF_REG, SENS_IOMUX_CLK_GATE_EN_M); +#elif CONFIG_IDF_TARGET_ESP32S3 + SET_PERI_REG_MASK(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_IOMUX_CLK_EN_M); +#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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h new file mode 100644 index 0000000000..41383245ea --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @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 + */ +void 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 + */ +void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt.h new file mode 100644 index 0000000000..0bdba291d5 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt_ops.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt_ops.h new file mode 100644 index 0000000000..f902507995 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt_ops.h @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Claire Xenia Wolf + * 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 renables 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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_lock_ulp_core.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_lock_ulp_core.h new file mode 100644 index 0000000000..acfd24588b --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_lock_ulp_core.h @@ -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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_print.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_print.h new file mode 100644 index 0000000000..ac9e8931f6 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_print.h @@ -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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_register_ops.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_register_ops.h new file mode 100644 index 0000000000..6bff84e0ef --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_register_ops.h @@ -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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_touch_ulp_core.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_touch_ulp_core.h new file mode 100644 index 0000000000..043c7d5072 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_touch_ulp_core.h @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023 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_sensor_legacy_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(touch_pad_t 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(touch_pad_t 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(touch_pad_t 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 basaline of all channels) + * @return esp_err_t ESP_OK when successful + */ +esp_err_t ulp_riscv_touch_pad_reset_benchmark(touch_pad_t 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(touch_pad_t 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(touch_pad_t 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(touch_pad_t 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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_uart_ulp_core.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_uart_ulp_core.h new file mode 100644 index 0000000000..53dcde55c2 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_uart_ulp_core.h @@ -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 diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_utils.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_utils.h new file mode 100644 index 0000000000..acc47a28bf --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_utils.h @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sdkconfig.h" +#include +#include "ulp_riscv_register_ops.h" +#include "ulp_riscv_interrupt.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; }) + +#if CONFIG_IDF_TARGET_ESP32S2 +/* These are only approximate default numbers, the default frequency + of the 8M oscillator is 8.5MHz +/- 5%, at the default DCAP setting +*/ +#define ULP_RISCV_CYCLES_PER_US 8.5 +#elif CONFIG_IDF_TARGET_ESP32S3 +#define ULP_RISCV_CYCLES_PER_US 17.5 +#endif +#define ULP_RISCV_CYCLES_PER_MS ULP_RISCV_CYCLES_PER_US*1000 + +/** + * @brief Makes the co-processor busy wait for a certain number of cycles + * + * @param cycles Number of cycles to busy wait + */ +void static inline ulp_riscv_delay_cycles(uint32_t cycles) +{ + uint32_t start = ULP_RISCV_GET_CCOUNT(); + /* Off with an estimate of cycles in this function to improve accuracy */ + uint32_t end = start + cycles - 20; + + while (ULP_RISCV_GET_CCOUNT() < end) { + /* Wait */ + } +} + +/** + * @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 diff --git a/components/ulp/ulp_riscv/ulp_core/start.S b/components/ulp/ulp_riscv/ulp_core/start.S new file mode 100644 index 0000000000..e1f0b123e1 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/start.S @@ -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 diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c new file mode 100644 index 0000000000..10905529b7 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c @@ -0,0 +1,30 @@ +/* + * 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); + 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; +} diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_gpio.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_gpio.c new file mode 100644 index 0000000000..ae0792c0d5 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_gpio.c @@ -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 */ diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c new file mode 100644 index 0000000000..75c39faf46 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c @@ -0,0 +1,284 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#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 /*! -1) { + /* If the ticks_to_wait value is not -1, keep track of ticks and + * break from the loop once the timeout is reached. + */ + ulp_riscv_delay_cycles(1); + to++; + if (to >= ticks_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 | | | + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------| + */ +void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size) +{ + uint32_t i = 0; + uint32_t cmd_idx = 0; + + if (size == 0) { + // Quietly return + return; + } + + // 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)); + 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(); +} + +/* + * 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 | | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + */ +void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size) +{ + uint32_t i = 0; + uint32_t cmd_idx = 0; + + if (size == 0) { + // Quietly return + return; + } + + // 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)); + 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(); +} diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c new file mode 100644 index 0000000000..18b5a418d3 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#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 */ diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c new file mode 100644 index 0000000000..d6f3b95dbd --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c @@ -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; +} diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c new file mode 100644 index 0000000000..6b3d55fa13 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c @@ -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(); +} diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_touch.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_touch.c new file mode 100644 index 0000000000..8ced246058 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_touch.c @@ -0,0 +1,125 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ulp_riscv_touch_ulp_core.h" +#include "soc/soc_caps.h" +#include "soc/touch_sensor_pins.h" +#include "hal/touch_sensor_hal.h" + +/* Check Touch Channel correctness */ +#define ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(channel) \ +{ \ + if (channel >= SOC_TOUCH_SENSOR_NUM || \ + channel < 0 || \ + channel == SOC_TOUCH_DENOISE_CHANNEL) { \ + return ESP_ERR_INVALID_ARG; \ + } \ +} \ + +esp_err_t ulp_riscv_touch_pad_read_raw_data(touch_pad_t 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 */ + *raw_data = touch_hal_read_raw_data(touch_num); + + return ESP_OK; +} + +esp_err_t ulp_riscv_touch_pad_read_benchmark(touch_pad_t 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_hal_read_benchmark(touch_num, benchmark); + + return ESP_OK; +} + +esp_err_t ulp_riscv_touch_pad_filter_read_smooth(touch_pad_t 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_hal_filter_read_smooth(touch_num, smooth_data); + + return ESP_OK; +} + +esp_err_t ulp_riscv_touch_pad_reset_benchmark(touch_pad_t touch_num) +{ + /* Check Arguments */ + if (touch_num > TOUCH_PAD_MAX || touch_num < 0) { + return ESP_ERR_INVALID_ARG; + } + + /* Reset benchmark */ + touch_hal_reset_benchmark(touch_num); + + return ESP_OK; +} + +esp_err_t ulp_riscv_touch_pad_sleep_channel_read_data(touch_pad_t 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_hal_sleep_read_data(raw_data); + + return ESP_OK; +} + +esp_err_t ulp_riscv_touch_pad_sleep_channel_read_benchmark(touch_pad_t 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_hal_sleep_read_benchmark(benchmark); + + return ESP_OK; +} + +esp_err_t ulp_riscv_touch_pad_sleep_channel_read_smooth(touch_pad_t 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_hal_sleep_read_smooth(smooth_data); + + return ESP_OK; +} + +esp_err_t ulp_riscv_touch_pad_sleep_channel_reset_benchmark(void) +{ + /* Reset benchmark */ + touch_hal_sleep_reset_benchmark(); + + return ESP_OK; +} diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c new file mode 100644 index 0000000000..b9d127f66e --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c @@ -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 * ((1000*1000) / 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); +} diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c new file mode 100644 index 0000000000..8fce93363a --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2015-2024 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" + +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 */ diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S new file mode 100644 index 0000000000..9b95e51c22 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S @@ -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 */ diff --git a/components/ulp/ulp_riscv/ulp_riscv.c b/components/ulp/ulp_riscv/ulp_riscv.c new file mode 100644 index 0000000000..d958c4bc0b --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_riscv.c @@ -0,0 +1,217 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_private/esp_clk.h" +#include "ulp_riscv.h" +#include "soc/soc.h" +#include "soc/rtc.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" +#include "hal/misc.h" +#include "ulp_common.h" +#include "esp_rom_sys.h" +#include "esp_check.h" +#include "esp_private/rtc_ctrl.h" + +__attribute__((unused)) static const char* TAG = "ulp-riscv"; + +esp_err_t ulp_riscv_isr_register(intr_handler_t fn, void *arg, uint32_t mask) +{ + /* Verify that the ISR callback is valid */ + ESP_RETURN_ON_FALSE(fn, ESP_ERR_INVALID_ARG, TAG, "ULP RISC-V ISR is NULL"); + + /* Verify that the interrupt bits are valid */ + if (!(mask & (RTC_CNTL_COCPU_INT_ST_M | RTC_CNTL_COCPU_TRAP_INT_ST_M))) { + ESP_LOGE(TAG, "Invalid bitmask for ULP RISC-V interrupts"); + return ESP_ERR_INVALID_ARG; + } + + /* Make sure we enable only the ULP interrupt bits. + * We don't want other RTC interrupts triggering this ISR. + */ + mask &= (RTC_CNTL_COCPU_INT_ST_M | RTC_CNTL_COCPU_TRAP_INT_ST_M); + + /* Register the RTC ISR */ + ESP_RETURN_ON_ERROR(rtc_isr_register(fn, arg, mask, 0), TAG, "rtc_isr_register() failed"); + + /* Enable the interrupt bits */ + SET_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, mask); + + return ESP_OK; +} + +esp_err_t ulp_riscv_isr_deregister(intr_handler_t fn, void *arg, uint32_t mask) +{ + /* Verify that the ISR callback is valid */ + ESP_RETURN_ON_FALSE(fn, ESP_ERR_INVALID_ARG, TAG, "ULP RISC-V ISR is NULL"); + + /* Verify that the interrupt bits are valid */ + if (!(mask & (RTC_CNTL_COCPU_INT_ST_M | RTC_CNTL_COCPU_TRAP_INT_ST_M))) { + ESP_LOGE(TAG, "Invalid bitmask for ULP RISC-V interrupts"); + return ESP_ERR_INVALID_ARG; + } + + /* Make sure we disable only the ULP interrupt bits */ + mask &= (RTC_CNTL_COCPU_INT_ST_M | RTC_CNTL_COCPU_TRAP_INT_ST_M); + + /* Disable the interrupt bits */ + CLEAR_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, mask); + + /* Deregister the RTC ISR */ + ESP_RETURN_ON_ERROR(rtc_isr_deregister(fn, arg), TAG, "rtc_isr_deregister() failed"); + + return ESP_OK; +} + +static esp_err_t ulp_riscv_config_wakeup_source(ulp_riscv_wakeup_source_t wakeup_source) +{ + esp_err_t ret = ESP_OK; + + switch (wakeup_source) { + case ULP_RISCV_WAKEUP_SOURCE_TIMER: + /* start ULP_TIMER */ + CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_CTRL_REG, RTC_CNTL_ULP_CP_FORCE_START_TOP); + SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); + break; + + case ULP_RISCV_WAKEUP_SOURCE_GPIO: + SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_GPIO_WAKEUP_ENA); + break; + + default: + ret = ESP_ERR_INVALID_ARG; + } + + return ret; +} + +esp_err_t ulp_riscv_config_and_run(ulp_riscv_cfg_t* cfg) +{ + esp_err_t ret = ESP_OK; + +#if CONFIG_IDF_TARGET_ESP32S2 + /* Reset COCPU when power on. */ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN); + + /* The coprocessor cpu trap signal doesnt have a stable reset value, + force ULP-RISC-V clock on to stop RTC_COCPU_TRAP_TRIG_EN from waking the CPU*/ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_CLK_FO); + + /* Disable ULP timer */ + CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); + /* wait for at least 1 RTC_SLOW_CLK cycle */ + esp_rom_delay_us(20); + /* Select RISC-V as the ULP_TIMER trigger target. */ + CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SEL); + + /* Select ULP-RISC-V to send the DONE signal. */ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE_FORCE); + + ret = ulp_riscv_config_wakeup_source(cfg->wakeup_source); + +#elif CONFIG_IDF_TARGET_ESP32S3 + /* Reset COCPU when power on. */ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN); + + /* The coprocessor cpu trap signal doesnt have a stable reset value, + force ULP-RISC-V clock on to stop RTC_COCPU_TRAP_TRIG_EN from waking the CPU*/ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_CLK_FO); + + /* Disable ULP timer */ + CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); + /* wait for at least 1 RTC_SLOW_CLK cycle */ + esp_rom_delay_us(20); + + /* We do not select RISC-V as the Coprocessor here as this could lead to a hang + * in the main CPU. Instead, we reset RTC_CNTL_COCPU_SEL after we have enabled the ULP timer. + * + * IDF-4510 + */ + //CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SEL); + + /* Select ULP-RISC-V to send the DONE signal */ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE_FORCE); + + /* Set the CLKGATE_EN signal */ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_CLKGATE_EN); + + ret = ulp_riscv_config_wakeup_source(cfg->wakeup_source); + + /* Select RISC-V as the ULP_TIMER trigger target + * Selecting the RISC-V as the Coprocessor at the end is a workaround + * for the hang issue recorded in IDF-4510. + */ + CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SEL); + + /* Clear any spurious wakeup trigger interrupts upon ULP startup */ + esp_rom_delay_us(20); + REG_WRITE(RTC_CNTL_INT_CLR_REG, RTC_CNTL_COCPU_INT_CLR | RTC_CNTL_COCPU_TRAP_INT_CLR | RTC_CNTL_ULP_CP_INT_CLR); + +#endif + + return ret; +} + +esp_err_t ulp_riscv_run(void) +{ + ulp_riscv_cfg_t cfg = ULP_RISCV_DEFAULT_CONFIG(); + return ulp_riscv_config_and_run(&cfg); +} + +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_halt(void) +{ + ulp_riscv_timer_stop(); + + /* suspends the ulp operation*/ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE); + + /* Resets the processor */ + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN); +} + +void ulp_riscv_reset() +{ + CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT | RTC_CNTL_COCPU_DONE); + CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN); + esp_rom_delay_us(20); + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT | RTC_CNTL_COCPU_DONE); + SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN); +} + +esp_err_t ulp_riscv_load_binary(const uint8_t* program_binary, size_t program_size_bytes) +{ + if (program_binary == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (program_size_bytes > CONFIG_ULP_COPROC_RESERVE_MEM) { + return ESP_ERR_INVALID_SIZE; + } + + uint8_t* base = (uint8_t*) RTC_SLOW_MEM; + + //Start by clearing memory reserved with zeros, this will also will initialize the bss: + hal_memset(base, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + hal_memcpy(base, program_binary, program_size_bytes); + + return ESP_OK; +} diff --git a/components/ulp/ulp_riscv/ulp_riscv_i2c.c b/components/ulp/ulp_riscv/ulp_riscv_i2c.c new file mode 100644 index 0000000000..138d479642 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_riscv_i2c.c @@ -0,0 +1,543 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ulp_riscv_i2c.h" +#include "esp_check.h" +#include "soc/rtc_i2c_reg.h" +#include "soc/rtc_i2c_struct.h" +#include "soc/rtc_io_struct.h" +#include "soc/sens_reg.h" +#include "soc/clk_tree_defs.h" +#include "hal/i2c_ll.h" +#include "hal/misc.h" +#include "driver/rtc_io.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +static const char *RTCI2C_TAG = "ulp_riscv_i2c"; + +#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_pin_cfg.sda_io_num; + gpio_num_t scl_io_num = cfg->i2c_pin_cfg.scl_io_num; + bool sda_pullup_en = cfg->i2c_pin_cfg.sda_pullup_en; + bool scl_pullup_en = cfg->i2c_pin_cfg.scl_pullup_en; + + /* Verify that the I2C GPIOs are valid */ + ESP_RETURN_ON_ERROR(i2c_gpio_is_cfg_valid(sda_io_num, scl_io_num), RTCI2C_TAG, "RTC I2C GPIO config invalid"); + + // NOTE: We always initialize the SCL pin first, then the SDA pin. + // This order of initialization is important to avoid any spurious + // I2C start conditions on the bus. + + /* Initialize SCL Pin */ + ESP_RETURN_ON_ERROR(i2c_configure_io(scl_io_num, scl_pullup_en), RTCI2C_TAG, "RTC I2C SCL pin config failed"); + + /* Initialize SDA Pin */ + ESP_RETURN_ON_ERROR(i2c_configure_io(sda_io_num, sda_pullup_en), RTCI2C_TAG, "RTC I2C SDA pin config failed"); + + /* Route SDA IO signal to the RTC subsystem */ + rtc_io_dev->touch_pad[sda_io_num].mux_sel = 1; + + /* Route SCL IO signal to the RTC subsystem */ + rtc_io_dev->touch_pad[scl_io_num].mux_sel = 1; + + /* Select RTC I2C function for SDA pin */ + rtc_io_dev->touch_pad[sda_io_num].fun_sel = 3; + + /* Select RTC I2C function for SCL pin */ + rtc_io_dev->touch_pad[scl_io_num].fun_sel = 3; + + /* Map the SDA and SCL signals to the RTC I2C controller */ + if (sda_io_num == GPIO_NUM_1) { + rtc_io_dev->sar_i2c_io.sda_sel = 0; + } else { + rtc_io_dev->sar_i2c_io.sda_sel = 1; + } + + if (scl_io_num == GPIO_NUM_0) { + rtc_io_dev->sar_i2c_io.scl_sel = 0; + } else { + rtc_io_dev->sar_i2c_io.scl_sel = 1; + } + + return ESP_OK; +} + +static esp_err_t i2c_set_timing(const ulp_riscv_i2c_cfg_t *cfg) +{ + /* Convert all timing parameters from micro-seconds to period in RTC_FAST_CLK cycles. + * RTC_FAST_CLK = 8.5 MHz for esp32s2 and 17.5 MHz for esp32s3. + * The following calculations approximate the period for each parameter. + */ + float scl_low_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_low_period); + float scl_high_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_high_period); + float sda_duty_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.sda_duty_period); + float scl_start_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_start_period); + float scl_stop_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_stop_period); + float i2c_trans_timeout = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.i2c_trans_timeout); + float setup_time_start = (cfg->i2c_timing_cfg.scl_high_period + cfg->i2c_timing_cfg.sda_duty_period); + float hold_time_start = (cfg->i2c_timing_cfg.scl_start_period - cfg->i2c_timing_cfg.sda_duty_period); + float setup_time_data = (cfg->i2c_timing_cfg.scl_low_period - cfg->i2c_timing_cfg.sda_duty_period); + + /* Verify timing constraints */ + ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.scl_low_period >= 1.3f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low period cannot be less than 1.3 micro seconds"); + // TODO: As per specs, SCL high period must be greater than 0.6 micro seconds but after tests it is found that we can have a the period as 0.3 micro seconds to + // achieve performance close to I2C fast mode. Therefore, this criteria is relaxed. + ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.scl_high_period >= 0.3f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL high period cannot be less than 0.3 micro seconds"); + ESP_RETURN_ON_FALSE(setup_time_start >= 0.6f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds"); + ESP_RETURN_ON_FALSE(hold_time_start >= 0.6f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be less than 0.6 micro seconds"); + ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.scl_stop_period >= 0.6f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds"); + ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.sda_duty_period <= 3.45f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be greater than 3.45 micro seconds"); + ESP_RETURN_ON_FALSE((setup_time_data * 1000) >= 250, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data setup time cannot be less than 250 nano seconds"); + + /* Verify filtering constrains + * + * I2C may have glitches on the transition edge, so the edge will be filtered in the design, + * which will also affect the value of the timing parameter register. + * Therefore, the following filtering constraints must be followed: + */ + ESP_RETURN_ON_FALSE(scl_stop_period > scl_high_period, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL Stop period cannot be greater than SCL high period"); + ESP_RETURN_ON_FALSE(sda_duty_period < scl_low_period, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SDA duty period cannot be less than the SCL low period"); + ESP_RETURN_ON_FALSE(scl_start_period > 8, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL start period must be greater than 8 RTC_FAST_CLK cycles"); + ESP_RETURN_ON_FALSE((scl_low_period + scl_high_period - sda_duty_period) > 8, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low + SCL high - SDA duty must be greater than 8 RTC_FAST_CLK cycles"); + + /* Verify SDA duty num constraints */ + ESP_RETURN_ON_FALSE(sda_duty_period > 14, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SDA duty period must be greater than 14 RTC_FAST_CLK cycles"); + + /* Set the RTC I2C timing parameters */ +#if CONFIG_IDF_TARGET_ESP32S2 + i2c_dev->scl_low.val = scl_low_period; // SCL low period + i2c_dev->scl_high.val = scl_high_period; // SCL high period + i2c_dev->sda_duty.val = sda_duty_period; // SDA duty cycle + i2c_dev->scl_start_period.val = scl_start_period; // Wait time after START condition + i2c_dev->scl_stop_period.val = scl_stop_period; // Wait time before END condition + i2c_dev->timeout.val = i2c_trans_timeout; // I2C transaction timeout +#elif CONFIG_IDF_TARGET_ESP32S3 + i2c_dev->i2c_scl_low.val = scl_low_period; // SCL low period + i2c_dev->i2c_scl_high.val = scl_high_period; // SCL high period + i2c_dev->i2c_sda_duty.val = sda_duty_period; // SDA duty cycle + i2c_dev->i2c_scl_start_period.val = scl_start_period; // Wait time after START condition + i2c_dev->i2c_scl_stop_period.val = scl_stop_period; // Wait time before END condition + i2c_dev->i2c_to.val = i2c_trans_timeout; // I2C transaction timeout +#endif // CONFIG_IDF_TARGET_ESP32S2 + + return ESP_OK; +} + +/* + * 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) +{ +#if CONFIG_IDF_TARGET_ESP32S2 + /* Reset cmd register */ + i2c_dev->command[cmd_idx].val = 0; + + /* Write new command to cmd register */ + i2c_dev->command[cmd_idx].done = 0; // CMD Done + i2c_dev->command[cmd_idx].op_code = op_code; // Opcode + i2c_dev->command[cmd_idx].ack_val = ack_val; // ACK bit sent by I2C controller during READ. + // Ignored during RSTART, STOP, END and WRITE cmds. + i2c_dev->command[cmd_idx].ack_exp = ack_expected; // ACK bit expected by I2C controller during WRITE. + // Ignored during RSTART, STOP, END and READ cmds. + i2c_dev->command[cmd_idx].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. + HAL_FORCE_MODIFY_U32_REG_FIELD(i2c_dev->command[cmd_idx], byte_num, byte_num); // Byte Num + +#elif CONFIG_IDF_TARGET_ESP32S3 + /* Reset cmd register */ + i2c_dev->i2c_cmd[cmd_idx].val = 0; + + /* Write new command to cmd register */ + i2c_dev->i2c_cmd[cmd_idx].i2c_command_done = 0; // CMD Done + i2c_dev->i2c_cmd[cmd_idx].i2c_op_code = op_code; // Opcode + i2c_dev->i2c_cmd[cmd_idx].i2c_ack_val = ack_val; // ACK bit sent by I2C controller during READ. + // Ignored during RSTART, STOP, END and WRITE cmds. + i2c_dev->i2c_cmd[cmd_idx].i2c_ack_exp = ack_expected; // ACK bit expected by I2C controller during WRITE. + // Ignored during RSTART, STOP, END and READ cmds. + i2c_dev->i2c_cmd[cmd_idx].i2c_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. + HAL_FORCE_MODIFY_U32_REG_FIELD(i2c_dev->i2c_cmd[cmd_idx], i2c_byte_num, byte_num); // Byte Num +#endif // CONFIG_IDF_TARGET_ESP32S2 +} + +static inline esp_err_t ulp_riscv_i2c_wait_for_interrupt(int32_t ticks_to_wait) +{ + uint32_t status = 0; + uint32_t to = 0; + esp_err_t ret = ESP_OK; + + while (1) { + status = READ_PERI_REG(RTC_I2C_INT_ST_REG); + + /* Return ESP_OK if Tx or Rx data interrupt bits are set. */ + if ((status & RTC_I2C_TX_DATA_INT_ST) || + (status & RTC_I2C_RX_DATA_INT_ST)) { + ret = ESP_OK; + break; + /* In case of error status, break and return ESP_FAIL */ +#if CONFIG_IDF_TARGET_ESP32S2 + } else if ((status & RTC_I2C_TIMEOUT_INT_ST) || +#elif CONFIG_IDF_TARGET_ESP32S3 + } else 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)) { + ret = ESP_FAIL; + break; + } + + if (ticks_to_wait > -1) { + /* If the ticks_to_wait value is not -1, keep track of ticks and + * break from the loop once the timeout is reached. + */ + vTaskDelay(1); + to++; + if (to >= ticks_to_wait) { + ret = ESP_ERR_TIMEOUT; + break; + } + } + } + + return ret; +} + +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 | | | + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------| + */ +void 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; + } + + /* 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); + + /* 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); + + portENTER_CRITICAL(&rtc_i2c_lock); + + for (i = 0; i < size; i++) { + /* Poll for RTC I2C Rx Data interrupt bit to be set */ + ret = ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT); + + if (ret == ESP_OK) { + /* 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 { + ESP_EARLY_LOGE(RTCI2C_TAG, "ulp_riscv_i2c: Read Failed!"); + uint32_t status = READ_PERI_REG(RTC_I2C_INT_RAW_REG); + ESP_EARLY_LOGE(RTCI2C_TAG, "ulp_riscv_i2c: RTC I2C Interrupt Raw Reg 0x%"PRIx32"", status); + ESP_EARLY_LOGE(RTCI2C_TAG, "ulp_riscv_i2c: RTC I2C Status Reg 0x%"PRIx32"", READ_PERI_REG(RTC_I2C_STATUS_REG)); + SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, status); + break; + } + } + + portEXIT_CRITICAL(&rtc_i2c_lock); + + /* 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); +} + +/* + * 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 | | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + */ +void ulp_riscv_i2c_master_write_to_device(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; + } + + /* 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); + + portENTER_CRITICAL(&rtc_i2c_lock); + + 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 */ + ret = ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT); + + if (ret == ESP_OK) { + /* Clear the Tx data interrupt bit */ + SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR); + } else { + ESP_EARLY_LOGE(RTCI2C_TAG, "ulp_riscv_i2c: Write Failed!"); + uint32_t status = READ_PERI_REG(RTC_I2C_INT_RAW_REG); + ESP_EARLY_LOGE(RTCI2C_TAG, "ulp_riscv_i2c: RTC I2C Interrupt Raw Reg 0x%"PRIx32"", status); + ESP_EARLY_LOGE(RTCI2C_TAG, "ulp_riscv_i2c: RTC I2C Status Reg 0x%"PRIx32"", READ_PERI_REG(RTC_I2C_STATUS_REG)); + SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, status); + break; + } + } + + portEXIT_CRITICAL(&rtc_i2c_lock); + + /* 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); +} + +esp_err_t ulp_riscv_i2c_master_init(const ulp_riscv_i2c_cfg_t *cfg) +{ + /* Clear any stale config registers */ + WRITE_PERI_REG(RTC_I2C_CTRL_REG, 0); + WRITE_PERI_REG(SENS_SAR_I2C_CTRL_REG, 0); + + /* Verify that the input cfg param is valid */ + ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "RTC I2C configuration is NULL"); + + /* Configure RTC I2C GPIOs */ + ESP_RETURN_ON_ERROR(i2c_set_pin(cfg), RTCI2C_TAG, "Failed to configure RTC I2C GPIOs"); + + /* Reset RTC I2C */ +#if CONFIG_IDF_TARGET_ESP32S2 + i2c_dev->ctrl.i2c_reset = 1; + esp_rom_delay_us(20); + i2c_dev->ctrl.i2c_reset = 0; +#elif CONFIG_IDF_TARGET_ESP32S3 + SET_PERI_REG_MASK(SENS_SAR_PERI_RESET_CONF_REG, SENS_RTC_I2C_RESET); + i2c_dev->i2c_ctrl.i2c_i2c_reset = 1; + esp_rom_delay_us(20); + i2c_dev->i2c_ctrl.i2c_i2c_reset = 0; + CLEAR_PERI_REG_MASK(SENS_SAR_PERI_RESET_CONF_REG, SENS_RTC_I2C_RESET); +#endif // CONFIG_IDF_TARGET_ESP32S2 + + /* Enable internal open-drain mode for SDA and SCL lines */ +#if CONFIG_IDF_TARGET_ESP32S2 + i2c_dev->ctrl.sda_force_out = 0; + i2c_dev->ctrl.scl_force_out = 0; +#elif CONFIG_IDF_TARGET_ESP32S3 + i2c_dev->i2c_ctrl.i2c_sda_force_out = 0; + i2c_dev->i2c_ctrl.i2c_scl_force_out = 0; +#endif // CONFIG_IDF_TARGET_ESP32S2 + +#if CONFIG_IDF_TARGET_ESP32S2 + /* Configure the RTC I2C controller in master mode */ + i2c_dev->ctrl.ms_mode = 1; + + /* Enable RTC I2C Clock gate */ + i2c_dev->ctrl.i2c_ctrl_clk_gate_en = 1; +#elif CONFIG_IDF_TARGET_ESP32S3 + /* For esp32s3, we need to enable the rtc_i2c clock gate before accessing rtc i2c registers */ + SET_PERI_REG_MASK(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_RTC_I2C_CLK_EN); + + /* Configure the RTC I2C controller in master mode */ + i2c_dev->i2c_ctrl.i2c_ms_mode = 1; + + /* Enable RTC I2C Clock gate */ + i2c_dev->i2c_ctrl.i2c_i2c_ctrl_clk_gate_en = 1; +#endif // CONFIG_IDF_TARGET_ESP32S2 + + /* Configure RTC I2C timing parameters */ + ESP_RETURN_ON_ERROR(i2c_set_timing(cfg), RTCI2C_TAG, "Failed to configure RTC I2C timing"); + + /* Clear any pending interrupts */ + WRITE_PERI_REG(RTC_I2C_INT_CLR_REG, UINT32_MAX); + + /* Enable RTC I2C interrupts */ + SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA | + RTC_I2C_TX_DATA_INT_ENA | + RTC_I2C_ARBITRATION_LOST_INT_ENA | + RTC_I2C_ACK_ERR_INT_ENA | +#if CONFIG_IDF_TARGET_ESP32S2 + RTC_I2C_TIMEOUT_INT_ENA); +#elif CONFIG_IDF_TARGET_ESP32S3 + RTC_I2C_TIME_OUT_INT_ENA); +#endif // CONFIG_IDF_TARGET_ESP32S2 + + return ESP_OK; +} diff --git a/components/ulp/ulp_riscv/ulp_riscv_lock.c b/components/ulp/ulp_riscv/ulp_riscv_lock.c new file mode 100644 index 0000000000..cab59a8d0e --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_riscv_lock.c @@ -0,0 +1,27 @@ +/* + * 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" + +#include + +void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock) +{ + assert(lock); + + lock->critical_section_flag_main_cpu = true; + lock->turn = ULP_RISCV_LOCK_TURN_ULP; + + while (lock->critical_section_flag_ulp && (lock->turn == ULP_RISCV_LOCK_TURN_ULP)) { + } +} + +void ulp_riscv_lock_release(ulp_riscv_lock_t *lock) +{ + assert(lock); + + lock->critical_section_flag_main_cpu = false; +} diff --git a/zephyr/esp32c6/CMakeLists.txt b/zephyr/esp32c6/CMakeLists.txt index 7183a491bb..f5b61f6bb4 100644 --- a/zephyr/esp32c6/CMakeLists.txt +++ b/zephyr/esp32c6/CMakeLists.txt @@ -105,6 +105,10 @@ if(CONFIG_SOC_SERIES_ESP32C6) ../../components/mbedtls/port/include ../port/include/boot + ../../components/ulp/lp_core/include + ../../components/ulp/lp_core/lp_core/include + ../../components/ulp/lp_core/shared/include + ../../components/ulp/ulp_common/include ) zephyr_link_libraries( @@ -208,7 +212,8 @@ if(CONFIG_SOC_SERIES_ESP32C6) ) endif() - zephyr_sources( + zephyr_sources_ifdef( + CONFIG_SOC_ESP32C6_HPCORE ../../components/soc/${CONFIG_SOC_SERIES}/gpio_periph.c ../../components/soc/${CONFIG_SOC_SERIES}/rtc_io_periph.c @@ -300,6 +305,7 @@ if(CONFIG_SOC_SERIES_ESP32C6) CONFIG_UART_ESP32 ../../components/hal/uart_hal.c ../../components/hal/uart_hal_iram.c + ../../components/soc/${CONFIG_SOC_SERIES}/uart_periph.c ) zephyr_sources_ifdef( @@ -334,6 +340,42 @@ if(CONFIG_SOC_SERIES_ESP32C6) ) endif() + if(CONFIG_SOC_ESP32C6_LPCORE) + zephyr_compile_definitions(IS_ULP_COCPU) + zephyr_ld_options("-nostartfiles") + zephyr_ld_options("-Wl,--no-warn-rwx-segments") + zephyr_ld_options("-Wl,--gc-sections") + endif() + + if(CONFIG_ULP_COPROC_ENABLED) + zephyr_sources_ifdef( + CONFIG_SOC_ESP32C6_HPCORE + ../../components/ulp/lp_core/lp_core.c + ../../components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c + ../../components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c + ../../components/hal/rtc_io_hal.c + ) + + zephyr_sources_ifdef( + CONFIG_SOC_ESP32C6_LPCORE + ../../components/ulp/lp_core/lp_core/lp_core_interrupt.c + ../../components/ulp/lp_core/lp_core/lp_core_utils.c + ../../components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c + ../../components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c + ../../components/hal/uart_hal_iram.c + # ../../components/hal/uart_hal.c + # ../../components/soc/${CONFIG_SOC_SERIES}/rtc_io_periph.c + # ../../components/hal/rtc_io_hal.c + # ../../components/hal/${CONFIG_SOC_SERIES}/clk_tree_hal.c + # ../../components/esp_hw_support/port/esp_clk_tree_common.c + # ../../components/esp_hw_support/port/${CONFIG_SOC_SERIES}/rtc_time.c + # ../../components/esp_hw_support/port/${CONFIG_SOC_SERIES}/rtc_clk.c + # ../../components/hal/efuse_hal.c + # ../../components/hal/${CONFIG_SOC_SERIES}/efuse_hal.c + + ) + endif() + ## shared WIFI/BT resources if (CONFIG_BT OR CONFIG_WIFI_ESP32) zephyr_sources( diff --git a/zephyr/port/boot/esp_image_loader.c b/zephyr/port/boot/esp_image_loader.c index e5d81abcb4..aec209cb74 100644 --- a/zephyr/port/boot/esp_image_loader.c +++ b/zephyr/port/boot/esp_image_loader.c @@ -112,16 +112,18 @@ void esp_app_image_load(int image_index, int slot, } #if SOC_RTC_FAST_MEM_SUPPORTED - if (!esp_ptr_in_rtc_iram_fast((void *)load_header.lp_rtc_iram_dest_addr) || - !esp_ptr_in_rtc_iram_fast((void *)(load_header.lp_rtc_iram_dest_addr + load_header.lp_rtc_iram_size))) { + if ((load_header.lp_rtc_iram_size) && + (!esp_ptr_in_rtc_iram_fast((void *)load_header.lp_rtc_iram_dest_addr) || + !esp_ptr_in_rtc_iram_fast((void *)(load_header.lp_rtc_iram_dest_addr + load_header.lp_rtc_iram_size)))) { BOOT_LOG_ERR("%s_IRAM region in load header is not valid. Aborting", LP_RTC_PREFIX); FIH_PANIC; } #endif #if SOC_RTC_SLOW_MEM_SUPPORTED - if (!esp_ptr_in_rtc_slow((void *)load_header.lp_rtc_dram_dest_addr) || - !esp_ptr_in_rtc_slow((void *)(load_header.lp_rtc_dram_dest_addr + load_header.lp_rtc_dram_size))) { + if ((load_header.lp_rtc_dram_size) && + (!esp_ptr_in_rtc_slow((void *)load_header.lp_rtc_dram_dest_addr) || + !esp_ptr_in_rtc_slow((void *)(load_header.lp_rtc_dram_dest_addr + load_header.lp_rtc_dram_size)))) { BOOT_LOG_ERR("%s_RAM region in load header is not valid. Aborting %p", LP_RTC_PREFIX, load_header.lp_rtc_dram_dest_addr); FIH_PANIC; }