diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index a69dd3ed09..0dad5798a1 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -2,8 +2,11 @@ idf_build_get_property(target IDF_TARGET) set(srcs "gpio.c" + "gptimer.c" + "timer_legacy.c" "i2c.c" "ledc.c" + "legacy_new_driver_coexist.c" "periph_ctrl.c" "rtc_io.c" "rtc_module.c" @@ -14,7 +17,6 @@ set(srcs "spi_master.c" "spi_slave.c" "spi_bus_lock.c" - "timer.c" "uart.c") set(includes "include" "${target}/include" "deprecated") diff --git a/components/driver/Kconfig b/components/driver/Kconfig index 432a0e5cd7..9d132f9c00 100644 --- a/components/driver/Kconfig +++ b/components/driver/Kconfig @@ -188,4 +188,30 @@ menu "Driver configurations" (e.g. SPI Flash write). endmenu # GDMA Configuration + menu "GPTimer Configuration" + config GPTIMER_CTRL_FUNC_IN_IRAM + bool "Place GPTimer control functions into IRAM" + default n + help + Place GPTimer control functions (like start/stop) into IRAM, + so that these functions can be IRAM-safe and able to be called in the other IRAM interrupt context. + Enabling this option can improve driver performance as well. + + config GPTIMER_ISR_IRAM_SAFE + bool "GPTimer ISR IRAM-Safe" + default n + help + This will ensure the GPTimer interrupt handle is IRAM-Safe, allow to avoid flash + cache misses, and also be able to run whilst the cache is disabled. + (e.g. SPI Flash write) + + config GPTIMER_SUPPRESS_DEPRECATE_WARN + bool "Suppress leagcy driver deprecated warning" + default n + help + Wether to suppress the deprecation warnings when using legacy timer group driver (driver/timer.h). + If you want to continue using the legacy driver, and don't want to see related deprecation warnings, + you can enable this option. + endmenu # GPTimer Configuration + endmenu # Driver configurations diff --git a/components/driver/include/driver/timer.h b/components/driver/deprecated/driver/timer.h similarity index 77% rename from components/driver/include/driver/timer.h rename to components/driver/deprecated/driver/timer.h index 61cdcd2c50..e035d16522 100644 --- a/components/driver/include/driver/timer.h +++ b/components/driver/deprecated/driver/timer.h @@ -7,147 +7,19 @@ #pragma once #include +#include +#include "sdkconfig.h" #include "esp_err.h" -#include "esp_attr.h" -#include "soc/soc_caps.h" -#include "esp_intr_alloc.h" -#include "hal/timer_types.h" +#include "driver/timer_types_legacy.h" + +#if !CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN +#warning "legacy timer group driver is deprecated, please migrate to driver/gptimer.h" +#endif #ifdef __cplusplus extern "C" { #endif -/** - * @brief Frequency of the clock on the input of the timer groups - * @note This macro is not correct for Timer Groups with multiple clock sources (e.g. APB, XTAL) - * So please don't use it in your application, we keep it here only for backward compatible - */ -#define TIMER_BASE_CLK (APB_CLK_FREQ) - -/** - * @brief Selects a Timer-Group out of 2 available groups - */ -typedef enum { - TIMER_GROUP_0 = 0, /*!< Hw timer group 0 */ -#if SOC_TIMER_GROUPS > 1 - TIMER_GROUP_1 = 1, /*!< Hw timer group 1 */ -#endif - TIMER_GROUP_MAX /*!< Maximum number of Hw timer groups */ -} timer_group_t; - -/** - * @brief Select a hardware timer from timer groups - */ -typedef enum { - TIMER_0 = 0, /*! 1 - TIMER_INTR_T1 = 1 << 1, /*!< interrupt of timer 1 */ - TIMER_INTR_WDT = 1 << 2, /*!< interrupt of watchdog */ -#else - TIMER_INTR_WDT = 1 << 1, /*!< interrupt of watchdog */ -#endif - TIMER_INTR_NONE = 0 -} timer_intr_t; -FLAG_ATTR(timer_intr_t) - -/** - * @brief Decides the direction of counter - */ -typedef enum { - TIMER_COUNT_DOWN = GPTIMER_COUNT_DOWN, /*!< Descending Count from cnt.high|cnt.low*/ - TIMER_COUNT_UP = GPTIMER_COUNT_UP, /*!< Ascending Count from Zero*/ - TIMER_COUNT_MAX /*!< Maximum number of timer count directions */ -} timer_count_dir_t; - -/** - * @brief Decides whether timer is on or paused - */ -typedef enum { - TIMER_PAUSE, /*! 1 + TIMER_GROUP_1 = 1, /*!< Hw timer group 1 */ +#endif + TIMER_GROUP_MAX /*!< Maximum number of Hw timer groups */ +} timer_group_t; + +/** + * @brief Timer ID + */ +typedef enum { + TIMER_0 = 0, /*!< Select timer0 of GROUPx*/ +#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1 + TIMER_1 = 1, /*!< Select timer1 of GROUPx*/ +#endif + TIMER_MAX, +} timer_idx_t; + +/** + * @brief Interrupt types of the timer. + */ +typedef enum { + TIMER_INTR_T0 = 1 << 0, /*!< interrupt of timer 0 */ +#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1 + TIMER_INTR_T1 = 1 << 1, /*!< interrupt of timer 1 */ + TIMER_INTR_WDT = 1 << 2, /*!< interrupt of watchdog */ +#else + TIMER_INTR_WDT = 1 << 1, /*!< interrupt of watchdog */ +#endif + TIMER_INTR_NONE = 0 +} timer_intr_t; +FLAG_ATTR(timer_intr_t) + +/** + * @brief Timer count direction + */ +typedef enum { + TIMER_COUNT_DOWN = GPTIMER_COUNT_DOWN, /*!< Descending Count from cnt.high|cnt.low*/ + TIMER_COUNT_UP = GPTIMER_COUNT_UP, /*!< Ascending Count from Zero*/ + TIMER_COUNT_MAX /*!< Maximum number of timer count directions */ +} timer_count_dir_t; + +/** + * @brief Timer start/stop command + */ +typedef enum { + TIMER_PAUSE, /*!< Pause timer counter*/ + TIMER_START, /*!< Start timer counter*/ +} timer_start_t; + +/** + * @brief Timer alarm command + */ +typedef enum { + TIMER_ALARM_DIS = 0, /*!< Disable timer alarm*/ + TIMER_ALARM_EN = 1, /*!< Enable timer alarm*/ + TIMER_ALARM_MAX +} timer_alarm_t; + +/** + * @brief Timer interrupt type + */ +typedef enum { + TIMER_INTR_LEVEL = 0, /*!< Interrupt mode: level mode*/ + TIMER_INTR_MAX +} timer_intr_mode_t; + +/** + * @brief Timer autoreload command + */ +typedef enum { + TIMER_AUTORELOAD_DIS = 0, /*!< Disable auto-reload: hardware will not load counter value after an alarm event*/ + TIMER_AUTORELOAD_EN = 1, /*!< Enable auto-reload: hardware will load counter value after an alarm event*/ + TIMER_AUTORELOAD_MAX, +} timer_autoreload_t; + +/** + * @brief Timer group clock source + */ +typedef enum { + TIMER_SRC_CLK_APB = GPTIMER_CLK_SRC_APB, /*!< Select APB as the source clock*/ +#if SOC_TIMER_GROUP_SUPPORT_XTAL + TIMER_SRC_CLK_XTAL = GPTIMER_CLK_SRC_XTAL, /*!< Select XTAL as the source clock*/ +#endif +} timer_src_clk_t; + +/** + * @brief Interrupt handler callback function + * + * @return + * - True Do task yield at the end of ISR + * - False Not do task yield at the end of ISR + * + * @note If you called FreeRTOS functions in callback, you need to return true or false based on + * the retrun value of argument `pxHigherPriorityTaskWoken`. + * For example, `xQueueSendFromISR` is called in callback, if the return value `pxHigherPriorityTaskWoken` + * of any FreeRTOS calls is pdTRUE, return true; otherwise return false. + */ +typedef bool (*timer_isr_t)(void *); + +/** + * @brief Interrupt handle, used in order to free the isr after use. + */ +typedef intr_handle_t timer_isr_handle_t; + +/** + * @brief Timer configurations + */ +typedef struct { + timer_alarm_t alarm_en; /*!< Timer alarm enable */ + timer_start_t counter_en; /*!< Counter enable */ + timer_intr_mode_t intr_type; /*!< Interrupt mode */ + timer_count_dir_t counter_dir; /*!< Counter direction */ + timer_autoreload_t auto_reload; /*!< Timer auto-reload */ + timer_src_clk_t clk_src; /*!< Selects source clock. */ + uint32_t divider; /*!< Counter clock divider */ +} timer_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/gptimer.c b/components/driver/gptimer.c new file mode 100644 index 0000000000..52535ec9a4 --- /dev/null +++ b/components/driver/gptimer.c @@ -0,0 +1,530 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG // uncomment this line to enable debug logs + +#include +#include +#include "freertos/FreeRTOS.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_intr_alloc.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_pm.h" +#include "driver/gptimer.h" +#include "hal/timer_types.h" +#include "hal/timer_hal.h" +#include "hal/timer_ll.h" +#include "soc/timer_periph.h" +#include "soc/soc_memory_types.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/esp_clk.h" + +// If ISR handler is allowed to run whilst cache is disabled, +// Make sure all the code and related variables used by the handler are in the SRAM +#if CONFIG_GPTIMER_ISR_IRAM_SAFE +#define GPTIMER_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) +#define GPTIMER_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define GPTIMER_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED +#define GPTIMER_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif //CONFIG_GPTIMER_ISR_IRAM_SAFE + +#if CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM +#define GPTIMER_CTRL_FUNC_ATTR IRAM_ATTR +#else +#define GPTIMER_CTRL_FUNC_ATTR +#endif // CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM + +#define GPTIMER_PM_LOCK_NAME_LEN_MAX 16 + +static const char *TAG = "gptimer"; + +typedef struct gptimer_platform_t gptimer_platform_t; +typedef struct gptimer_group_t gptimer_group_t; +typedef struct gptimer_t gptimer_t; + +struct gptimer_platform_t { + _lock_t mutex; // platform level mutex lock + gptimer_group_t *groups[SOC_TIMER_GROUPS]; // timer group pool + int group_ref_counts[SOC_TIMER_GROUPS]; // reference count used to protect group install/uninstall +}; + +struct gptimer_group_t { + int group_id; + portMUX_TYPE spinlock; // to protect per-group register level concurrent access + gptimer_t *timers[SOC_TIMER_GROUP_TIMERS_PER_GROUP]; +}; + +typedef enum { + GPTIMER_FSM_STOP, + GPTIMER_FSM_START, +} gptimer_lifecycle_fsm_t; + +struct gptimer_t { + gptimer_group_t *group; + int timer_id; + unsigned int resolution_hz; + unsigned long long reload_count; + unsigned long long alarm_count; + gptimer_count_direction_t direction; + timer_hal_context_t hal; + gptimer_lifecycle_fsm_t fsm; // access to fsm should be protect by spinlock, as fsm is also accessed from ISR handler + intr_handle_t intr; + _lock_t mutex; // to protect other resource allocation, like interrupt handle + portMUX_TYPE spinlock; // to protect per-timer resources concurent accessed by task and ISR handler + gptimer_alarm_cb_t on_alarm; + void *user_ctx; + esp_pm_lock_handle_t pm_lock; // power management lock +#if CONFIG_PM_ENABLE + char pm_lock_name[GPTIMER_PM_LOCK_NAME_LEN_MAX]; // pm lock name +#endif + struct { + uint32_t intr_shared: 1; + uint32_t auto_reload_on_alarm: 1; + uint32_t alarm_en: 1; + } flags; +}; + +// gptimer driver platform, it's always a singleton +static gptimer_platform_t s_platform; + +static gptimer_group_t *gptimer_acquire_group_handle(int group_id); +static void gptimer_release_group_handle(gptimer_group_t *group); +static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz); +static esp_err_t gptimer_install_interrupt(gptimer_t *timer); +IRAM_ATTR static void gptimer_default_isr(void *args); + +esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer) +{ + esp_err_t ret = ESP_OK; + gptimer_group_t *group = NULL; + gptimer_t *timer = NULL; + int group_id = -1; + int timer_id = -1; + ESP_GOTO_ON_FALSE(config && ret_timer, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(config->resolution_hz, ESP_ERR_INVALID_ARG, err, TAG, "invalid timer resolution:%d", config->resolution_hz); + + timer = heap_caps_calloc(1, sizeof(gptimer_t), GPTIMER_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(timer, ESP_ERR_NO_MEM, err, TAG, "no mem for gptimer"); + + for (int i = 0; (i < SOC_TIMER_GROUPS) && (timer_id < 0); i++) { + group = gptimer_acquire_group_handle(i); + ESP_GOTO_ON_FALSE(group, ESP_ERR_NO_MEM, err, TAG, "no mem for group (%d)", group_id); + // loop to search free timer in the group + portENTER_CRITICAL(&group->spinlock); + for (int j = 0; j < SOC_TIMER_GROUP_TIMERS_PER_GROUP; j++) { + if (!group->timers[j]) { + group_id = i; + timer_id = j; + group->timers[j] = timer; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (timer_id < 0) { + gptimer_release_group_handle(group); + group = NULL; + } + } + + ESP_GOTO_ON_FALSE(timer_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free timer"); + timer->timer_id = timer_id; + timer->group = group; + // initialize HAL layer + timer_hal_init(&timer->hal, group_id, timer_id); + // stop counter, alarm, auto-reload + timer_ll_enable_counter(timer->hal.dev, timer_id, false); + timer_ll_enable_auto_reload(timer->hal.dev, timer_id, false); + timer_ll_enable_alarm(timer->hal.dev, timer_id, false); + // select clock source, set clock resolution + ESP_GOTO_ON_ERROR(gptimer_select_periph_clock(timer, config->clk_src, config->resolution_hz), err, TAG, "set periph clock failed"); + // initialize counter value to zero + timer_hal_set_counter_value(&timer->hal, 0); + // set counting direction + timer_ll_set_count_direction(timer->hal.dev, timer_id, config->direction); + + // interrupt register is shared by all timers in the same group + portENTER_CRITICAL(&group->spinlock); + timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer_id), false); // disable interrupt + timer_ll_clear_intr_status(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer_id)); // clear pending interrupt event + portEXIT_CRITICAL(&group->spinlock); + // initialize other members of timer + timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + timer->fsm = GPTIMER_FSM_STOP; + timer->direction = config->direction; + timer->flags.intr_shared = config->flags.intr_shared; + _lock_init(&timer->mutex); + ESP_LOGD(TAG, "new gptimer (%d,%d) at %p, resolution=%uHz", group_id, timer_id, timer, timer->resolution_hz); + *ret_timer = timer; + return ESP_OK; + +err: + if (timer) { + if (timer->pm_lock) { + esp_pm_lock_delete(timer->pm_lock); + } + free(timer); + } + if (group) { + gptimer_release_group_handle(group); + } + return ret; +} + +esp_err_t gptimer_del_timer(gptimer_handle_t timer) +{ + gptimer_group_t *group = NULL; + bool valid_state = true; + ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + portENTER_CRITICAL(&timer->spinlock); + if (timer->fsm != GPTIMER_FSM_STOP) { + valid_state = false; + } + portEXIT_CRITICAL(&timer->spinlock); + ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "can't delete timer as it's not in stop state"); + group = timer->group; + int group_id = group->group_id; + int timer_id = timer->timer_id; + + if (timer->intr) { + esp_intr_free(timer->intr); + ESP_LOGD(TAG, "uninstall interrupt service for timer (%d,%d)", group_id, timer_id); + } + if (timer->pm_lock) { + esp_pm_lock_delete(timer->pm_lock); + ESP_LOGD(TAG, "uninstall APB_FREQ_MAX lock for timer (%d,%d)", group_id, timer_id); + } + _lock_close(&timer->mutex); + free(timer); + ESP_LOGD(TAG, "del timer (%d,%d)", group_id, timer_id); + + portENTER_CRITICAL(&group->spinlock); + group->timers[timer_id] = NULL; + portEXIT_CRITICAL(&group->spinlock); + // timer has a reference on group, release it now + gptimer_release_group_handle(group); + + return ESP_OK; +} + +GPTIMER_CTRL_FUNC_ATTR +esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value) +{ + ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + portENTER_CRITICAL_SAFE(&timer->spinlock); + timer_hal_set_counter_value(&timer->hal, value); + portEXIT_CRITICAL_SAFE(&timer->spinlock); + return ESP_OK; +} + +GPTIMER_CTRL_FUNC_ATTR +esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, unsigned long long *value) +{ + ESP_RETURN_ON_FALSE(timer && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + portENTER_CRITICAL_SAFE(&timer->spinlock); + *value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id); + portEXIT_CRITICAL_SAFE(&timer->spinlock); + return ESP_OK; +} + +esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data) +{ + gptimer_group_t *group = NULL; + ESP_RETURN_ON_FALSE(timer && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + group = timer->group; + +#if CONFIG_GPTIMER_ISR_IRAM_SAFE + if (cbs->on_alarm) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_alarm), ESP_ERR_INVALID_ARG, TAG, "on_alarm callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_in_dram(user_data) || + esp_ptr_in_diram_dram(user_data) || + esp_ptr_in_rtc_dram_fast(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in DRAM"); + } +#endif + + // lazy install interrupt service + ESP_RETURN_ON_ERROR(gptimer_install_interrupt(timer), TAG, "install interrupt service failed"); + + // enable/disable GPTimer interrupt events + portENTER_CRITICAL_SAFE(&group->spinlock); + timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id), cbs->on_alarm); // enable timer interrupt + portEXIT_CRITICAL_SAFE(&group->spinlock); + + timer->on_alarm = cbs->on_alarm; + timer->user_ctx = user_data; + return ESP_OK; +} + +GPTIMER_CTRL_FUNC_ATTR +esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config) +{ + ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + if (config) { + // When auto_reload is enabled, alarm_count should not be equal to reload_count + bool valid_auto_reload = !config->flags.auto_reload_on_alarm || config->alarm_count != config->reload_count; + ESP_RETURN_ON_FALSE(valid_auto_reload, ESP_ERR_INVALID_ARG, TAG, "reload count can't equal to alarm count"); + + timer->reload_count = config->reload_count; + timer->alarm_count = config->alarm_count; + timer->flags.auto_reload_on_alarm = config->flags.auto_reload_on_alarm; + timer->flags.alarm_en = true; + + portENTER_CRITICAL_SAFE(&timer->spinlock); + timer_ll_set_reload_value(timer->hal.dev, timer->timer_id, config->reload_count); + timer_ll_set_alarm_value(timer->hal.dev, timer->timer_id, config->alarm_count); + portEXIT_CRITICAL_SAFE(&timer->spinlock); + } else { + timer->flags.auto_reload_on_alarm = false; + timer->flags.alarm_en = false; + } + + portENTER_CRITICAL_SAFE(&timer->spinlock); + timer_ll_enable_auto_reload(timer->hal.dev, timer->timer_id, timer->flags.auto_reload_on_alarm); + timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en); + portEXIT_CRITICAL_SAFE(&timer->spinlock); + return ESP_OK; +} + +GPTIMER_CTRL_FUNC_ATTR +esp_err_t gptimer_start(gptimer_handle_t timer) +{ + ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + // acquire power manager lock + if (timer->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(timer->pm_lock), TAG, "acquire APB_FREQ_MAX lock failed"); + } + // interrupt interupt service + if (timer->intr) { + ESP_RETURN_ON_ERROR(esp_intr_enable(timer->intr), TAG, "enable interrupt service failed"); + } + + portENTER_CRITICAL_SAFE(&timer->spinlock); + timer_ll_enable_counter(timer->hal.dev, timer->timer_id, true); + timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en); + timer->fsm = GPTIMER_FSM_START; + portEXIT_CRITICAL_SAFE(&timer->spinlock); + + return ESP_OK; +} + +GPTIMER_CTRL_FUNC_ATTR +esp_err_t gptimer_stop(gptimer_handle_t timer) +{ + ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + // disable counter, alarm, autoreload + portENTER_CRITICAL_SAFE(&timer->spinlock); + timer_ll_enable_counter(timer->hal.dev, timer->timer_id, false); + timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, false); + timer->fsm = GPTIMER_FSM_STOP; + portEXIT_CRITICAL_SAFE(&timer->spinlock); + + // disable interrupt service + if (timer->intr) { + ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt service failed"); + } + // release power manager lock + if (timer->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_release(timer->pm_lock), TAG, "release APB_FREQ_MAX lock failed"); + } + + return ESP_OK; +} + +static gptimer_group_t *gptimer_acquire_group_handle(int group_id) +{ + // esp_log_level_set(TAG, ESP_LOG_DEBUG); + bool new_group = false; + gptimer_group_t *group = NULL; + + // prevent install timer group concurrently + _lock_acquire(&s_platform.mutex); + if (!s_platform.groups[group_id]) { + group = heap_caps_calloc(1, sizeof(gptimer_group_t), GPTIMER_MEM_ALLOC_CAPS); + if (group) { + new_group = true; + s_platform.groups[group_id] = group; + // initialize timer group members + group->group_id = group_id; + group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // enable APB access timer registers + periph_module_enable(timer_group_periph_signals.groups[group_id].module); + } + } else { + group = s_platform.groups[group_id]; + } + // someone acquired the group handle means we have a new object that refer to this group + s_platform.group_ref_counts[group_id]++; + _lock_release(&s_platform.mutex); + + if (new_group) { + ESP_LOGD(TAG, "new group (%d) @%p", group_id, group); + } + + return group; +} + +static void gptimer_release_group_handle(gptimer_group_t *group) +{ + int group_id = group->group_id; + bool do_deinitialize = false; + + _lock_acquire(&s_platform.mutex); + s_platform.group_ref_counts[group_id]--; + if (s_platform.group_ref_counts[group_id] == 0) { + assert(s_platform.groups[group_id]); + do_deinitialize = true; + s_platform.groups[group_id] = NULL; + // Theoretically we need to disable the peripheral clock for the timer group + // However, next time when we enable the peripheral again, the registers will be reset to default value, including the watchdog registers inside the group + // Then the watchdog will go into reset state, e.g. the flash boot watchdog is enabled again and reset the system very soon + // periph_module_disable(timer_group_periph_signals.groups[group_id].module); + } + _lock_release(&s_platform.mutex); + + if (do_deinitialize) { + free(group); + ESP_LOGD(TAG, "del group (%d)", group_id); + } +} + +static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz) +{ + unsigned int counter_src_hz = 0; + esp_err_t ret = ESP_OK; + int timer_id = timer->timer_id; + switch (src_clk) { + case GPTIMER_CLK_SRC_APB: + counter_src_hz = esp_clk_apb_freq(); +#if CONFIG_PM_ENABLE + sprintf(timer->pm_lock_name, "gptimer_%d_%d", timer->group->group_id, timer_id); // e.g. gptimer_0_0 + ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, timer->pm_lock_name, &timer->pm_lock); + ESP_RETURN_ON_ERROR(ret, TAG, "create APB_FREQ_MAX lock failed"); + ESP_LOGD(TAG, "install APB_FREQ_MAX lock for timer (%d,%d)", timer->group->group_id, timer_id); +#endif + break; +#if SOC_TIMER_GROUP_SUPPORT_XTAL + case GPTIMER_CLK_SRC_XTAL: + counter_src_hz = esp_clk_xtal_freq(); + break; +#endif + default: + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "clock source %d is not support", src_clk); + break; + } + timer_ll_set_clock_source(timer->hal.dev, timer_id, src_clk); + unsigned int prescale = counter_src_hz / resolution_hz; // potential resolution loss here + timer_ll_set_clock_prescale(timer->hal.dev, timer_id, prescale); + timer->resolution_hz = counter_src_hz / prescale; // this is the real resolution + if (timer->resolution_hz != resolution_hz) { + ESP_LOGW(TAG, "resolution lost, expect %ul, real %ul", resolution_hz, timer->resolution_hz); + } + return ret; +} + +static esp_err_t gptimer_install_interrupt(gptimer_t *timer) +{ + esp_err_t ret = ESP_OK; + gptimer_group_t *group = timer->group; + int group_id = group->group_id; + int timer_id = timer->timer_id; + bool new_isr = false; + + if (!timer->intr) { + _lock_acquire(&timer->mutex); + if (!timer->intr) { + // if user wants to control the interrupt allocation more precisely, we can expose more flags in `gptimer_config_t` + int extra_isr_flags = timer->flags.intr_shared ? ESP_INTR_FLAG_SHARED : 0; + ret = esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_id].timer_irq_id[timer_id], extra_isr_flags | GPTIMER_INTR_ALLOC_FLAGS, + (uint32_t)timer_ll_get_intr_status_reg(timer->hal.dev), TIMER_LL_EVENT_ALARM(timer_id), + gptimer_default_isr, timer, &timer->intr); + new_isr = (ret == ESP_OK); + } + _lock_release(&timer->mutex); + } + + if (new_isr) { + ESP_LOGD(TAG, "install interrupt service for timer (%d,%d)", group_id, timer_id); + } + + return ret; +} + +// Put the default ISR handler in the IRAM for better performance +IRAM_ATTR static void gptimer_default_isr(void *args) +{ + bool need_yield = false; + gptimer_t *timer = (gptimer_t *)args; + gptimer_group_t *group = timer->group; + gptimer_alarm_cb_t on_alarm_cb = timer->on_alarm; + uint32_t intr_status = timer_ll_get_intr_status(timer->hal.dev); + + if (intr_status & TIMER_LL_EVENT_ALARM(timer->timer_id)) { + // Note: when alarm event happends, the alarm will be disabled automatically by hardware + gptimer_alarm_event_data_t edata = { + .count_value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id), + .alarm_value = timer->alarm_count, + }; + + portENTER_CRITICAL_ISR(&group->spinlock); + timer_ll_clear_intr_status(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id)); + // for auto-reload, we need to re-enable the alarm manually + if (timer->flags.auto_reload_on_alarm) { + timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, true); + } + portEXIT_CRITICAL_ISR(&group->spinlock); + + if (on_alarm_cb) { + if (on_alarm_cb(timer, &edata, timer->user_ctx)) { + need_yield = true; + } + } + } + + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///// The Following APIs are for internal use only (e.g. unit test) ///////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle) +{ + ESP_RETURN_ON_FALSE(timer && ret_intr_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + *ret_intr_handle = timer->intr; + return ESP_OK; +} + +esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock) +{ + ESP_RETURN_ON_FALSE(timer && ret_pm_lock, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + *ret_pm_lock = timer->pm_lock; + return ESP_OK; +} + +/** + * @brief This function will be called during start up, to check that gptimer driver is not running along with the legacy timer group driver + */ +__attribute__((constructor)) +static void check_gptimer_driver_conflict(void) +{ + extern int timer_group_driver_init_count; + timer_group_driver_init_count++; + if (timer_group_driver_init_count > 1) { + ESP_EARLY_LOGE(TAG, "CONFLICT! The gptimer driver can't work along with the legacy timer group driver"); + abort(); + } +} diff --git a/components/driver/include/driver/gptimer.h b/components/driver/include/driver/gptimer.h new file mode 100644 index 0000000000..1902e9e917 --- /dev/null +++ b/components/driver/include/driver/gptimer.h @@ -0,0 +1,197 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "hal/timer_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of General Purpose Timer handle + */ +typedef struct gptimer_t *gptimer_handle_t; + +/** + * @brief GPTimer event data + */ +typedef struct { + uint64_t count_value; /*!< Current count value */ + uint64_t alarm_value; /*!< Current alarm value */ +} gptimer_alarm_event_data_t; + +/** + * @brief Timer alarm callback prototype + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @param[in] edata Alarm event data, fed by driver + * @param[in] user_ctx User data, passed from `gptimer_config_t` + * @return Whether a high priority task has been waken up by this function + */ +typedef bool (*gptimer_alarm_cb_t) (gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx); + +/** + * @brief Group of supported GPTimer callbacks + * @note The callbacks are all running under ISR environment + */ +typedef struct { + gptimer_alarm_cb_t on_alarm; /*!< Timer alarm callback */ +} gptimer_event_callbacks_t; + +/** + * @brief General Purpose Timer configuration + */ +typedef struct { + gptimer_clock_source_t clk_src; /*!< GPTimer clock source */ + gptimer_count_direction_t direction; /*!< Count direction */ + uint32_t resolution_hz; /*!< Counter resolution (working frequency) in Hz, + hence, the step size of each count tick equals to (1 / resolution_hz) seconds */ + struct { + uint32_t intr_shared: 1; /*!< Set true, the timer interrupt number can be shared with other peripherals */ + } flags; +} gptimer_config_t; + +/** + * @brief General Purpose Timer alarm configuration + */ +typedef struct { + uint64_t alarm_count; /*!< Alarm target count value */ + uint64_t reload_count; /*!< Alarm reload count value, effect only when `auto_reload_on_alarm` is set to true */ + struct { + uint32_t auto_reload_on_alarm: 1; /*!< Reload the count value by hardware, immediately at the alarm event */ + } flags; +} gptimer_alarm_config_t; + +/** + * @brief Create a new General Purpose Timer, and return the handle + * + * @note Once a timer is created, it is placed in the stopped state and will not start until `gptimer_start()` is called. + * + * @param[in] config GPTimer configuration + * @param[out] ret_timer Returned timer handle + * @return + * - ESP_OK: Create GPTimer successfully + * - ESP_ERR_INVALID_ARG: Create GPTimer failed because of invalid argument + * - ESP_ERR_NO_MEM: Create GPTimer failed because out of memory + * - ESP_ERR_NOT_FOUND: Create GPTimer failed because all hardware timers are used up and no more free one + * - ESP_FAIL: Create GPTimer failed because of other error + */ +esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer); + +/** + * @brief Delete the GPTimer handle + * + * @note A timer must be in a stop state before it can be deleted. + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @return + * - ESP_OK: Delete GPTimer successfully + * - ESP_ERR_INVALID_ARG: Delete GPTimer failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Delete GPTimer failed because the timer has not stopped + * - ESP_FAIL: Delete GPTimer failed because of other error + */ +esp_err_t gptimer_del_timer(gptimer_handle_t timer); + +/** + * @brief Set GPTimer raw count value + * + * @note When updating the raw count of an active timer, the timer will immediately start counting from the new value. + * @note This function is allowed to run within ISR context + * @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @param[in] value Count value to be set + * @return + * - ESP_OK: Set GPTimer raw count value successfully + * - ESP_ERR_INVALID_ARG: Set GPTimer raw count value failed because of invalid argument + * - ESP_FAIL: Set GPTimer raw count value failed because of other error + */ +esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value); + +/** + * @brief Get GPTimer raw count value + * + * @note With the raw count value and the resolution set in the `gptimer_config_t`, you can convert the count value into seconds. + * @note This function is allowed to run within ISR context + * @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @param[out] value Returned GPTimer count value + * @return + * - ESP_OK: Get GPTimer raw count value successfully + * - ESP_ERR_INVALID_ARG: Get GPTimer raw count value failed because of invalid argument + * - ESP_FAIL: Get GPTimer raw count value failed because of other error + */ +esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value); + +/** + * @brief Set callbacks for GPTimer + * + * @note The user registered callbacks are expected to be runnable within ISR context + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + * - ESP_FAIL: Set event callbacks failed because of other error + */ +esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Set alarm event actions for GPTimer. + * + * @note This function is allowed to run within ISR context + * @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @param[in] config Alarm configuration, especially, set config to NULL means disabling the alarm function + * @return + * - ESP_OK: Set alarm action for GPTimer successfully + * - ESP_ERR_INVALID_ARG: Set alarm action for GPTimer failed because of invalid argument + * - ESP_FAIL: Set alarm action for GPTimer failed because of other error + */ +esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config); + +/** + * @brief Start GPTimer + * + * @note This function is allowed to run within ISR context + * @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @return + * - ESP_OK: Start GPTimer successfully + * - ESP_ERR_INVALID_ARG: Start GPTimer failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Start GPTimer failed because the timer is not in stop state + * - ESP_FAIL: Start GPTimer failed because of other error + */ +esp_err_t gptimer_start(gptimer_handle_t timer); + +/** + * @brief Stop GPTimer + * + * @note This function is allowed to run within ISR context + * @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @return + * - ESP_OK: Stop GPTimer successfully + * - ESP_ERR_INVALID_ARG: Stop GPTimer failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Stop GPTimer failed because the timer is not in start state + * - ESP_FAIL: Stop GPTimer failed because of other error + */ +esp_err_t gptimer_stop(gptimer_handle_t timer); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/esp_private/gptimer.h b/components/driver/include/esp_private/gptimer.h new file mode 100644 index 0000000000..4e357a2d57 --- /dev/null +++ b/components/driver/include/esp_private/gptimer.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// DO NOT USE THESE APIS IN YOUR APPLICATIONS +// The following APIs are for internal use, public to other IDF components, but not for users' applications. + +#pragma once + +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "esp_pm.h" +#include "driver/gptimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get GPTimer interrupt handle + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @param[out] ret_intr_handle Timer's internal interrupt handle + * @return + * - ESP_OK: Get GPTimer interrupt handle successfully + * - ESP_ERR_INVALID_ARG: Get GPTimer interrupt handle failed because of invalid argument + * - ESP_FAIL: Get GPTimer interrupt handle failed because of other error + */ +esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle); + +/** + * @brief Get GPTimer power management lock + * + * @param[in] timer Timer handle created by `gptimer_new_timer()` + * @param[out] ret_pm_lock Timer's internal power management lock + * @return + * - ESP_OK: Get GPTimer power management lock successfully + * - ESP_ERR_INVALID_ARG: Get GPTimer power management lock failed because of invalid argument + * - ESP_FAIL: Get GPTimer power management lock failed because of other error + */ +esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/legacy_new_driver_coexist.c b/components/driver/legacy_new_driver_coexist.c new file mode 100644 index 0000000000..3896ee2d5e --- /dev/null +++ b/components/driver/legacy_new_driver_coexist.c @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief This count is used to prevent the coexistence of + * the legacy timer group driver (deprecated/driver/timer.h) and the new gptimer driver (driver/gptimer.h). + */ +int timer_group_driver_init_count = 0; diff --git a/components/driver/test/test_timer.c b/components/driver/test/test_timer.c deleted file mode 100644 index 08598fee92..0000000000 --- a/components/driver/test/test_timer.c +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "esp_system.h" -#include "unity.h" -#include "nvs_flash.h" -#include "driver/timer.h" -#include "soc/rtc.h" -#include "soc/soc_caps.h" -#include "esp_rom_sys.h" - -#define TEST_TIMER_RESOLUTION_HZ 1000000 // 1MHz resolution -#define TIMER_DELTA 0.001 - -static bool alarm_flag; -static xQueueHandle timer_queue; - -typedef struct { - timer_group_t timer_group; - timer_idx_t timer_idx; -} timer_info_t; - -typedef struct { - timer_autoreload_t type; // the type of timer's event - timer_group_t timer_group; - timer_idx_t timer_idx; - uint64_t timer_counter_value; -} timer_event_t; - -#define TIMER_INFO_INIT(TG, TID) {.timer_group = (TG), .timer_idx = (TID),} - -static timer_info_t timer_info[] = { -#if SOC_TIMER_GROUP_TOTAL_TIMERS >= 4 - TIMER_INFO_INIT(TIMER_GROUP_0, TIMER_0), - TIMER_INFO_INIT(TIMER_GROUP_0, TIMER_1), - TIMER_INFO_INIT(TIMER_GROUP_1, TIMER_0), - TIMER_INFO_INIT(TIMER_GROUP_1, TIMER_1), -#elif SOC_TIMER_GROUP_TOTAL_TIMERS >= 2 - TIMER_INFO_INIT(TIMER_GROUP_0, TIMER_0), - TIMER_INFO_INIT(TIMER_GROUP_1, TIMER_0), -#else - TIMER_INFO_INIT(TIMER_GROUP_0, TIMER_0), -#endif -}; - -static intr_handle_t timer_isr_handles[SOC_TIMER_GROUP_TOTAL_TIMERS]; - -#define GET_TIMER_INFO(TG, TID) (&timer_info[(TG)*SOC_TIMER_GROUP_TIMERS_PER_GROUP+(TID)]) - -// timer group interruption handle callback -static bool test_timer_group_isr_cb(void *arg) -{ - bool is_awoken = false; - timer_info_t *info = (timer_info_t *) arg; - const timer_group_t timer_group = info->timer_group; - const timer_idx_t timer_idx = info->timer_idx; - uint64_t timer_val; - double time; - uint64_t alarm_value; - timer_event_t evt; - alarm_flag = true; - if (timer_group_get_auto_reload_in_isr(timer_group, timer_idx)) { // For autoreload mode, the counter value has been cleared - timer_group_clr_intr_status_in_isr(timer_group, timer_idx); - esp_rom_printf("This is TG%d timer[%d] reload-timer alarm!\n", timer_group, timer_idx); - timer_get_counter_value(timer_group, timer_idx, &timer_val); - timer_get_counter_time_sec(timer_group, timer_idx, &time); - evt.type = TIMER_AUTORELOAD_EN; - } else { - timer_group_clr_intr_status_in_isr(timer_group, timer_idx); - esp_rom_printf("This is TG%d timer[%d] count-up-timer alarm!\n", timer_group, timer_idx); - timer_get_counter_value(timer_group, timer_idx, &timer_val); - timer_get_counter_time_sec(timer_group, timer_idx, &time); - timer_get_alarm_value(timer_group, timer_idx, &alarm_value); - timer_set_counter_value(timer_group, timer_idx, 0); - evt.type = TIMER_AUTORELOAD_DIS; - } - evt.timer_group = timer_group; - evt.timer_idx = timer_idx; - evt.timer_counter_value = timer_val; - if (timer_queue != NULL) { - BaseType_t awoken = pdFALSE; - BaseType_t ret = xQueueSendFromISR(timer_queue, &evt, &awoken); - TEST_ASSERT_EQUAL(pdTRUE, ret); - if (awoken) { - is_awoken = true; - } - } - return is_awoken; -} - -// timer group interruption handle -static void test_timer_group_isr(void *arg) -{ - if (test_timer_group_isr_cb(arg)) { - portYIELD_FROM_ISR(); - } -} - -// initialize all timer -static void all_timer_init(timer_config_t *config, bool expect_init) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ASSERT_EQUAL((expect_init ? ESP_OK : ESP_ERR_INVALID_ARG), timer_init(tg_idx, timer_idx, config)); - } - } - if (timer_queue == NULL) { - timer_queue = xQueueCreate(10, sizeof(timer_event_t)); - } -} - -// deinitialize all timer -static void all_timer_deinit(void) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_deinit(tg_idx, timer_idx)); - } - } - if (timer_queue != NULL) { - vQueueDelete(timer_queue); - timer_queue = NULL; - } -} - -// start all of timer -static void all_timer_start(void) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_start(tg_idx, timer_idx)); - } - } -} - -static void all_timer_set_counter_value(uint64_t set_cnt_val) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_set_counter_value(tg_idx, timer_idx, set_cnt_val)); - } - } -} - -static void all_timer_pause(void) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_pause(tg_idx, timer_idx)); - } - } -} - -static void all_timer_get_counter_value(uint64_t set_cnt_val, bool expect_equal_set_val, - uint64_t *actual_cnt_val) -{ - uint64_t current_cnt_val; - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_get_counter_value(tg_idx, timer_idx, ¤t_cnt_val)); - if (expect_equal_set_val) { - TEST_ASSERT_EQUAL(set_cnt_val, current_cnt_val); - } else { - TEST_ASSERT_NOT_EQUAL(set_cnt_val, current_cnt_val); - if (actual_cnt_val != NULL) { - actual_cnt_val[tg_idx * SOC_TIMER_GROUP_TIMERS_PER_GROUP + timer_idx] = current_cnt_val; - } - } - } - } -} - -static void all_timer_get_counter_time_sec(int expect_time) -{ - double time; - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_get_counter_time_sec(tg_idx, timer_idx, &time)); - TEST_ASSERT_FLOAT_WITHIN(TIMER_DELTA, expect_time, time); - } - } -} - -static void all_timer_set_counter_mode(timer_count_dir_t counter_dir) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_set_counter_mode(tg_idx, timer_idx, counter_dir)); - } - } -} - -static void all_timer_set_divider(uint32_t divider) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_set_divider(tg_idx, timer_idx, divider)); - } - } -} - -static void all_timer_set_alarm_value(uint64_t alarm_cnt_val) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_set_alarm_value(tg_idx, timer_idx, alarm_cnt_val)); - } - } -} - -static void all_timer_get_alarm_value(uint64_t *alarm_vals) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_get_alarm_value(tg_idx, timer_idx, &alarm_vals[tg_idx * SOC_TIMER_GROUP_TIMERS_PER_GROUP + timer_idx])); - } - } -} - -static void all_timer_isr_reg(void) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_isr_register(tg_idx, timer_idx, test_timer_group_isr, - GET_TIMER_INFO(tg_idx, timer_idx), ESP_INTR_FLAG_LOWMED, &timer_isr_handles[tg_idx * SOC_TIMER_GROUP_TIMERS_PER_GROUP + timer_idx])); - } - } -} - -static void all_timer_isr_unreg(void) -{ - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(esp_intr_free(timer_isr_handles[tg_idx * SOC_TIMER_GROUP_TIMERS_PER_GROUP + timer_idx])); - } - } -} - -// enable interrupt and start timer -static void timer_intr_enable_and_start(int timer_group, int timer_idx, double alarm_time) -{ - TEST_ESP_OK(timer_pause(timer_group, timer_idx)); - TEST_ESP_OK(timer_set_counter_value(timer_group, timer_idx, 0x0)); - TEST_ESP_OK(timer_set_alarm_value(timer_group, timer_idx, alarm_time * TEST_TIMER_RESOLUTION_HZ)); - TEST_ESP_OK(timer_set_alarm(timer_group, timer_idx, TIMER_ALARM_EN)); - TEST_ESP_OK(timer_enable_intr(timer_group, timer_idx)); - TEST_ESP_OK(timer_start(timer_group, timer_idx)); -} - -static void timer_isr_check(timer_group_t group_num, timer_idx_t timer_num, timer_autoreload_t autoreload, uint64_t alarm_cnt_val) -{ - timer_event_t evt; - TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(timer_queue, &evt, 3000 / portTICK_PERIOD_MS)); - TEST_ASSERT_EQUAL(autoreload, evt.type); - TEST_ASSERT_EQUAL(group_num, evt.timer_group); - TEST_ASSERT_EQUAL(timer_num, evt.timer_idx); - TEST_ASSERT_EQUAL((uint32_t)(alarm_cnt_val >> 32), (uint32_t)(evt.timer_counter_value >> 32)); - TEST_ASSERT_UINT32_WITHIN(1000, (uint32_t)(alarm_cnt_val), (uint32_t)(evt.timer_counter_value)); -} - -static void timer_intr_enable_disable_test(timer_group_t group_num, timer_idx_t timer_num, uint64_t alarm_cnt_val) -{ - alarm_flag = false; - TEST_ESP_OK(timer_set_counter_value(group_num, timer_num, 0)); - TEST_ESP_OK(timer_set_alarm(group_num, timer_num, TIMER_ALARM_EN)); - TEST_ESP_OK(timer_enable_intr(group_num, timer_num)); - TEST_ESP_OK(timer_start(group_num, timer_num)); - timer_isr_check(group_num, timer_num, TIMER_AUTORELOAD_DIS, alarm_cnt_val); - TEST_ASSERT_EQUAL(true, alarm_flag); - - // disable interrupt of tg0_timer0 - alarm_flag = false; - TEST_ESP_OK(timer_pause(group_num, timer_num)); - TEST_ESP_OK(timer_set_counter_value(group_num, timer_num, 0)); - TEST_ESP_OK(timer_disable_intr(group_num, timer_num)); - TEST_ESP_OK(timer_start(group_num, timer_num)); - vTaskDelay(2000 / portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(false, alarm_flag); -} - -TEST_CASE("Timer init", "[hw_timer]") -{ - // Test init 1:config parameter - // empty parameter - timer_config_t config0 = { }; - all_timer_init(&config0, false); - - // only one parameter - timer_config_t config1 = { - .auto_reload = TIMER_AUTORELOAD_EN - }; - all_timer_init(&config1, false); - - // lack one parameter - timer_config_t config2 = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .intr_type = TIMER_INTR_LEVEL - }; - all_timer_init(&config2, true); - - config2.counter_en = TIMER_PAUSE; - all_timer_init(&config2, true); - - // Test init 2: init - uint64_t set_timer_val = 0x0; - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_DIS, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .intr_type = TIMER_INTR_LEVEL - }; - - // judge get config parameters - timer_config_t get_config; - TEST_ESP_OK(timer_init(TIMER_GROUP_0, TIMER_0, &config)); - TEST_ESP_OK(timer_get_config(TIMER_GROUP_0, TIMER_0, &get_config)); - TEST_ASSERT_EQUAL(config.alarm_en, get_config.alarm_en); - TEST_ASSERT_EQUAL(config.auto_reload, get_config.auto_reload); - TEST_ASSERT_EQUAL(config.counter_dir, get_config.counter_dir); - TEST_ASSERT_EQUAL(config.counter_en, get_config.counter_en); - TEST_ASSERT_EQUAL(config.intr_type, get_config.intr_type); - TEST_ASSERT_EQUAL(config.divider, get_config.divider); - - all_timer_init(&config, true); - all_timer_pause(); - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - all_timer_get_counter_value(set_timer_val, false, NULL); - - // Test init 3: wrong parameter - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_init(-1, TIMER_0, &config)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_init(TIMER_GROUP_0, 2, &config)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_init(TIMER_GROUP_0, -1, &config)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_init(2, TIMER_0, &config)); - all_timer_deinit(); -} - -/** - * read count case: - * 1. start timer compare value - * 2. pause timer compare value - * 3. delay some time */ -TEST_CASE("Timer read counter value", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .intr_type = TIMER_INTR_LEVEL - }; - uint64_t set_timer_val = 0x0; - - all_timer_init(&config, true); - - // Test read value 1: start timer get counter value - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - all_timer_get_counter_value(set_timer_val, false, NULL); - - // Test read value 2: pause timer get counter value - all_timer_pause(); - set_timer_val = 0x30405000ULL; - all_timer_set_counter_value(set_timer_val); - all_timer_get_counter_value(set_timer_val, true, NULL); - - // Test read value 3:delay 1s get counter value - set_timer_val = 0x0; - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); - all_timer_get_counter_time_sec(1); - all_timer_deinit(); -} - -/** - * start timer case: - * 1. normal start - * 2. error start parameter - * */ -TEST_CASE("Timer start", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .intr_type = TIMER_INTR_LEVEL - }; - uint64_t set_timer_val = 0x0; - all_timer_init(&config, true); - - //Test start 1: normal start - all_timer_start(); - all_timer_set_counter_value(set_timer_val); - all_timer_get_counter_value(set_timer_val, false, NULL); - - //Test start 2:wrong parameter - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_start(2, TIMER_0)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_start(-1, TIMER_0)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_start(TIMER_GROUP_0, 2)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_start(TIMER_GROUP_0, -1)); - all_timer_deinit(); -} - -/** - * pause timer case: - * 1. normal pause, read value - * 2. error pause error - */ -TEST_CASE("Timer pause", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .intr_type = TIMER_INTR_LEVEL - }; - uint64_t set_timer_val = 0x0; - all_timer_init(&config, true); - - //Test pause 1: right parameter - all_timer_pause(); - all_timer_set_counter_value(set_timer_val); - all_timer_get_counter_value(set_timer_val, true, NULL); - - //Test pause 2: wrong parameter - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_pause(-1, TIMER_0)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_pause(TIMER_GROUP_0, -1)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_pause(2, TIMER_0)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_pause(TIMER_GROUP_0, 2)); - all_timer_deinit(); -} - -// positive mode and negative mode -TEST_CASE("Timer counter mode (up / down)", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .intr_type = TIMER_INTR_LEVEL - }; - uint64_t set_timer_val = 0x0; - all_timer_init(&config, true); - all_timer_pause(); - - // Test counter mode 1: TIMER_COUNT_UP - all_timer_set_counter_mode(TIMER_COUNT_UP); - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); - all_timer_get_counter_time_sec(1); - - // Test counter mode 2: TIMER_COUNT_DOWN - all_timer_pause(); - set_timer_val = TEST_TIMER_RESOLUTION_HZ * 3; // 3s clock counter value - all_timer_set_counter_mode(TIMER_COUNT_DOWN); - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); - all_timer_get_counter_time_sec(2); - - // Test counter mode 3 : wrong parameter - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_set_counter_mode(TIMER_GROUP_0, TIMER_0, -1)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_set_counter_mode(TIMER_GROUP_0, TIMER_0, 2)); - all_timer_deinit(); -} - -TEST_CASE("Timer divider", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .intr_type = TIMER_INTR_LEVEL - }; - uint64_t set_timer_val = 0; - uint64_t time_val[TIMER_GROUP_MAX * TIMER_MAX]; - uint64_t comp_time_val[TIMER_GROUP_MAX * TIMER_MAX]; - all_timer_init(&config, true); - - all_timer_pause(); - all_timer_set_counter_value(set_timer_val); - - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); - all_timer_get_counter_value(set_timer_val, false, time_val); - - all_timer_pause(); - all_timer_set_divider(config.divider / 2); // half of original divider - all_timer_set_counter_value(set_timer_val); - - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); //delay the same time - all_timer_get_counter_value(set_timer_val, false, comp_time_val); - for (int i = 0; i < TIMER_GROUP_MAX * TIMER_MAX; i++) { - TEST_ASSERT_INT_WITHIN(2000, 1000000, time_val[i]); - TEST_ASSERT_INT_WITHIN(2000, 2000000, comp_time_val[i]); - } - - all_timer_pause(); - all_timer_set_divider(256); - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); //delay the same time - all_timer_get_counter_value(set_timer_val, false, comp_time_val); - for (int i = 0; i < TIMER_GROUP_MAX * TIMER_MAX; i++) { - TEST_ASSERT_INT_WITHIN(100, APB_CLK_FREQ / 256, comp_time_val[i]); - } - - all_timer_pause(); - all_timer_set_divider(2); - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); - all_timer_get_counter_value(set_timer_val, false, comp_time_val); - for (int i = 0; i < TIMER_GROUP_MAX * TIMER_MAX; i++) { - TEST_ASSERT_INT_WITHIN(10000, APB_CLK_FREQ / 2, comp_time_val[i]); - } - - all_timer_pause(); - all_timer_set_divider(65536); - all_timer_set_counter_value(set_timer_val); - all_timer_start(); - vTaskDelay(1000 / portTICK_PERIOD_MS); //delay the same time - all_timer_get_counter_value(set_timer_val, false, comp_time_val); - for (int i = 0; i < TIMER_GROUP_MAX * TIMER_MAX; i++) { - TEST_ASSERT_INT_WITHIN(10, APB_CLK_FREQ / 65536, comp_time_val[i]); - } - - all_timer_pause(); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_set_divider(TIMER_GROUP_0, TIMER_0, 1)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, timer_set_divider(TIMER_GROUP_0, TIMER_0, 65537)); - all_timer_deinit(); -} - -/** - * enable alarm case: - * 1. enable alarm ,set alarm value and get value - * 2. disable alarm ,set alarm value and get value - */ -TEST_CASE("Timer enable alarm", "[hw_timer]") -{ - timer_config_t config_test = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_DIS, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL - }; - all_timer_init(&config_test, true); - all_timer_isr_reg(); - - // enable alarm of tg0_timer1 - alarm_flag = false; - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_0, TIMER_0, TIMER_ALARM_EN)); - timer_intr_enable_and_start(TIMER_GROUP_0, TIMER_0, 1.2); - timer_isr_check(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_DIS, 1.2 * TEST_TIMER_RESOLUTION_HZ); - TEST_ASSERT_EQUAL(true, alarm_flag); - - // disable alarm of tg0_timer1 - alarm_flag = false; - timer_intr_enable_and_start(TIMER_GROUP_0, TIMER_0, 1.2); - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_0, TIMER_0, TIMER_ALARM_DIS)); - vTaskDelay(2000 / portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(false, alarm_flag); - -#if SOC_TIMER_GROUPS > 1 - // enable alarm of tg1_timer0 - alarm_flag = false; - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_1, TIMER_0, TIMER_ALARM_EN)); - timer_intr_enable_and_start(TIMER_GROUP_1, TIMER_0, 1.2); - timer_isr_check(TIMER_GROUP_1, TIMER_0, TIMER_AUTORELOAD_DIS, 1.2 * TEST_TIMER_RESOLUTION_HZ); - TEST_ASSERT_EQUAL(true, alarm_flag); - - // disable alarm of tg1_timer0 - alarm_flag = false; - timer_intr_enable_and_start(TIMER_GROUP_1, TIMER_0, 1.2); - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_1, TIMER_0, TIMER_ALARM_DIS)); - vTaskDelay(2000 / portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(false, alarm_flag); -#endif - all_timer_isr_unreg(); - all_timer_deinit(); -} - -/** - * alarm value case: - * 1. set alarm value and get value - * 2. interrupt test time - */ -TEST_CASE("Timer set alarm value", "[hw_timer]") -{ - uint64_t alarm_val[SOC_TIMER_GROUP_TOTAL_TIMERS]; - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL - }; - all_timer_init(&config, true); - all_timer_isr_reg(); - - // set and get alarm value - all_timer_set_alarm_value(3 * TEST_TIMER_RESOLUTION_HZ); - all_timer_get_alarm_value(alarm_val); - for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { - TEST_ASSERT_EQUAL_UINT32(3 * TEST_TIMER_RESOLUTION_HZ, (uint32_t)alarm_val[i]); - } - - // set interrupt read alarm value - timer_intr_enable_and_start(TIMER_GROUP_0, TIMER_0, 2.4); - timer_isr_check(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_DIS, 2.4 * TEST_TIMER_RESOLUTION_HZ); -#if SOC_TIMER_GROUPS > 1 - timer_intr_enable_and_start(TIMER_GROUP_1, TIMER_0, 1.4); - timer_isr_check(TIMER_GROUP_1, TIMER_0, TIMER_AUTORELOAD_DIS, 1.4 * TEST_TIMER_RESOLUTION_HZ); -#endif - all_timer_isr_unreg(); - all_timer_deinit(); -} - -/** - * auto reload case: - * 1. no reload - * 2. auto reload - */ -TEST_CASE("Timer auto reload", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL - }; - all_timer_init(&config, true); - all_timer_isr_reg(); - - // test disable auto_reload - timer_intr_enable_and_start(TIMER_GROUP_0, TIMER_0, 1.14); - timer_isr_check(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_DIS, 1.14 * TEST_TIMER_RESOLUTION_HZ); -#if SOC_TIMER_GROUPS > 1 - timer_intr_enable_and_start(TIMER_GROUP_1, TIMER_0, 1.14); - timer_isr_check(TIMER_GROUP_1, TIMER_0, TIMER_AUTORELOAD_DIS, 1.14 * TEST_TIMER_RESOLUTION_HZ); -#endif - - //test enable auto_reload - TEST_ESP_OK(timer_set_auto_reload(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_EN)); - timer_intr_enable_and_start(TIMER_GROUP_0, TIMER_0, 1.4); - timer_isr_check(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_EN, 0); -#if SOC_TIMER_GROUPS > 1 - TEST_ESP_OK(timer_set_auto_reload(TIMER_GROUP_1, TIMER_0, TIMER_AUTORELOAD_EN)); - timer_intr_enable_and_start(TIMER_GROUP_1, TIMER_0, 1.4); - timer_isr_check(TIMER_GROUP_1, TIMER_0, TIMER_AUTORELOAD_EN, 0); -#endif - all_timer_isr_unreg(); - all_timer_deinit(); -} - -/** - * timer_enable_intr case: - * 1. enable timer_intr - * 2. disable timer_intr - */ -TEST_CASE("Timer enable timer interrupt", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_DIS, - .counter_dir = TIMER_COUNT_UP, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL - }; - - all_timer_init(&config, true); - all_timer_pause(); - all_timer_set_alarm_value(1.2 * TEST_TIMER_RESOLUTION_HZ); - all_timer_set_counter_value(0); - all_timer_isr_reg(); - timer_intr_enable_disable_test(TIMER_GROUP_0, TIMER_0, 1.2 * TEST_TIMER_RESOLUTION_HZ); -#if SOC_TIMER_GROUPS > 1 - timer_intr_enable_disable_test(TIMER_GROUP_1, TIMER_0, 1.2 * TEST_TIMER_RESOLUTION_HZ); -#endif - - // enable interrupt of tg0_timer0 again - alarm_flag = false; - TEST_ESP_OK(timer_pause(TIMER_GROUP_0, TIMER_0)); - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0)); - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_0, TIMER_0, TIMER_ALARM_EN)); - TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0)); - TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - timer_isr_check(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_DIS, 1.2 * TEST_TIMER_RESOLUTION_HZ); - TEST_ASSERT_EQUAL(true, alarm_flag); - all_timer_isr_unreg(); - all_timer_deinit(); -} - -/** - * enable timer group case: - * 1. enable timer group - * 2. disable timer group - */ -TEST_CASE("Timer enable timer group interrupt", "[hw_timer][ignore]") -{ - intr_handle_t isr_handle = NULL; - alarm_flag = false; - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL - }; - uint64_t set_timer_val = 0x0; - all_timer_init(&config, true); - all_timer_pause(); - all_timer_set_counter_value(set_timer_val); - all_timer_set_alarm_value(1.2 * TEST_TIMER_RESOLUTION_HZ); - - // enable interrupt of tg0_timer0 - TEST_ESP_OK(timer_group_intr_enable(TIMER_GROUP_0, TIMER_INTR_T0)); - TEST_ESP_OK(timer_isr_register(TIMER_GROUP_0, TIMER_0, test_timer_group_isr, - GET_TIMER_INFO(TIMER_GROUP_0, TIMER_0), ESP_INTR_FLAG_LOWMED, &isr_handle)); - TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - timer_isr_check(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_DIS, 1.2 * TEST_TIMER_RESOLUTION_HZ); - TEST_ASSERT_EQUAL(true, alarm_flag); - - // disable interrupt of tg0_timer0 - alarm_flag = false; - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, set_timer_val)); - TEST_ESP_OK(timer_group_intr_disable(TIMER_GROUP_0, TIMER_INTR_T0)); - TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - vTaskDelay(2000 / portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(false, alarm_flag); - esp_intr_free(isr_handle); -} - -/** - * isr_register case: - * Cycle register 15 times, compare the heap size to ensure no memory leaks - */ -TEST_CASE("Timer interrupt register", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_DIS, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL - }; - - for (int i = 0; i < 15; i++) { - all_timer_init(&config, true); - timer_isr_handle_t timer_isr_handle[TIMER_GROUP_MAX * TIMER_MAX]; - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(timer_isr_register(tg_idx, timer_idx, test_timer_group_isr, - GET_TIMER_INFO(tg_idx, timer_idx), ESP_INTR_FLAG_LOWMED, &timer_isr_handle[tg_idx * SOC_TIMER_GROUP_TIMERS_PER_GROUP + timer_idx])); - } - } - - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_0, TIMER_0, TIMER_ALARM_EN)); - timer_intr_enable_and_start(TIMER_GROUP_0, TIMER_0, 0.54); -#if SOC_TIMER_GROUPS > 1 - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_1, TIMER_0, TIMER_ALARM_EN)); - timer_intr_enable_and_start(TIMER_GROUP_1, TIMER_0, 0.34); -#endif - - TEST_ESP_OK(timer_set_auto_reload(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_EN)); - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_0, TIMER_0, TIMER_ALARM_EN)); - timer_intr_enable_and_start(TIMER_GROUP_0, TIMER_0, 0.4); -#if SOC_TIMER_GROUPS > 1 - TEST_ESP_OK(timer_set_auto_reload(TIMER_GROUP_1, TIMER_0, TIMER_AUTORELOAD_EN)); - TEST_ESP_OK(timer_set_alarm(TIMER_GROUP_1, TIMER_0, TIMER_ALARM_EN)); - timer_intr_enable_and_start(TIMER_GROUP_1, TIMER_0, 0.6); -#endif - vTaskDelay(1000 / portTICK_PERIOD_MS); - - // ISR hanlde function should be free before next ISR register. - for (uint32_t tg_idx = 0; tg_idx < TIMER_GROUP_MAX; tg_idx++) { - for (uint32_t timer_idx = 0; timer_idx < TIMER_MAX; timer_idx++) { - TEST_ESP_OK(esp_intr_free(timer_isr_handle[tg_idx * SOC_TIMER_GROUP_TIMERS_PER_GROUP + timer_idx])); - } - } - all_timer_deinit(); - } -} - -#if SOC_TIMER_GROUP_SUPPORT_XTAL -/** - * Timer clock source: - * 1. configure clock source as APB clock, and enable timer interrupt - * 2. configure clock source as XTAL clock, adn enable timer interrupt - */ -TEST_CASE("Timer clock source", "[hw_timer]") -{ - // configure clock source as APB clock - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_DIS, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL, - }; - all_timer_init(&config, true); - all_timer_pause(); - all_timer_set_alarm_value(1.2 * TEST_TIMER_RESOLUTION_HZ); - all_timer_set_counter_value(0); - all_timer_isr_reg(); - - timer_intr_enable_disable_test(TIMER_GROUP_0, TIMER_0, 1.2 * TEST_TIMER_RESOLUTION_HZ); -#if SOC_TIMER_GROUPS > 1 - timer_intr_enable_disable_test(TIMER_GROUP_1, TIMER_0, 1.2 * TEST_TIMER_RESOLUTION_HZ); -#endif - - // configure clock source as XTAL clock - all_timer_pause(); - config.clk_src = TIMER_SRC_CLK_XTAL; - config.divider = rtc_clk_xtal_freq_get() * 1000000 / TEST_TIMER_RESOLUTION_HZ; - all_timer_init(&config, true); - all_timer_set_alarm_value(1.2 * TEST_TIMER_RESOLUTION_HZ); - - timer_intr_enable_disable_test(TIMER_GROUP_0, TIMER_0, 1.2 * TEST_TIMER_RESOLUTION_HZ); -#if SOC_TIMER_GROUPS > 1 - timer_intr_enable_disable_test(TIMER_GROUP_1, TIMER_0, 1.2 * TEST_TIMER_RESOLUTION_HZ); -#endif - - all_timer_isr_unreg(); - all_timer_deinit(); -} -#endif - -/** - * Timer ISR callback test - */ -TEST_CASE("Timer ISR callback", "[hw_timer]") -{ - alarm_flag = false; - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_DIS, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL, - }; - uint64_t alarm_cnt_val = 1.2 * TEST_TIMER_RESOLUTION_HZ; - uint64_t set_timer_val = 0x0; - all_timer_init(&config, true); - all_timer_pause(); - all_timer_set_alarm_value(alarm_cnt_val); - all_timer_set_counter_value(set_timer_val); - - // add isr callback for tg0_timer0 - TEST_ESP_OK(timer_isr_callback_add(TIMER_GROUP_0, TIMER_0, test_timer_group_isr_cb, - GET_TIMER_INFO(TIMER_GROUP_0, TIMER_0), ESP_INTR_FLAG_LOWMED)); - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, set_timer_val)); - TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - timer_isr_check(TIMER_GROUP_0, TIMER_0, TIMER_AUTORELOAD_DIS, alarm_cnt_val); - TEST_ASSERT_EQUAL(true, alarm_flag); - - // remove isr callback for tg0_timer0 - TEST_ESP_OK(timer_pause(TIMER_GROUP_0, TIMER_0)); - TEST_ESP_OK(timer_isr_callback_remove(TIMER_GROUP_0, TIMER_0)); - alarm_flag = false; - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, set_timer_val)); - TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - vTaskDelay(2000 / portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(false, alarm_flag); - -#if SOC_TIMER_GROUPS > 1 - // add isr callback for tg1_timer0 - TEST_ESP_OK(timer_pause(TIMER_GROUP_1, TIMER_0)); - TEST_ESP_OK(timer_isr_callback_add(TIMER_GROUP_1, TIMER_0, test_timer_group_isr_cb, - GET_TIMER_INFO(TIMER_GROUP_1, TIMER_0), ESP_INTR_FLAG_LOWMED)); - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_1, TIMER_0, set_timer_val)); - TEST_ESP_OK(timer_start(TIMER_GROUP_1, TIMER_0)); - timer_isr_check(TIMER_GROUP_1, TIMER_0, TIMER_AUTORELOAD_DIS, alarm_cnt_val); - TEST_ASSERT_EQUAL(true, alarm_flag); - - // remove isr callback for tg1_timer0 - TEST_ESP_OK(timer_pause(TIMER_GROUP_1, TIMER_0)); - TEST_ESP_OK(timer_isr_callback_remove(TIMER_GROUP_1, TIMER_0)); - alarm_flag = false; - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_1, TIMER_0, set_timer_val)); - TEST_ESP_OK(timer_start(TIMER_GROUP_1, TIMER_0)); - vTaskDelay(2000 / portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(false, alarm_flag); -#endif - all_timer_deinit(); -} - -/** - * Timer memory test - */ -TEST_CASE("Timer init/deinit stress test", "[hw_timer]") -{ - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .alarm_en = TIMER_ALARM_EN, - .auto_reload = TIMER_AUTORELOAD_EN, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .intr_type = TIMER_INTR_LEVEL, - }; - for (uint32_t i = 0; i < 100; i++) { - all_timer_init(&config, true); - all_timer_deinit(); - } -} - -// The following test cases are used to check if the timer_group fix works. -// Some applications use a software reset, at the reset time, timer_group happens to generate an interrupt. -// but software reset does not clear interrupt status, this is not safe for application when enable the interrupt of timer_group. -// This case will check under this fix, whether the interrupt status is cleared after timer_group initialization. -static void timer_group_test_init(void) -{ - static const uint32_t time_ms = 100; // Alarm value 100ms. - static const uint32_t ste_val = time_ms * TEST_TIMER_RESOLUTION_HZ / 1000; - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_PAUSE, - .alarm_en = TIMER_ALARM_EN, - .intr_type = TIMER_INTR_LEVEL, - .auto_reload = TIMER_AUTORELOAD_EN, - }; - TEST_ESP_OK(timer_init(TIMER_GROUP_0, TIMER_0, &config)); - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL)); - TEST_ESP_OK(timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, ste_val)); - //Now the timer is ready. - //We only need to check the interrupt status and don't have to register a interrupt routine. -} - -static void timer_group_test_first_stage(void) -{ - static uint8_t loop_cnt = 0; - timer_group_test_init(); - //Start timer - TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0)); - TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - //Waiting for timer_group to generate an interrupt - while ( !(timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0) && - loop_cnt++ < 100) { - vTaskDelay(200); - } - TEST_ASSERT_EQUAL(TIMER_INTR_T0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0); - esp_restart(); -} - -static void timer_group_test_second_stage(void) -{ - TEST_ASSERT_EQUAL(ESP_RST_SW, esp_reset_reason()); - timer_group_test_init(); - TEST_ASSERT_EQUAL(0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0); - // After enable the interrupt, timer alarm should not trigger immediately - TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0)); - //After the timer_group is initialized, TIMERG0.int_raw.t0 should be cleared. - TEST_ASSERT_EQUAL(0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0); -} - -TEST_CASE_MULTIPLE_STAGES("timer_group software reset test", - "[intr_status][intr_status = 0]", - timer_group_test_first_stage, - timer_group_test_second_stage); - -// -// Timer check reinitialization sequence -// -TEST_CASE("Timer check reinitialization sequence", "[hw_timer]") -{ - // 1. step - install driver - timer_group_test_init(); - // 2 - register interrupt and start timer - TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0)); - TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - // Do some work - vTaskDelay(80 / portTICK_PERIOD_MS); - // 3 - deinit timer driver - TEST_ESP_OK(timer_deinit(TIMER_GROUP_0, TIMER_0)); - timer_config_t config = { - .clk_src = TIMER_SRC_CLK_APB, - .divider = APB_CLK_FREQ / TEST_TIMER_RESOLUTION_HZ, - .counter_dir = TIMER_COUNT_UP, - .counter_en = TIMER_START, - .alarm_en = TIMER_ALARM_EN, - .intr_type = TIMER_INTR_LEVEL, - .auto_reload = TIMER_AUTORELOAD_EN, - }; - // 4 - reinstall driver - TEST_ESP_OK(timer_init(TIMER_GROUP_0, TIMER_0, &config)); - // 5 - enable interrupt - TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0)); - vTaskDelay(30 / portTICK_PERIOD_MS); - // The pending timer interrupt should not be triggered - TEST_ASSERT_EQUAL(0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0); -} diff --git a/components/driver/test_apps/gptimer/CMakeLists.txt b/components/driver/test_apps/gptimer/CMakeLists.txt new file mode 100644 index 0000000000..897e67221c --- /dev/null +++ b/components/driver/test_apps/gptimer/CMakeLists.txt @@ -0,0 +1,18 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(gptimer_test) + +if(CONFIG_GPTIMER_ISR_IRAM_SAFE) + add_custom_target(check_test_app_sections ALL + COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py + --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/ + --elf-file ${CMAKE_BINARY_DIR}/gptimer_test.elf + find-refs + --from-sections=.iram0.text + --to-sections=.flash.text,.flash.rodata + --exit-code + DEPENDS ${elf} + ) +endif() diff --git a/components/driver/test_apps/gptimer/README.md b/components/driver/test_apps/gptimer/README.md new file mode 100644 index 0000000000..3f7a0a04f0 --- /dev/null +++ b/components/driver/test_apps/gptimer/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/gptimer/app_test.py b/components/driver/test_apps/gptimer/app_test.py new file mode 100644 index 0000000000..649eac8830 --- /dev/null +++ b/components/driver/test_apps/gptimer/app_test.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import glob +import os + +import ttfw_idf +from tiny_test_fw import Utility + + +@ttfw_idf.idf_component_unit_test(env_tag='COMPONENT_UT_GENERIC', target=['esp32', 'esp32s2', 'esp32s3', 'esp32c3']) +def test_component_ut_gptimer(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None + # Get the names of all configs (sdkconfig.ci.* files) + config_files = glob.glob(os.path.join(os.path.dirname(__file__), 'sdkconfig.ci.*')) + config_names = [os.path.basename(s).replace('sdkconfig.ci.', '') for s in config_files] + + # Run test once with binaries built for each config + for name in config_names: + Utility.console_log(f'Checking config "{name}"... ', end='') + dut = env.get_dut('gptimer', 'components/driver/test_apps/gptimer', app_config_name=name) + dut.start_app() + stdout = dut.expect('Press ENTER to see the list of tests', full_stdout=True) + dut.write('*') + stdout = dut.expect("Enter next test, or 'enter' to see menu", full_stdout=True, timeout=30) + ttfw_idf.ComponentUTResult.parse_result(stdout,ttfw_idf.TestFormat.UNITY_BASIC) + env.close_dut(dut.name) + Utility.console_log(f'Test config "{name}" done') + + +if __name__ == '__main__': + test_component_ut_gptimer() diff --git a/components/driver/test_apps/gptimer/main/CMakeLists.txt b/components/driver/test_apps/gptimer/main/CMakeLists.txt new file mode 100644 index 0000000000..a3b22adfb3 --- /dev/null +++ b/components/driver/test_apps/gptimer/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(srcs "test_app_main.c" + "test_gptimer.c" + "test_gptimer_iram.c") + +idf_component_register(SRCS ${srcs} + PRIV_REQUIRES driver unity spi_flash) + +target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_gptimer" "-u test_app_include_gptimer_iram") diff --git a/components/driver/test_apps/gptimer/main/test_app_main.c b/components/driver/test_apps/gptimer/main/test_app_main.c new file mode 100644 index 0000000000..e4a1d39b41 --- /dev/null +++ b/components/driver/test_apps/gptimer/main/test_app_main.c @@ -0,0 +1,51 @@ +/* + * 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 GPTimer driver, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-300) + +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) +{ + // ____ ____ _____ _ _____ _ + // / ___| _ \_ _(_)_ __ ___ ___ _ __ |_ _|__ ___| |_ + // | | _| |_) || | | | '_ ` _ \ / _ \ '__| | |/ _ \/ __| __| + // | |_| | __/ | | | | | | | | | __/ | | | __/\__ \ |_ + // \____|_| |_| |_|_| |_| |_|\___|_| |_|\___||___/\__| + printf(" ____ ____ _____ _ _____ _\r\n"); + printf(" / ___| _ \\_ _(_)_ __ ___ ___ _ __ |_ _|__ ___| |_\r\n"); + printf("| | _| |_) || | | | '_ ` _ \\ / _ \\ '__| | |/ _ \\/ __| __|\r\n"); + printf("| |_| | __/ | | | | | | | | | __/ | | | __/\\__ \\ |_\r\n"); + printf(" \\____|_| |_| |_|_| |_| |_|\\___|_| |_|\\___||___/\\__|\r\n"); + unity_run_menu(); +} diff --git a/components/driver/test_apps/gptimer/main/test_gptimer.c b/components/driver/test_apps/gptimer/main/test_gptimer.c new file mode 100644 index 0000000000..fc776119b5 --- /dev/null +++ b/components/driver/test_apps/gptimer/main/test_gptimer.c @@ -0,0 +1,487 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/gptimer.h" +#include "soc/soc_caps.h" +#include "esp_attr.h" + +#if CONFIG_GPTIMER_ISR_IRAM_SAFE +#define TEST_ALARM_CALLBACK_ATTR IRAM_ATTR +#else +#define TEST_ALARM_CALLBACK_ATTR +#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE + +void test_app_include_gptimer(void) +{ +} + +TEST_CASE("gptimer_set_get_raw_count", "[gptimer]") +{ + gptimer_config_t config = { + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&config, &timers[i])); + } + + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gptimer_new_timer(&config, &timers[0])); + unsigned long long get_value = 0; + printf("check gptimer initial count value\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &get_value)); + TEST_ASSERT_EQUAL(0, get_value); + } + unsigned long long set_values[] = {100, 500, 666}; + for (size_t j = 0; j < sizeof(set_values) / sizeof(set_values[0]); j++) { + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + printf("set raw count to %llu for gptimer %d\r\n", set_values[j], i); + TEST_ESP_OK(gptimer_set_raw_count(timers[i], set_values[j])); + } + vTaskDelay(pdMS_TO_TICKS(10)); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &get_value)); + printf("get raw count of gptimer %d: %llu\r\n", i, get_value); + TEST_ASSERT_EQUAL(set_values[j], get_value); + } + } + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } +} + +TEST_CASE("gptimer_wallclock_with_various_clock_sources", "[gptimer]") +{ + gptimer_clock_source_t test_clk_srcs[] = { + GPTIMER_CLK_SRC_APB, +#if SOC_TIMER_GROUP_SUPPORT_XTAL + GPTIMER_CLK_SRC_XTAL, +#endif // SOC_TIMER_GROUP_SUPPORT_XTAL + }; + + // test with various clock sources + for (size_t i = 0; i < sizeof(test_clk_srcs) / sizeof(test_clk_srcs[0]); i++) { + gptimer_config_t timer_config = { + .clk_src = test_clk_srcs[i], + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i])); + } + printf("start timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_start(timers[i])); + } + vTaskDelay(pdMS_TO_TICKS(20)); // 20ms = 20_000 ticks + unsigned long long value = 0; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value)); + TEST_ASSERT_UINT_WITHIN(1000, 20000, value); + } + printf("stop timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_stop(timers[i])); + } + printf("check whether timers have stopped\r\n"); + vTaskDelay(pdMS_TO_TICKS(20)); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value)); + printf("get raw count of gptimer %d: %llu\r\n", i, value); + TEST_ASSERT_UINT_WITHIN(1000, 20000, value); + } + printf("restart timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_start(timers[i])); + } + vTaskDelay(pdMS_TO_TICKS(20)); + printf("stop timers again\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_stop(timers[i])); + } + printf("check whether timers have stopped\r\n"); + vTaskDelay(pdMS_TO_TICKS(20)); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value)); + printf("get raw count of gptimer %d: %llu\r\n", i, value); + TEST_ASSERT_UINT_WITHIN(2000, 40000, value); + } + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } + } +} + +TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_stop_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + xTaskHandle task_handle = (xTaskHandle)user_data; + BaseType_t high_task_wakeup; + gptimer_stop(timer); + esp_rom_printf("count=%lld @alarm\n", edata->count_value); + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("gptimer_stop_on_alarm", "[gptimer]") +{ + xTaskHandle task_handle = xTaskGetCurrentTaskHandle(); + + gptimer_config_t timer_config = { + .resolution_hz = 1 * 1000 * 1000, + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_UP, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i])); + } + + gptimer_event_callbacks_t cbs = { + .on_alarm = test_gptimer_alarm_stop_callback, + }; + gptimer_alarm_config_t alarm_config = {}; + + printf("start timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + alarm_config.alarm_count = 100000 * (i + 1); + TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle)); + TEST_ESP_OK(gptimer_start(timers[i])); + printf("alarm value for gptimer %d: %llu\r\n", i, alarm_config.alarm_count); + } + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + } + + printf("check whether the timers have stopped in the ISR\r\n"); + vTaskDelay(pdMS_TO_TICKS(20)); + unsigned long long value = 0; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value)); + printf("get raw count of gptimer %d: %llu\r\n", i, value); + TEST_ASSERT_UINT_WITHIN(40, 100000 * (i + 1), value); + } + + printf("restart timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + alarm_config.alarm_count = 100000 * (i + 1); + // reset counter value to zero + TEST_ESP_OK(gptimer_set_raw_count(timers[i], 0)); + TEST_ESP_OK(gptimer_start(timers[i])); + } + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + } + printf("check whether the timers have stopped in the ISR\r\n"); + vTaskDelay(pdMS_TO_TICKS(20)); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value)); + printf("get raw count of gptimer %d: %llu\r\n", i, value); + TEST_ASSERT_UINT_WITHIN(40, 100000 * (i + 1), value); + } + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } +} + +TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_reload_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + xTaskHandle task_handle = (xTaskHandle)user_data; + BaseType_t high_task_wakeup; + esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value); + // check if the count value has been reloaded + TEST_ASSERT_UINT_WITHIN(20, 100, edata->count_value); + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("gptimer_auto_reload_on_alarm", "[gptimer]") +{ + xTaskHandle task_handle = xTaskGetCurrentTaskHandle(); + + gptimer_config_t timer_config = { + .resolution_hz = 1 * 1000 * 1000, + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_UP, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i])); + } + + gptimer_event_callbacks_t cbs = { + .on_alarm = test_gptimer_alarm_reload_callback, + }; + gptimer_alarm_config_t alarm_config = { + .reload_count = 100, + .alarm_count = 100000, + .flags.auto_reload_on_alarm = true, + }; + + printf("start timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle)); + TEST_ESP_OK(gptimer_start(timers[i])); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + // delete should fail if timer is not stopped + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, gptimer_del_timer(timers[i])); + TEST_ESP_OK(gptimer_stop(timers[i])); + } + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } +} + +TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_normal_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + xTaskHandle task_handle = (xTaskHandle)user_data; + BaseType_t high_task_wakeup; + esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value); + // check the count value at alarm event + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("gptimer_one_shot_alarm", "[gptimer]") +{ + xTaskHandle task_handle = xTaskGetCurrentTaskHandle(); + + gptimer_config_t timer_config = { + .resolution_hz = 1 * 1000 * 1000, + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_UP, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i])); + } + + gptimer_event_callbacks_t cbs = { + .on_alarm = test_gptimer_alarm_normal_callback, + }; + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = 100000, // 100ms + }; + + printf("start timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle)); + TEST_ESP_OK(gptimer_start(timers[i])); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + // no alarm event should trigger again, as auto-reload is not enabled and alarm value hasn't changed in the isr + TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + // the alarm is stopped, but the counter should still work + uint64_t value = 0; + TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value)); + TEST_ASSERT_UINT_WITHIN(1000, 1100000, value); // 1100000 = 100ms alarm + 1s delay + TEST_ESP_OK(gptimer_stop(timers[i])); + } + + printf("restart timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_start(timers[i])); + // alarm should be triggered immediately as the counter value has across the target alarm value already + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, 0)); + TEST_ESP_OK(gptimer_stop(timers[i])); + } + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } +} + +TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_update_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + xTaskHandle task_handle = (xTaskHandle)user_data; + BaseType_t high_task_wakeup; + esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value); + gptimer_alarm_config_t alarm_config = { + .alarm_count = edata->count_value + 100000, // alarm in next 100ms again + }; + gptimer_set_alarm_action(timer, &alarm_config); + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("gptimer_update_alarm_dynamically", "[gptimer]") +{ + xTaskHandle task_handle = xTaskGetCurrentTaskHandle(); + + gptimer_config_t timer_config = { + .resolution_hz = 1 * 1000 * 1000, + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_UP, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i])); + } + + gptimer_event_callbacks_t cbs = { + .on_alarm = test_gptimer_alarm_update_callback, + }; + gptimer_alarm_config_t alarm_config = { + .alarm_count = 100000, // initial alarm count, 100ms + }; + printf("start timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle)); + TEST_ESP_OK(gptimer_start(timers[i])); + // check the alarm event for multiple times + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + TEST_ESP_OK(gptimer_stop(timers[i])); + // check there won't be more interrupts triggered than expected + TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + } + + printf("restart timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_start(timers[i])); + // check the alarm event for multiple times + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + TEST_ESP_OK(gptimer_stop(timers[i])); + // check there won't be more interrupts triggered than expected + TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500))); + } + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } +} + +TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_count_down_reload_alarm_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + xTaskHandle task_handle = (xTaskHandle)user_data; + BaseType_t high_task_wakeup; + esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value); + // check if the count value has been reloaded + TEST_ASSERT_UINT_WITHIN(20, 200000, edata->count_value); + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("gptimer_count_down_reload", "[gptimer]") +{ + xTaskHandle task_handle = xTaskGetCurrentTaskHandle(); + + gptimer_config_t timer_config = { + .resolution_hz = 1 * 1000 * 1000, + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_DOWN, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i])); + TEST_ESP_OK(gptimer_set_raw_count(timers[i], 200000)); + } + + gptimer_event_callbacks_t cbs = { + .on_alarm = test_gptimer_count_down_reload_alarm_callback, + }; + gptimer_alarm_config_t alarm_config = { + .reload_count = 200000, // 200ms + .alarm_count = 0, + .flags.auto_reload_on_alarm = true, + }; + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle)); + TEST_ESP_OK(gptimer_start(timers[i])); + // check twice, as it's a period event + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ESP_OK(gptimer_stop(timers[i])); + } + + printf("restart gptimer with previous configuration\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_start(timers[i])); + // check twice, as it's a period event + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ESP_OK(gptimer_stop(timers[i])); + } + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } +} + +TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_overflow_reload_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + xTaskHandle task_handle = (xTaskHandle)user_data; + BaseType_t high_task_wakeup; + // Note: esp_rom_printf can't print value with 64 bit length, so the following print result is meaningless, but as an incidator for test that the alarm has fired + esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value); + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("gptimer_overflow", "[gptimer]") +{ + xTaskHandle task_handle = xTaskGetCurrentTaskHandle(); + + gptimer_config_t timer_config = { + .resolution_hz = 1 * 1000 * 1000, + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_UP, + }; + gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS]; + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i])); + } +#if SOC_TIMER_GROUP_COUNTER_BIT_WIDTH == 64 + uint64_t reload_at = UINT64_MAX - 100000; +#else + uint64_t reload_at = (1ULL << SOC_TIMER_GROUP_COUNTER_BIT_WIDTH) - 100000; +#endif + gptimer_event_callbacks_t cbs = { + .on_alarm = test_gptimer_overflow_reload_callback, + }; + gptimer_alarm_config_t alarm_config = { + .reload_count = reload_at, + .alarm_count = 100000, // 100ms + .flags.auto_reload_on_alarm = true, + }; + // The counter should start from [COUNTER_MAX-100000] and overflows to [0] and continue, then reached to alarm value [100000], reloaded to [COUNTER_MAX-100000] automatically + // thus the period should be 200ms + printf("start timers\r\n"); + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle)); + // we start from the reload value + TEST_ESP_OK(gptimer_set_raw_count(timers[i], reload_at)); + TEST_ESP_OK(gptimer_start(timers[i])); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(400))); + TEST_ESP_OK(gptimer_stop(timers[i])); + } + + for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) { + TEST_ESP_OK(gptimer_del_timer(timers[i])); + } +} diff --git a/components/driver/test_apps/gptimer/main/test_gptimer_iram.c b/components/driver/test_apps/gptimer/main/test_gptimer_iram.c new file mode 100644 index 0000000000..cfbeb1bede --- /dev/null +++ b/components/driver/test_apps/gptimer/main/test_gptimer_iram.c @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "unity.h" +#include "driver/gptimer.h" +#include "esp_spi_flash.h" +#include "soc/soc_caps.h" + +void test_app_include_gptimer_iram(void) +{ +} + +#if CONFIG_GPTIMER_ISR_IRAM_SAFE + +typedef struct { + size_t buf_size; + uint8_t *buf; + size_t flash_addr; + size_t repeat_count; + SemaphoreHandle_t done_sem; +} read_task_arg_t; + +typedef struct { + size_t delay_time_us; + size_t repeat_count; +} block_task_arg_t; + +static bool IRAM_ATTR on_gptimer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) +{ + block_task_arg_t *arg = (block_task_arg_t *)user_ctx; + esp_rom_delay_us(arg->delay_time_us); + arg->repeat_count++; + return false; +} + +static void flash_read_task(void *varg) +{ + read_task_arg_t *arg = (read_task_arg_t *)varg; + for (size_t i = 0; i < arg->repeat_count; i++) { + TEST_ESP_OK(spi_flash_read(arg->flash_addr, arg->buf, arg->buf_size)); + } + xSemaphoreGive(arg->done_sem); + vTaskDelete(NULL); +} + +TEST_CASE("gptimer_iram_interrupt_safe", "[gptimer]") +{ + gptimer_handle_t gptimer = NULL; + const size_t size = 128; + uint8_t *buf = malloc(size); + TEST_ASSERT_NOT_NULL(buf); + SemaphoreHandle_t done_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(done_sem); + read_task_arg_t read_arg = { + .buf_size = size, + .buf = buf, + .flash_addr = 0, + .repeat_count = 1000, + .done_sem = done_sem, + }; + + block_task_arg_t block_arg = { + .repeat_count = 0, + .delay_time_us = 100, + }; + + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_APB, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + gptimer_event_callbacks_t cbs = { + .on_alarm = on_gptimer_alarm_cb, + }; + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = 120, + .flags.auto_reload_on_alarm = true, + }; + TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config)); + TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, &block_arg)); + TEST_ESP_OK(gptimer_start(gptimer)); + + xTaskCreatePinnedToCore(flash_read_task, "read_flash", 2048, &read_arg, 3, NULL, portNUM_PROCESSORS - 1); + // wait for task done + xSemaphoreTake(done_sem, portMAX_DELAY); + printf("alarm callback runs %d times\r\n", block_arg.repeat_count); + TEST_ASSERT_GREATER_THAN(1000, block_arg.repeat_count); + // delete gptimer + TEST_ESP_OK(gptimer_stop(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); + vSemaphoreDelete(done_sem); + free(buf); + // leave time for IDLE task to recycle deleted task + vTaskDelay(2); +} + +#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE diff --git a/components/driver/test_apps/gptimer/sdkconfig.ci.iram_safe b/components/driver/test_apps/gptimer/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..3979b28af4 --- /dev/null +++ b/components/driver/test_apps/gptimer/sdkconfig.ci.iram_safe @@ -0,0 +1,5 @@ +CONFIG_COMPILER_DUMP_RTL_FILES=y +CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y +CONFIG_GPTIMER_ISR_IRAM_SAFE=y +# disable log as most of log access rodata string on error, causing RTL check failure +CONFIG_LOG_DEFAULT_LEVEL_NONE=y diff --git a/components/driver/test_apps/gptimer/sdkconfig.ci.release b/components/driver/test_apps/gptimer/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/driver/test_apps/gptimer/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/driver/test_apps/gptimer/sdkconfig.defaults b/components/driver/test_apps/gptimer/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/gptimer/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/driver/timer.c b/components/driver/timer_legacy.c similarity index 97% rename from components/driver/timer.c rename to components/driver/timer_legacy.c index b9cc2e1a81..1e8f3937cf 100644 --- a/components/driver/timer.c +++ b/components/driver/timer_legacy.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,14 +10,14 @@ #include "esp_check.h" #include "esp_intr_alloc.h" #include "freertos/FreeRTOS.h" -#include "driver/timer.h" -#include "esp_private/periph_ctrl.h" +#include "driver/timer_types_legacy.h" #include "hal/timer_hal.h" #include "hal/timer_ll.h" #include "hal/check.h" #include "soc/timer_periph.h" #include "soc/rtc.h" #include "soc/timer_group_reg.h" +#include "esp_private/periph_ctrl.h" static const char *TIMER_TAG = "timer_group"; @@ -230,6 +230,42 @@ static void IRAM_ATTR timer_isr_default(void *arg) } } +esp_err_t timer_enable_intr(timer_group_t group_num, timer_idx_t timer_num) +{ + ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); + ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR); + ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR); + TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]); + timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), true); + TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]); + return ESP_OK; +} + +esp_err_t timer_disable_intr(timer_group_t group_num, timer_idx_t timer_num) +{ + ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); + ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR); + ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR); + TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]); + timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), false); + TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]); + return ESP_OK; +} + +esp_err_t timer_isr_register(timer_group_t group_num, timer_idx_t timer_num, + void (*fn)(void *), void *arg, int intr_alloc_flags, timer_isr_handle_t *handle) +{ + ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); + ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR); + ESP_RETURN_ON_FALSE(fn != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_PARAM_ADDR_ERROR); + ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR); + timer_hal_context_t *hal = &p_timer_obj[group_num][timer_num]->hal; + return esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_num].timer_irq_id[timer_num], + intr_alloc_flags, + (uint32_t)timer_ll_get_intr_status_reg(hal->dev), + TIMER_LL_EVENT_ALARM(timer_num), fn, arg, handle); +} + esp_err_t timer_isr_callback_add(timer_group_t group_num, timer_idx_t timer_num, timer_isr_t isr_handler, void *args, int intr_alloc_flags) { ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); @@ -261,20 +297,6 @@ esp_err_t timer_isr_callback_remove(timer_group_t group_num, timer_idx_t timer_n return ESP_OK; } -esp_err_t timer_isr_register(timer_group_t group_num, timer_idx_t timer_num, - void (*fn)(void *), void *arg, int intr_alloc_flags, timer_isr_handle_t *handle) -{ - ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); - ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR); - ESP_RETURN_ON_FALSE(fn != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_PARAM_ADDR_ERROR); - ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR); - timer_hal_context_t *hal = &p_timer_obj[group_num][timer_num]->hal; - return esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_num].timer_irq_id[timer_num], - intr_alloc_flags, - (uint32_t)timer_ll_get_intr_status_reg(hal->dev), - TIMER_LL_EVENT_ALARM(timer_num), fn, arg, handle); -} - esp_err_t timer_init(timer_group_t group_num, timer_idx_t timer_num, const timer_config_t *config) { ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); @@ -369,28 +391,6 @@ esp_err_t timer_group_intr_disable(timer_group_t group_num, timer_intr_t disable return ESP_OK; } -esp_err_t timer_enable_intr(timer_group_t group_num, timer_idx_t timer_num) -{ - ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); - ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR); - ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR); - TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]); - timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), true); - TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]); - return ESP_OK; -} - -esp_err_t timer_disable_intr(timer_group_t group_num, timer_idx_t timer_num) -{ - ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR); - ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR); - ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR); - TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]); - timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), false); - TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]); - return ESP_OK; -} - /* This function is deprecated */ timer_intr_t IRAM_ATTR timer_group_intr_get_in_isr(timer_group_t group_num) { @@ -411,17 +411,17 @@ uint32_t IRAM_ATTR timer_group_get_intr_status_in_isr(timer_group_t group_num) return intr_status; } +void IRAM_ATTR timer_group_clr_intr_status_in_isr(timer_group_t group_num, timer_idx_t timer_num) +{ + timer_ll_clear_intr_status(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num)); +} + /* This function is deprecated */ void IRAM_ATTR timer_group_intr_clr_in_isr(timer_group_t group_num, timer_idx_t timer_num) { timer_group_clr_intr_status_in_isr(group_num, timer_num); } -void IRAM_ATTR timer_group_clr_intr_status_in_isr(timer_group_t group_num, timer_idx_t timer_num) -{ - timer_ll_clear_intr_status(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num)); -} - void IRAM_ATTR timer_group_enable_alarm_in_isr(timer_group_t group_num, timer_idx_t timer_num) { timer_ll_enable_alarm(p_timer_obj[group_num][timer_num]->hal.dev, timer_num, true); @@ -474,9 +474,17 @@ esp_err_t IRAM_ATTR timer_spinlock_give(timer_group_t group_num) return ESP_OK; } - -STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_T0, TIMG_T0_INT_CLR); -#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1 -STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_T1, TIMG_T1_INT_CLR); -#endif -STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_WDT, TIMG_WDT_INT_CLR); +/** + * @brief This function will be called during start up, to check that this legacy timer group driver is not running along with the gptimer driver + */ +__attribute__((constructor)) +static void check_legacy_timer_driver_conflict(void) +{ + extern int timer_group_driver_init_count; + timer_group_driver_init_count++; + if (timer_group_driver_init_count > 1) { + ESP_EARLY_LOGE(TIMER_TAG, "CONFLICT! The legacy timer group driver can't work along with the gptimer driver"); + abort(); + } + ESP_EARLY_LOGW(TIMER_TAG, "legacy timer group driver is deprecated, please migrate to use driver/gptimer.h"); +} diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index cba0a93167..fd05f07fd8 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -14,6 +14,7 @@ if(NOT BOOTLOADER_BUILD) "spi_slave_hal.c" "spi_slave_hal_iram.c" "timer_hal.c" + "timer_hal_iram.c" "ledc_hal.c" "ledc_hal_iram.c" "i2c_hal.c" diff --git a/components/hal/linker.lf b/components/hal/linker.lf index 5693f4a754..23f3bc95b1 100644 --- a/components/hal/linker.lf +++ b/components/hal/linker.lf @@ -22,3 +22,5 @@ entries: if IDF_TARGET_ESP32 = n: spi_flash_hal_gpspi (noflash) systimer_hal (noflash) + if GPTIMER_CTRL_FUNC_IN_IRAM = y: + timer_hal_iram (noflash) diff --git a/components/hal/timer_hal.c b/components/hal/timer_hal.c index d7efa52542..d7bbd387be 100644 --- a/components/hal/timer_hal.c +++ b/components/hal/timer_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,13 +12,3 @@ void timer_hal_init(timer_hal_context_t *hal, uint32_t group_num, uint32_t timer hal->dev = TIMER_LL_GET_HW(group_num); hal->timer_id = timer_num; } - -void timer_hal_set_counter_value(timer_hal_context_t *hal, uint64_t load_val) -{ - // save current reload value - uint64_t old_reload = timer_ll_get_reload_value(hal->dev, hal->timer_id); - timer_ll_set_reload_value(hal->dev, hal->timer_id, load_val); - timer_ll_trigger_soft_reload(hal->dev, hal->timer_id); - // restore the previous reload value - timer_ll_set_reload_value(hal->dev, hal->timer_id, old_reload); -} diff --git a/components/hal/timer_hal_iram.c b/components/hal/timer_hal_iram.c new file mode 100644 index 0000000000..da75d78db5 --- /dev/null +++ b/components/hal/timer_hal_iram.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "hal/timer_hal.h" +#include "hal/timer_ll.h" + +void timer_hal_set_counter_value(timer_hal_context_t *hal, uint64_t load_val) +{ + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // - `timer_ll_set_reload_value()` will only indicate the `reload_value` + // - `timer_ll_set_reload_value()` + ``timer_ll_trigger_soft_reload()` can update the HW counter value by software + // Therefore, after updating the HW counter value, we need to restore the previous `reload_value`. + // Attention: The following process should be protected by a lock in the driver layer. + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // save current reload value + uint64_t old_reload = timer_ll_get_reload_value(hal->dev, hal->timer_id); + timer_ll_set_reload_value(hal->dev, hal->timer_id, load_val); + timer_ll_trigger_soft_reload(hal->dev, hal->timer_id); + // restore the previous reload value + timer_ll_set_reload_value(hal->dev, hal->timer_id, old_reload); +}