diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index 2751184b6035..07282a70fb78 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -3,7 +3,7 @@ idf_build_get_property(target IDF_TARGET) set(srcs "gpio/gpio.c" "gpio/rtc_io.c" - "gptimer.c" + "gptimer/gptimer.c" "sdspi_crc.c" "sdspi_host.c" "sdspi_transaction.c" @@ -25,6 +25,10 @@ if(CONFIG_SOC_LEDC_SUPPORTED) list(APPEND srcs "ledc.c") endif() +if(CONFIG_SOC_TIMER_SUPPORT_ETM) + list(APPEND srcs "gptimer/gptimer_etm.c") +endif() + if(CONFIG_SOC_I2C_SUPPORTED) list(APPEND srcs "i2c.c") endif() @@ -65,6 +69,10 @@ if(CONFIG_SOC_PCNT_SUPPORTED) list(APPEND srcs "pulse_cnt.c" "deprecated/pcnt_legacy.c") endif() +if(CONFIG_SOC_GPIO_SUPPORT_ETM) + list(APPEND srcs "gpio/gpio_etm.c") +endif() + if(CONFIG_SOC_SDMMC_HOST_SUPPORTED) list(APPEND srcs "sdmmc_transaction.c" "sdmmc_host.c") endif() diff --git a/components/driver/gpio/gpio_etm.c b/components/driver/gpio/gpio_etm.c new file mode 100644 index 000000000000..aa2de292ddab --- /dev/null +++ b/components/driver/gpio/gpio_etm.c @@ -0,0 +1,305 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_ETM_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "freertos/FreeRTOS.h" +#include "driver/gpio.h" +#include "driver/gpio_etm.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "soc/soc_caps.h" +#include "hal/gpio_ll.h" +#include "hal/gpio_etm_ll.h" +#include "esp_private/etm_interface.h" + +#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +static const char *TAG = "gpio-etm"; + +typedef struct gpio_etm_task_t gpio_etm_task_t; +typedef struct gpio_etm_event_t gpio_etm_event_t; + +typedef struct gpio_etm_group_t { + portMUX_TYPE spinlock; + gpio_etm_dev_t *dev; + gpio_etm_task_t *tasks[SOC_GPIO_ETM_TASKS_PER_GROUP]; + gpio_etm_event_t *events[SOC_GPIO_ETM_EVENTS_PER_GROUP]; +} gpio_etm_group_t; + +struct gpio_etm_event_t { + esp_etm_event_t base; + int chan_id; + gpio_etm_group_t *group; +}; + +struct gpio_etm_task_t { + esp_etm_task_t base; + int chan_id; + gpio_etm_group_t *group; + size_t num_of_gpios; // record the number of GPIOs that are bound to the etm task +}; + +static gpio_etm_group_t s_gpio_etm_group = { + .dev = &GPIO_ETM, + .spinlock = portMUX_INITIALIZER_UNLOCKED, +}; + +static esp_err_t gpio_etm_event_register_to_group(gpio_etm_event_t *event) +{ + gpio_etm_group_t *group = &s_gpio_etm_group; + int chan_id = -1; + // loop to search free one in the group + portENTER_CRITICAL(&group->spinlock); + for (int j = 0; j < SOC_GPIO_ETM_EVENTS_PER_GROUP; j++) { + if (!group->events[j]) { + chan_id = j; + group->events[j] = event; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + + ESP_RETURN_ON_FALSE(chan_id != -1, ESP_ERR_NOT_FOUND, TAG, "no free event channel"); + event->group = group; + event->chan_id = chan_id; + return ESP_OK; +} + +static esp_err_t gpio_etm_task_register_to_group(gpio_etm_task_t *task) +{ + gpio_etm_group_t *group = &s_gpio_etm_group; + int chan_id = -1; + // loop to search free one in the group + portENTER_CRITICAL(&group->spinlock); + for (int j = 0; j < SOC_GPIO_ETM_TASKS_PER_GROUP; j++) { + if (!group->tasks[j]) { + chan_id = j; + group->tasks[j] = task; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + + ESP_RETURN_ON_FALSE(chan_id != -1, ESP_ERR_NOT_FOUND, TAG, "no free task channel"); + task->group = group; + task->chan_id = chan_id; + return ESP_OK; +} + +static void gpio_etm_event_unregister_from_group(gpio_etm_event_t *event) +{ + gpio_etm_group_t *group = event->group; + int chan_id = event->chan_id; + portENTER_CRITICAL(&group->spinlock); + group->events[chan_id] = NULL; + portEXIT_CRITICAL(&group->spinlock); +} + +static void gpio_etm_task_unregister_from_group(gpio_etm_task_t *task) +{ + gpio_etm_group_t *group = task->group; + int chan_id = task->chan_id; + portENTER_CRITICAL(&group->spinlock); + group->tasks[chan_id] = NULL; + portEXIT_CRITICAL(&group->spinlock); +} + +static esp_err_t gpio_etm_event_destroy(gpio_etm_event_t *event) +{ + if (event->group) { + gpio_etm_event_unregister_from_group(event); + } + free(event); + return ESP_OK; +} + +static esp_err_t gpio_etm_task_destroy(gpio_etm_task_t *task) +{ + if (task->group) { + gpio_etm_task_unregister_from_group(task); + } + free(task); + return ESP_OK; +} + +static esp_err_t gpio_del_etm_event(esp_etm_event_t *event) +{ + gpio_etm_event_t *gpio_event = __containerof(event, gpio_etm_event_t, base); + gpio_etm_group_t *group = gpio_event->group; + // disable event channel + gpio_ll_etm_enable_event_channel(group->dev, gpio_event->chan_id, false); + gpio_etm_event_destroy(gpio_event); + return ESP_OK; +} + +static esp_err_t gpio_del_etm_task(esp_etm_task_t *task) +{ + gpio_etm_task_t *gpio_task = __containerof(task, gpio_etm_task_t, base); + // make sure user has called `gpio_etm_task_rm_gpio` to clean the etm task channel + ESP_RETURN_ON_FALSE(gpio_task->num_of_gpios == 0, ESP_ERR_INVALID_STATE, TAG, "some GPIO till bounded to the etm task"); + gpio_etm_task_destroy(gpio_task); + return ESP_OK; +} + +esp_err_t gpio_new_etm_event(const gpio_etm_event_config_t *config, esp_etm_event_handle_t *ret_event) +{ +#if CONFIG_ETM_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + gpio_etm_event_t *event = NULL; + ESP_GOTO_ON_FALSE(config && ret_event, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + + event = heap_caps_calloc(1, sizeof(gpio_etm_event_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(event, ESP_ERR_NO_MEM, err, TAG, "no mem for event channel"); + // register the event channel to the group + ESP_GOTO_ON_ERROR(gpio_etm_event_register_to_group(event), err, TAG, "register event channel to group failed"); + int chan_id = event->chan_id; + + uint32_t event_id = 0; + switch (config->edge) { + case GPIO_ETM_EVENT_EDGE_ANY: + event_id = GPIO_LL_ETM_EVENT_ID_ANY_EDGE(chan_id); + break; + case GPIO_ETM_EVENT_EDGE_POS: + event_id = GPIO_LL_ETM_EVENT_ID_POS_EDGE(chan_id); + break; + case GPIO_ETM_EVENT_EDGE_NEG: + event_id = GPIO_LL_ETM_EVENT_ID_NEG_EDGE(chan_id); + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid edge"); + } + + event->base.del = gpio_del_etm_event; + event->base.event_id = event_id; + event->base.trig_periph = ETM_TRIG_PERIPH_GPIO; + ESP_LOGD(TAG, "new event @%p, event_id=%"PRIu32", chan_id=%d", event, event_id, chan_id); + *ret_event = &event->base; + return ESP_OK; + +err: + if (event) { + gpio_etm_event_destroy(event); + } + return ret; +} + +esp_err_t gpio_new_etm_task(const gpio_etm_task_config_t *config, esp_etm_task_handle_t *ret_task) +{ +#if CONFIG_ETM_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + gpio_etm_task_t *task = NULL; + ESP_GOTO_ON_FALSE(config && ret_task, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + + task = heap_caps_calloc(1, sizeof(gpio_etm_task_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(task, ESP_ERR_NO_MEM, err, TAG, "no mem for task channel"); + // register the task channel to the group + ESP_GOTO_ON_ERROR(gpio_etm_task_register_to_group(task), err, TAG, "register task channel to group failed"); + int chan_id = task->chan_id; + + uint32_t task_id = 0; + switch (config->action) { + case GPIO_ETM_TASK_ACTION_SET: + task_id = GPIO_LL_ETM_TASK_ID_SET(chan_id); + break; + case GPIO_ETM_TASK_ACTION_CLR: + task_id = GPIO_LL_ETM_TASK_ID_CLR(chan_id); + break; + case GPIO_ETM_TASK_ACTION_TOG: + task_id = GPIO_LL_ETM_TASK_ID_TOG(chan_id); + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid action"); + } + + task->base.del = gpio_del_etm_task; + task->base.task_id = task_id; + task->base.trig_periph = ETM_TRIG_PERIPH_GPIO; + ESP_LOGD(TAG, "new task @%p, task_id=%"PRIu32", chan_id=%d", task, task_id, chan_id); + *ret_task = &task->base; + return ESP_OK; + +err: + if (task) { + gpio_etm_task_destroy(task); + } + return ret; +} + +esp_err_t gpio_etm_event_bind_gpio(esp_etm_event_handle_t event, int gpio_num) +{ + ESP_RETURN_ON_FALSE(event, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(event->trig_periph == ETM_TRIG_PERIPH_GPIO, ESP_ERR_INVALID_ARG, TAG, "not a gpio etm event"); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "gpio is not input capable"); + gpio_etm_event_t *gpio_event = __containerof(event, gpio_etm_event_t, base); + gpio_etm_group_t *group = gpio_event->group; + // disable gpio etm event channel first + gpio_ll_etm_enable_event_channel(group->dev, gpio_event->chan_id, false); + // then set the gpio number + gpio_ll_etm_event_channel_set_gpio(group->dev, gpio_event->chan_id, gpio_num); + // enable gpio etm event channel again + gpio_ll_etm_enable_event_channel(group->dev, gpio_event->chan_id, true); + return ESP_OK; +} + +esp_err_t gpio_etm_task_add_gpio(esp_etm_task_handle_t task, int gpio_num) +{ + ESP_RETURN_ON_FALSE(task, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(task->trig_periph == ETM_TRIG_PERIPH_GPIO, ESP_ERR_INVALID_ARG, TAG, "not a gpio etm task"); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "gpio is not output capable"); + gpio_etm_task_t *gpio_task = __containerof(task, gpio_etm_task_t, base); + gpio_etm_group_t *group = gpio_task->group; + bool gpio_not_enabled = true; + // use spinlock as this function may be called with different task object in different threads + // and the gpio_num might reside in the same register + portENTER_CRITICAL(&group->spinlock); + // check if the gpio has been enabled + if (!gpio_ll_etm_is_task_gpio_enabled(group->dev, gpio_num)) { + gpio_ll_etm_gpio_set_task_channel(group->dev, gpio_num, gpio_task->chan_id); + gpio_ll_etm_enable_task_gpio(group->dev, gpio_num, true); + } else { + gpio_not_enabled = false; + } + portEXIT_CRITICAL(&group->spinlock); + ESP_RETURN_ON_FALSE(gpio_not_enabled, ESP_ERR_INVALID_STATE, TAG, "gpio already enabled by other task channel"); + gpio_task->num_of_gpios++; + return ESP_OK; +} + +esp_err_t gpio_etm_task_rm_gpio(esp_etm_task_handle_t task, int gpio_num) +{ + ESP_RETURN_ON_FALSE(task, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "gpio is not output capable"); + gpio_etm_task_t *gpio_task = __containerof(task, gpio_etm_task_t, base); + gpio_etm_group_t *group = gpio_task->group; + bool gpio_enabled_by_this_task = true; + // use spinlock as this function may be called with different task object in different threads + // and the gpio_num might reside in the same register + portENTER_CRITICAL(&group->spinlock); + // check if the gpio is managed by this etm task channel + if (gpio_ll_etm_is_task_gpio_enabled(group->dev, gpio_num) && + (gpio_ll_etm_gpio_get_task_channel(group->dev, gpio_num) == gpio_task->chan_id)) { + gpio_ll_etm_enable_task_gpio(group->dev, gpio_num, false); + } else { + gpio_enabled_by_this_task = false; + } + portEXIT_CRITICAL(&group->spinlock); + ESP_RETURN_ON_FALSE(gpio_enabled_by_this_task, ESP_ERR_INVALID_STATE, TAG, "gpio is not enabled by this task channel"); + gpio_task->num_of_gpios--; + return ESP_OK; +} diff --git a/components/driver/gptimer.c b/components/driver/gptimer/gptimer.c similarity index 91% rename from components/driver/gptimer.c rename to components/driver/gptimer/gptimer.c index a278ee38880d..9477d0e4f420 100644 --- a/components/driver/gptimer.c +++ b/components/driver/gptimer/gptimer.c @@ -15,8 +15,6 @@ #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" @@ -28,70 +26,15 @@ #include "esp_memory_utils.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 || CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM -#define GPTIMER_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) -#else -#define GPTIMER_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT -#endif - -#if CONFIG_GPTIMER_ISR_IRAM_SAFE -#define GPTIMER_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) -#else -#define GPTIMER_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED -#endif - -#define GPTIMER_PM_LOCK_NAME_LEN_MAX 16 +#include "gptimer_priv.h" 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 { +typedef 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_INIT, - GPTIMER_FSM_ENABLE, -} gptimer_fsm_t; - -struct gptimer_t { - gptimer_group_t *group; - int timer_id; - uint32_t resolution_hz; - uint64_t reload_count; - uint64_t alarm_count; - gptimer_count_direction_t direction; - timer_hal_context_t hal; - gptimer_fsm_t fsm; - intr_handle_t intr; - portMUX_TYPE spinlock; // to protect per-timer resources concurrent accessed by task and ISR handler - gptimer_alarm_cb_t on_alarm; - void *user_ctx; - gptimer_clock_source_t clk_src; - 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_platform_t; // gptimer driver platform, it's always a singleton static gptimer_platform_t s_platform; @@ -239,6 +182,16 @@ esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, unsigned long long *valu return ESP_OK; } +esp_err_t gptimer_get_captured_count(gptimer_handle_t timer, uint64_t *value) +{ + ESP_RETURN_ON_FALSE_ISR(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; diff --git a/components/driver/gptimer/gptimer_etm.c b/components/driver/gptimer/gptimer_etm.c new file mode 100644 index 000000000000..1147f91f4359 --- /dev/null +++ b/components/driver/gptimer/gptimer_etm.c @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "driver/gptimer.h" +#include "gptimer_priv.h" +#include "hal/timer_ll.h" +#include "esp_private/etm_interface.h" + +#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +static const char *TAG = "gptimer-etm"; + +static esp_err_t gptimer_del_etm_event(esp_etm_event_t *event) +{ + free(event); + return ESP_OK; +} + +static esp_err_t gptimer_del_etm_task(esp_etm_task_t *task) +{ + free(task); + return ESP_OK; +} + +esp_err_t gptimer_new_etm_event(gptimer_handle_t timer, gptimer_etm_event_type_t event_type, esp_etm_event_handle_t *out_event) +{ + esp_etm_event_t *event = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(timer && out_event, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(event_type < GPTIMER_ETM_EVENT_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid event type"); + event = heap_caps_calloc(1, sizeof(esp_etm_event_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(event, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM event"); + + gptimer_group_t *group = timer->group; + int group_id = group->group_id; + int timer_id = timer->timer_id; + uint32_t event_id = TIMER_LL_ETM_EVENT_TABLE(group_id, timer_id, event_type); + ESP_GOTO_ON_FALSE(event_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported event type"); + + // fill the ETM event object + event->event_id = event_id; + event->trig_periph = ETM_TRIG_PERIPH_GPTIMER; + event->del = gptimer_del_etm_event; + *out_event = event; + return ESP_OK; + +err: + if (event) { + gptimer_del_etm_event(event); + } + return ret; +} + +esp_err_t gptimer_new_etm_task(gptimer_handle_t timer, gptimer_etm_task_type_t task_type, esp_etm_task_handle_t *out_task) +{ + esp_etm_task_t *task = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(timer && out_task, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(task_type < GPTIMER_ETM_TASK_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid task type"); + task = heap_caps_calloc(1, sizeof(esp_etm_task_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(task, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM task"); + + gptimer_group_t *group = timer->group; + int group_id = group->group_id; + int timer_id = timer->timer_id; + uint32_t task_id = TIMER_LL_ETM_TASK_TABLE(group_id, timer_id, task_type); + ESP_GOTO_ON_FALSE(task_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported task type"); + + // fill the ETM task object + task->task_id = task_id; + task->trig_periph = ETM_TRIG_PERIPH_GPTIMER; + task->del = gptimer_del_etm_task; + *out_task = task; + return ESP_OK; + +err: + if (task) { + gptimer_del_etm_task(task); + } + return ret; +} diff --git a/components/driver/gptimer/gptimer_priv.h b/components/driver/gptimer/gptimer_priv.h new file mode 100644 index 000000000000..4cabb2032f34 --- /dev/null +++ b/components/driver/gptimer/gptimer_priv.h @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "esp_heap_caps.h" +#include "esp_pm.h" +#include "soc/soc_caps.h" +#include "hal/timer_hal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// 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 || CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM +#define GPTIMER_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define GPTIMER_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +#if CONFIG_GPTIMER_ISR_IRAM_SAFE +#define GPTIMER_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) +#else +#define GPTIMER_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED +#endif + +#define GPTIMER_PM_LOCK_NAME_LEN_MAX 16 + +typedef struct gptimer_t gptimer_t; + +typedef 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]; +} gptimer_group_t; + +typedef enum { + GPTIMER_FSM_INIT, + GPTIMER_FSM_ENABLE, +} gptimer_fsm_t; + +struct gptimer_t { + gptimer_group_t *group; + int timer_id; + uint32_t resolution_hz; + uint64_t reload_count; + uint64_t alarm_count; + gptimer_count_direction_t direction; + timer_hal_context_t hal; + gptimer_fsm_t fsm; + intr_handle_t intr; + portMUX_TYPE spinlock; // to protect per-timer resources concurrent accessed by task and ISR handler + gptimer_alarm_cb_t on_alarm; + void *user_ctx; + gptimer_clock_source_t clk_src; + 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; +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/gpio.h b/components/driver/include/driver/gpio.h index 2b30018e08dc..4c2d971c9d2e 100644 --- a/components/driver/include/driver/gpio.h +++ b/components/driver/include/driver/gpio.h @@ -13,6 +13,7 @@ #include "soc/soc_caps.h" #include "hal/gpio_types.h" #include "esp_rom_gpio.h" +#include "driver/gpio_etm.h" #ifdef __cplusplus extern "C" { @@ -150,7 +151,7 @@ esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level); int gpio_get_level(gpio_num_t gpio_num); /** - * @brief GPIO set direction + * @brief GPIO set direction * * Configure GPIO direction,such as output_only,input_only,output_and_input * @@ -480,7 +481,7 @@ esp_err_t gpio_sleep_sel_en(gpio_num_t gpio_num); esp_err_t gpio_sleep_sel_dis(gpio_num_t gpio_num); /** - * @brief GPIO set direction at sleep + * @brief GPIO set direction at sleep * * Configure GPIO direction,such as output_only,input_only,output_and_input * diff --git a/components/driver/include/driver/gpio_etm.h b/components/driver/include/driver/gpio_etm.h new file mode 100644 index 000000000000..e7acf722d54c --- /dev/null +++ b/components/driver/include/driver/gpio_etm.h @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "esp_etm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief GPIO edges that can be used as ETM event + */ +typedef enum { + GPIO_ETM_EVENT_EDGE_POS, /*!< A rising edge on the GPIO will generate an ETM event signal */ + GPIO_ETM_EVENT_EDGE_NEG, /*!< A falling edge on the GPIO will generate an ETM event signal */ + GPIO_ETM_EVENT_EDGE_ANY, /*!< Any edge on the GPIO can generate an ETM event signal */ +} gpio_etm_event_edge_t; + +/** + * @brief GPIO ETM event configuration + */ +typedef struct { + gpio_etm_event_edge_t edge; /*!< Which kind of edge can trigger the ETM event module */ +} gpio_etm_event_config_t; + +/** + * @brief Create an ETM event object for the GPIO peripheral + * + * @note The created ETM event object can be deleted later by calling `esp_etm_del_event` + * @note The newly created ETM event object is not bind to any GPIO, you need to call `gpio_etm_event_bind_gpio` to bind the wanted GPIO + * + * @param[in] config GPIO ETM event configuration + * @param[out] ret_event Returned ETM event handle + * @return + * - ESP_OK: Create ETM event successfully + * - ESP_ERR_INVALID_ARG: Create ETM event failed because of invalid argument + * - ESP_ERR_NO_MEM: Create ETM event failed because of out of memory + * - ESP_ERR_NOT_FOUND: Create ETM event failed because all events are used up and no more free one + * - ESP_FAIL: Create ETM event failed because of other reasons + */ +esp_err_t gpio_new_etm_event(const gpio_etm_event_config_t *config, esp_etm_event_handle_t *ret_event); + +/** + * @brief Bind the GPIO with the ETM event + * + * @note Calling this function multiple times with different GPIO number can override the previous setting immediately. + * @note Only GPIO ETM object can call this function + * + * @param[in] event ETM event handle that created by `gpio_new_etm_event` + * @param[in] gpio_num GPIO number that can trigger the ETM event + * @return + * - ESP_OK: Set the GPIO for ETM event successfully + * - ESP_ERR_INVALID_ARG: Set the GPIO for ETM event failed because of invalid argument, e.g. GPIO is not input capable, ETM event is not of GPIO type + * - ESP_FAIL: Set the GPIO for ETM event failed because of other reasons + */ +esp_err_t gpio_etm_event_bind_gpio(esp_etm_event_handle_t event, int gpio_num); + +/** + * @brief GPIO actions that can be taken by the ETM task + */ +typedef enum { + GPIO_ETM_TASK_ACTION_SET, /*!< Set the GPIO level to high */ + GPIO_ETM_TASK_ACTION_CLR, /*!< Clear the GPIO level to low */ + GPIO_ETM_TASK_ACTION_TOG, /*!< Toggle the GPIO level */ +} gpio_etm_task_action_t; + +/** + * @brief GPIO ETM task configuration + */ +typedef struct { + gpio_etm_task_action_t action; /*!< Which action to take by the ETM task module */ +} gpio_etm_task_config_t; + +/** + * @brief Create an ETM task object for the GPIO peripheral + * + * @note The created ETM task object can be deleted later by calling `esp_etm_del_task` + * @note The GPIO ETM task works like a container, a newly created ETM task object doesn't have GPIO members to be managed. + * You need to call `gpio_etm_task_add_gpio` to put one or more GPIOs to the container. + * + * @param[in] config GPIO ETM task configuration + * @param[out] ret_task Returned ETM task handle + * @return + * - ESP_OK: Create ETM task successfully + * - ESP_ERR_INVALID_ARG: Create ETM task failed because of invalid argument + * - ESP_ERR_NO_MEM: Create ETM task failed because of out of memory + * - ESP_ERR_NOT_FOUND: Create ETM task failed because all tasks are used up and no more free one + * - ESP_FAIL: Create ETM task failed because of other reasons + */ +esp_err_t gpio_new_etm_task(const gpio_etm_task_config_t *config, esp_etm_task_handle_t *ret_task); + +/** + * @brief Add GPIO to the ETM task. + * + * @note You can call this function multiple times to add more GPIOs + * @note Only GPIO ETM object can call this function + * + * @param[in] task ETM task handle that created by `gpio_new_etm_task` + * @param[in] gpio_num GPIO number that can be controlled by the ETM task + * @return + * - ESP_OK: Add GPIO to the ETM task successfully + * - ESP_ERR_INVALID_ARG: Add GPIO to the ETM task failed because of invalid argument, e.g. GPIO is not output capable, ETM task is not of GPIO type + * - ESP_ERR_INVALID_STATE: Add GPIO to the ETM task failed because the GPIO is used by other ETM task already + * - ESP_FAIL: Add GPIO to the ETM task failed because of other reasons + */ +esp_err_t gpio_etm_task_add_gpio(esp_etm_task_handle_t task, int gpio_num); + +/** + * @brief Remove the GPIO from the ETM task + * + * @note Before deleting the ETM task, you need to remove all the GPIOs from the ETM task by this function + * @note Only GPIO ETM object can call this function + * + * @param[in] task ETM task handle that created by `gpio_new_etm_task` + * @param[in] gpio_num GPIO number that to be remove from the ETM task + * @return + * - ESP_OK: Remove the GPIO from the ETM task successfully + * - ESP_ERR_INVALID_ARG: Remove the GPIO from the ETM task failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Remove the GPIO from the ETM task failed because the GPIO is not controlled by this ETM task + * - ESP_FAIL: Remove the GPIO from the ETM task failed because of other reasons + */ +esp_err_t gpio_etm_task_rm_gpio(esp_etm_task_handle_t task, int gpio_num); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/gptimer.h b/components/driver/include/driver/gptimer.h index e68273443f7f..b24f38ab77f4 100644 --- a/components/driver/include/driver/gptimer.h +++ b/components/driver/include/driver/gptimer.h @@ -9,44 +9,13 @@ #include #include #include "esp_err.h" -#include "hal/timer_types.h" +#include "driver/gptimer_types.h" +#include "driver/gptimer_etm.h" #ifdef __cplusplus extern "C" { #endif -/** - * @brief Type of General Purpose Timer handle - */ -typedef struct gptimer_t *gptimer_handle_t; - -/** - * @brief GPTimer alarm 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_register_event_callbacks` - * @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 - * @note When CONFIG_GPTIMER_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. - */ -typedef struct { - gptimer_alarm_cb_t on_alarm; /*!< Timer alarm callback */ -} gptimer_event_callbacks_t; - /** * @brief General Purpose Timer configuration */ @@ -60,17 +29,6 @@ typedef struct { } flags; /*!< GPTimer config 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; /*!< Alarm config flags*/ -} gptimer_alarm_config_t; - /** * @brief Create a new General Purpose Timer, and return the handle * @@ -135,6 +93,31 @@ esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value); */ esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value); +/** + * @brief Get GPTimer captured count value + * + * @note The capture action can be issued either by external event or by software (see also `gptimer_get_raw_count`). + * @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 captured count value + * @return + * - ESP_OK: Get GPTimer captured count value successfully + * - ESP_ERR_INVALID_ARG: Get GPTimer captured count value failed because of invalid argument + * - ESP_FAIL: Get GPTimer captured count value failed because of other error + */ +esp_err_t gptimer_get_captured_count(gptimer_handle_t timer, uint64_t *value); + +/** + * @brief Group of supported GPTimer callbacks + * @note The callbacks are all running under ISR environment + * @note When CONFIG_GPTIMER_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + */ +typedef struct { + gptimer_alarm_cb_t on_alarm; /*!< Timer alarm callback */ +} gptimer_event_callbacks_t; + /** * @brief Set callbacks for GPTimer * @@ -153,6 +136,17 @@ esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value); */ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data); +/** + * @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; /*!< Alarm config flags*/ +} gptimer_alarm_config_t; + /** * @brief Set alarm event actions for GPTimer. * diff --git a/components/driver/include/driver/gptimer_etm.h b/components/driver/include/driver/gptimer_etm.h new file mode 100644 index 000000000000..506d904f077f --- /dev/null +++ b/components/driver/include/driver/gptimer_etm.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_etm.h" +#include "driver/gptimer_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the ETM event for GPTimer + * + * @note The created ETM event object can be deleted later by calling `esp_etm_del_event` + * + * @param[in] timer Timer handle created by `gptimer_new_timer` + * @param[in] event_type GPTimer ETM event type + * @param[out] out_event Returned ETM event handle + * @return + * - ESP_OK: Get ETM event successfully + * - ESP_ERR_INVALID_ARG: Get ETM event failed because of invalid argument + * - ESP_FAIL: Get ETM event failed because of other error + */ +esp_err_t gptimer_new_etm_event(gptimer_handle_t timer, gptimer_etm_event_type_t event_type, esp_etm_event_handle_t *out_event); + +/** + * @brief Get the ETM task for GPTimer + * + * @note The created ETM task object can be deleted later by calling `esp_etm_del_task` + * + * @param[in] timer Timer handle created by `gptimer_new_timer` + * @param[in] task_type GPTimer ETM task type + * @param[out] out_task Returned ETM task handle + * @return + * - ESP_OK: Get ETM task successfully + * - ESP_ERR_INVALID_ARG: Get ETM task failed because of invalid argument + * - ESP_FAIL: Get ETM task failed because of other error + */ +esp_err_t gptimer_new_etm_task(gptimer_handle_t timer, gptimer_etm_task_type_t task_type, esp_etm_task_handle_t *out_task); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/gptimer_types.h b/components/driver/include/driver/gptimer_types.h new file mode 100644 index 000000000000..4339e9627557 --- /dev/null +++ b/components/driver/include/driver/gptimer_types.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 alarm 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_register_event_callbacks` + * @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); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/linker.lf b/components/driver/linker.lf index 811a40948e2f..8492030bfec4 100644 --- a/components/driver/linker.lf +++ b/components/driver/linker.lf @@ -4,6 +4,7 @@ entries: if GPTIMER_CTRL_FUNC_IN_IRAM = y: gptimer: gptimer_set_raw_count (noflash) gptimer: gptimer_get_raw_count (noflash) + gptimer: gptimer_get_captured_count (noflash) gptimer: gptimer_set_alarm_action (noflash) gptimer: gptimer_start (noflash) gptimer: gptimer_stop (noflash) diff --git a/components/esp_hw_support/.build-test-rules.yml b/components/esp_hw_support/.build-test-rules.yml index 01b0ede9a788..e61b946b796e 100644 --- a/components/esp_hw_support/.build-test-rules.yml +++ b/components/esp_hw_support/.build-test-rules.yml @@ -5,3 +5,7 @@ components/esp_hw_support/test_apps/dma: - if: IDF_TARGET in ["esp32"] temporary: false reason: Neither GDMA nor CPDMA is supported on ESP32 + +components/esp_hw_support/test_apps/etm: + disable: + - if: SOC_ETM_SUPPORTED != 1 diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index 08053a828d08..8bf80f7fa0d5 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -30,15 +30,19 @@ if(NOT BOOTLOADER_BUILD) endif() if(CONFIG_SOC_GDMA_SUPPORTED) - list(APPEND srcs "gdma.c" "port/async_memcpy_impl_gdma.c") + list(APPEND srcs "dma/gdma.c" "dma/async_memcpy_impl_gdma.c") endif() if(CONFIG_SOC_CP_DMA_SUPPORTED) - list(APPEND srcs "port/async_memcpy_impl_cp_dma.c") + list(APPEND srcs "dma/async_memcpy_impl_cp_dma.c") endif() if(CONFIG_SOC_GDMA_SUPPORTED OR CONFIG_SOC_CP_DMA_SUPPORTED) - list(APPEND srcs "esp_async_memcpy.c") + list(APPEND srcs "dma/esp_async_memcpy.c") + endif() + + if(CONFIG_SOC_GDMA_SUPPORT_ETM) + list(APPEND srcs "dma/gdma_etm.c") endif() if(CONFIG_SOC_SYSTIMER_SUPPORTED) @@ -49,6 +53,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "esp_hmac.c") endif() + if(CONFIG_SOC_ETM_SUPPORTED) + list(APPEND srcs "esp_etm.c") + endif() + # ESP32C6-TODO if(CONFIG_IDF_TARGET_ESP32C6) list(REMOVE_ITEM srcs diff --git a/components/esp_hw_support/Kconfig b/components/esp_hw_support/Kconfig index 989849b0ae3e..7d022f50c589 100644 --- a/components/esp_hw_support/Kconfig +++ b/components/esp_hw_support/Kconfig @@ -161,6 +161,16 @@ menu "Hardware Settings" so that these functions can be IRAM-safe and able to be called in the other IRAM interrupt context. endmenu + menu "ETM Configuration" + depends on SOC_ETM_SUPPORTED + config ETM_ENABLE_DEBUG_LOG + bool "Enable debug log" + default n + help + Wether to enable the debug log message for ETM core driver. + Note that, this option only controls the ETM related driver log, won't affect other drivers. + endmenu # ETM Configuration + menu "MMU Config" # This Config is used for configure the MMU. # Be configured based on flash size selection. diff --git a/components/esp_hw_support/README.md b/components/esp_hw_support/README.md index e75de016c4c0..20892026c4f7 100644 --- a/components/esp_hw_support/README.md +++ b/components/esp_hw_support/README.md @@ -1,7 +1,77 @@ -## `esp_hw_support` ## +# `esp_hw_support` (G1 component) -This component contains hardware-related operations for supporting the system. These operations -are one level above that of `hal` in that these(1) use system services such as memory allocation, logging, scheduling -or (2) may be multi-step operations involving/affecting multiple parts of the SoC. +This component contains hardware-related operations for supporting the system. These operations are one level above that of `hal` in that: -Implementations that don't fit other components cleanly, but are not worth creating a new component for (yet) may also be placed here as long as they don't pull dependencies other than the core system components. \ No newline at end of file +1. it uses system services such as memory allocation, logging, scheduling +2. it may be multi-step operations involving/affecting multiple parts of the SoC +3. it offers a service for other components vary from multiple layers (G1, G2 and G3) of ESP-IDF + +Implementations that don't fit other components cleanly, but are not worth creating a new component for (yet) may also be placed here as long as they don't pull dependencies other than the core system components. + +## Event-Task Service (esp_etm) + +### esp_etm driver design + +`esp_etm` driver is divided into two parts: + +* The **core** driver, which focuses on ETM channel allocation and offers APIs to connect the channel with ETM tasks and ETM events that come from other peripherals. +* **Peripheral** side extensions, e.g. GPTimer support generating different kinds of ETM events, and accept multiple ETM tasks. These extensions are implemented in the peripheral driver, and can be located in different components. Usually, the task and event extensions will simply inherit the interface that defined in the core driver. + +See the following class diagram, we take the GPIO and GPTimer as the example to illustrate the architecture of `esp_etm` driver. + +```mermaid +classDiagram + esp_etm_channel_t "1" --> "1" esp_etm_event_t : Has + esp_etm_channel_t "1" --> "1" esp_etm_task_t : Has + class esp_etm_channel_t { + -int chan_id + -esp_etm_event_t event + -esp_etm_task_t task + +enable() esp_err_t + +disable() esp_err_t + +connect(event, task) esp_err_t + +dump() esp_err_t + } + + class esp_etm_event_t { + <> + #int event_id + #etm_trigger_peripheral_t trig_periph + #del() esp_err_t + } + + class esp_etm_task_t { + <> + #int task_id + #etm_trigger_peripheral_t trig_periph + #del() esp_err_t + } + + gpio_etm_event_t --|> esp_etm_event_t : Inheritance + class gpio_etm_event_t { + -int chan_id + +bind_gpio(gpio_num_t gpio) esp_err_t + } + + gpio_etm_task_t --|> esp_etm_task_t : Inheritance + class gpio_etm_task_t { + -int chan_id + +add_gpio(gpio_num) esp_err_t + +rm_gpio(gpio_num) esp_err_t + } + + gptimer_t "1" --> "1..*" gptimer_etm_event_t : Has + gptimer_t "1" --> "1..*" gptimer_etm_task_t : Has + class gptimer_t { + -gptimer_etm_event_t[] events + -gptimer_etm_task_t[] tasks + } + + gptimer_etm_event_t --|> esp_etm_event_t : Inheritance + class gptimer_etm_event_t { + } + + gptimer_etm_task_t --|> esp_etm_task_t : Inheritance + class gptimer_etm_task_t { + } +``` diff --git a/components/esp_hw_support/port/async_memcpy_impl_cp_dma.c b/components/esp_hw_support/dma/async_memcpy_impl_cp_dma.c similarity index 90% rename from components/esp_hw_support/port/async_memcpy_impl_cp_dma.c rename to components/esp_hw_support/dma/async_memcpy_impl_cp_dma.c index 8d268a20b60b..de74a9d5a0cd 100644 --- a/components/esp_hw_support/port/async_memcpy_impl_cp_dma.c +++ b/components/esp_hw_support/dma/async_memcpy_impl_cp_dma.c @@ -12,6 +12,7 @@ #include "esp_log.h" #include "esp_attr.h" #include "esp_err.h" +#include "esp_etm.h" #include "esp_async_memcpy_impl.h" IRAM_ATTR static void async_memcpy_impl_default_isr_handler(void *args) @@ -76,6 +77,14 @@ esp_err_t async_memcpy_impl_restart(async_memcpy_impl_t *impl) return ESP_OK; } +esp_err_t async_memcpy_impl_new_etm_event(async_memcpy_impl_t *impl, async_memcpy_etm_event_t event_type, esp_etm_event_handle_t *out_event) +{ + (void)impl; + (void)event_type; + (void)out_event; + return ESP_ERR_NOT_SUPPORTED; +} + bool async_memcpy_impl_is_buffer_address_valid(async_memcpy_impl_t *impl, void *src, void *dst) { // CP_DMA can only access SRAM diff --git a/components/esp_hw_support/port/async_memcpy_impl_gdma.c b/components/esp_hw_support/dma/async_memcpy_impl_gdma.c similarity index 90% rename from components/esp_hw_support/port/async_memcpy_impl_gdma.c rename to components/esp_hw_support/dma/async_memcpy_impl_gdma.c index ee56d174a956..4bf879206dbf 100644 --- a/components/esp_hw_support/port/async_memcpy_impl_gdma.c +++ b/components/esp_hw_support/dma/async_memcpy_impl_gdma.c @@ -110,6 +110,16 @@ esp_err_t async_memcpy_impl_restart(async_memcpy_impl_t *impl) return ESP_OK; } +esp_err_t async_memcpy_impl_new_etm_event(async_memcpy_impl_t *impl, async_memcpy_etm_event_t event_type, esp_etm_event_handle_t *out_event) +{ + if (event_type == ASYNC_MEMCPY_ETM_EVENT_COPY_DONE) { + // use the RX EOF to indicate the async memcpy done event + return gdma_new_etm_event(impl->rx_channel, GDMA_ETM_EVENT_EOF, out_event); + } else { + return ESP_ERR_NOT_SUPPORTED; + } +} + bool async_memcpy_impl_is_buffer_address_valid(async_memcpy_impl_t *impl, void *src, void *dst) { bool valid = true; diff --git a/components/esp_hw_support/esp_async_memcpy.c b/components/esp_hw_support/dma/esp_async_memcpy.c similarity index 97% rename from components/esp_hw_support/esp_async_memcpy.c rename to components/esp_hw_support/dma/esp_async_memcpy.c index 6b245f2d7e68..cd0001b5ecfd 100644 --- a/components/esp_hw_support/esp_async_memcpy.c +++ b/components/esp_hw_support/dma/esp_async_memcpy.c @@ -117,6 +117,12 @@ esp_err_t esp_async_memcpy_uninstall(async_memcpy_t asmcp) return ret; } +esp_err_t esp_async_memcpy_new_etm_event(async_memcpy_t asmcp, async_memcpy_etm_event_t event_type, esp_etm_event_handle_t *out_event) +{ + ESP_RETURN_ON_FALSE(asmcp, ESP_ERR_INVALID_ARG, TAG, "mcp handle can't be null"); + return async_memcpy_impl_new_etm_event(&asmcp->mcp_impl, event_type, out_event); +} + static int async_memcpy_prepare_receive(async_memcpy_t asmcp, void *buffer, size_t size, dma_descriptor_t **start_desc, dma_descriptor_t **end_desc) { uint32_t prepared_length = 0; diff --git a/components/esp_hw_support/gdma.c b/components/esp_hw_support/dma/gdma.c similarity index 91% rename from components/esp_hw_support/gdma.c rename to components/esp_hw_support/dma/gdma.c index 6ffa28fc8b39..10be32483582 100644 --- a/components/esp_hw_support/gdma.c +++ b/components/esp_hw_support/dma/gdma.c @@ -13,42 +13,18 @@ #include "freertos/task.h" #include "soc/soc_caps.h" #include "soc/periph_defs.h" -#include "esp_intr_alloc.h" #include "esp_log.h" #include "esp_check.h" -#include "esp_heap_caps.h" -#include "hal/gdma_hal.h" -#include "hal/gdma_ll.h" -#include "soc/gdma_periph.h" #include "esp_memory_utils.h" #include "esp_private/periph_ctrl.h" -#include "esp_private/gdma.h" +#include "gdma_priv.h" static const char *TAG = "gdma"; -#if CONFIG_GDMA_ISR_IRAM_SAFE || CONFIG_GDMA_CTRL_FUNC_IN_IRAM -#define GDMA_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) -#else -#define GDMA_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT -#endif - -#if CONFIG_GDMA_ISR_IRAM_SAFE -#define GDMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) -#else -#define GDMA_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED -#endif - #define GDMA_INVALID_PERIPH_TRIG (0x3F) #define SEARCH_REQUEST_RX_CHANNEL (1 << 0) #define SEARCH_REQUEST_TX_CHANNEL (1 << 1) -typedef struct gdma_platform_t gdma_platform_t; -typedef struct gdma_group_t gdma_group_t; -typedef struct gdma_pair_t gdma_pair_t; -typedef struct gdma_channel_t gdma_channel_t; -typedef struct gdma_tx_channel_t gdma_tx_channel_t; -typedef struct gdma_rx_channel_t gdma_rx_channel_t; - /** * GDMA driver consists of there object class, namely: Group, Pair and Channel. * Channel is allocated when user calls `gdma_new_channel`, its lifecycle is maintained by user. @@ -61,51 +37,11 @@ typedef struct gdma_rx_channel_t gdma_rx_channel_t; * For pair, it has a spinlock, which is used to protect pair level stuffs, e.g. channel handle slots, occupy code. */ -struct gdma_platform_t { +typedef struct gdma_platform_t { portMUX_TYPE spinlock; // platform level spinlock gdma_group_t *groups[SOC_GDMA_GROUPS]; // array of GDMA group instances int group_ref_counts[SOC_GDMA_GROUPS]; // reference count used to protect group install/uninstall -}; - -struct gdma_group_t { - int group_id; // Group ID, index from 0 - gdma_hal_context_t hal; // HAL instance is at group level - portMUX_TYPE spinlock; // group level spinlock - gdma_pair_t *pairs[SOC_GDMA_PAIRS_PER_GROUP]; // handles of GDMA pairs - int pair_ref_counts[SOC_GDMA_PAIRS_PER_GROUP]; // reference count used to protect pair install/uninstall -}; - -struct gdma_pair_t { - gdma_group_t *group; // which group the pair belongs to - int pair_id; // Pair ID, index from 0 - gdma_tx_channel_t *tx_chan; // pointer of tx channel in the pair - gdma_rx_channel_t *rx_chan; // pointer of rx channel in the pair - int occupy_code; // each bit indicates which channel has been occupied (an occupied channel will be skipped during channel search) - portMUX_TYPE spinlock; // pair level spinlock -}; - -struct gdma_channel_t { - gdma_pair_t *pair; // which pair the channel belongs to - intr_handle_t intr; // per-channel interrupt handle - portMUX_TYPE spinlock; // channel level spinlock - gdma_channel_direction_t direction; // channel direction - int periph_id; // Peripheral instance ID, indicates which peripheral is connected to this GDMA channel - size_t sram_alignment; // alignment for memory in SRAM - size_t psram_alignment; // alignment for memory in PSRAM - esp_err_t (*del)(gdma_channel_t *channel); // channel deletion function, it's polymorphic, see `gdma_del_tx_channel` or `gdma_del_rx_channel` -}; - -struct gdma_tx_channel_t { - gdma_channel_t base; // GDMA channel, base class - void *user_data; // user registered DMA event data - gdma_event_callback_t on_trans_eof; // TX EOF callback -}; - -struct gdma_rx_channel_t { - gdma_channel_t base; // GDMA channel, base class - void *user_data; // user registered DMA event data - gdma_event_callback_t on_recv_eof; // RX EOF callback -}; +} gdma_platform_t; static gdma_group_t *gdma_acquire_group_handle(int group_id); static gdma_pair_t *gdma_acquire_pair_handle(gdma_group_t *group, int pair_id); diff --git a/components/esp_hw_support/dma/gdma_etm.c b/components/esp_hw_support/dma/gdma_etm.c new file mode 100644 index 000000000000..d959c8193eb4 --- /dev/null +++ b/components/esp_hw_support/dma/gdma_etm.c @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG + +#include +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "hal/gdma_hal.h" +#include "hal/gdma_ll.h" +#include "soc/gdma_periph.h" +#include "esp_private/gdma.h" +#include "esp_private/etm_interface.h" +#include "gdma_priv.h" + +#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +static const char *TAG = "gdma-etm"; + +typedef struct gdma_etm_task_t { + esp_etm_task_t base; + gdma_channel_t *chan; +} gdma_etm_task_t; + +static esp_err_t gdma_del_etm_event(esp_etm_event_t *event) +{ + free(event); + return ESP_OK; +} + +static esp_err_t gdma_del_etm_task(esp_etm_task_t *task) +{ + gdma_etm_task_t *gdma_task = __containerof(task, gdma_etm_task_t, base); + gdma_channel_t *dma_chan = gdma_task->chan; + gdma_pair_t *pair = dma_chan->pair; + gdma_group_t *group = pair->group; + if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX) { + gdma_ll_rx_enable_etm_task(group->hal.dev, pair->pair_id, false); + } else { + gdma_ll_tx_enable_etm_task(group->hal.dev, pair->pair_id, false); + } + free(gdma_task); + return ESP_OK; +} + +esp_err_t gdma_new_etm_event(gdma_channel_handle_t dma_chan, gdma_etm_event_type_t event_type, esp_etm_event_handle_t *out_event) +{ + esp_etm_event_t *event = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(dma_chan && out_event, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(event_type < GDMA_ETM_EVENT_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid event type"); + event = heap_caps_calloc(1, sizeof(esp_etm_event_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(event, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM event"); + + gdma_pair_t *pair = dma_chan->pair; + gdma_group_t *group = pair->group; + uint32_t event_id = 0; + + if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX) { + event_id = GDMA_LL_RX_ETM_EVENT_TABLE(group->group_id, pair->pair_id, event_type); + } else { + event_id = GDMA_LL_TX_ETM_EVENT_TABLE(group->group_id, pair->pair_id, event_type); + } + ESP_GOTO_ON_FALSE(event_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported event type"); + + // fill the ETM event object + event->event_id = event_id; + event->trig_periph = ETM_TRIG_PERIPH_GDMA; + event->del = gdma_del_etm_event; + *out_event = event; + return ESP_OK; + +err: + if (event) { + gdma_del_etm_event(event); + } + return ret; +} + +esp_err_t gdma_new_etm_task(gdma_channel_handle_t dma_chan, gdma_etm_task_type_t task_type, esp_etm_task_handle_t *out_task) +{ + gdma_etm_task_t *task = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(dma_chan && out_task, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(task_type < GDMA_ETM_TASK_MAX, ESP_ERR_INVALID_ARG, err, TAG, "invalid task type"); + task = heap_caps_calloc(1, sizeof(gdma_etm_task_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(task, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM task"); + + gdma_pair_t *pair = dma_chan->pair; + gdma_group_t *group = pair->group; + uint32_t task_id = 0; + + if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX) { + task_id = GDMA_LL_RX_ETM_TASK_TABLE(group->group_id, pair->pair_id, task_type); + gdma_ll_rx_enable_etm_task(group->hal.dev, pair->pair_id, true); + } else { + task_id = GDMA_LL_TX_ETM_TASK_TABLE(group->group_id, pair->pair_id, task_type); + gdma_ll_tx_enable_etm_task(group->hal.dev, pair->pair_id, true); + } + ESP_GOTO_ON_FALSE(task_id != 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "not supported task type"); + + // fill the ETM task object + task->chan = dma_chan; + task->base.task_id = task_id; + task->base.trig_periph = ETM_TRIG_PERIPH_GDMA; + task->base.del = gdma_del_etm_task; + *out_task = &(task->base); + return ESP_OK; + +err: + if (task) { + gdma_del_etm_task(&task->base); + } + return ret; +} diff --git a/components/esp_hw_support/dma/gdma_priv.h b/components/esp_hw_support/dma/gdma_priv.h new file mode 100644 index 000000000000..22c41b4c57f5 --- /dev/null +++ b/components/esp_hw_support/dma/gdma_priv.h @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "esp_heap_caps.h" +#include "soc/soc_caps.h" +#include "hal/gdma_hal.h" +#include "hal/gdma_ll.h" +#include "soc/gdma_periph.h" +#include "esp_private/gdma.h" + +#if CONFIG_GDMA_ISR_IRAM_SAFE || CONFIG_GDMA_CTRL_FUNC_IN_IRAM +#define GDMA_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define GDMA_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +#if CONFIG_GDMA_ISR_IRAM_SAFE +#define GDMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) +#else +#define GDMA_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct gdma_pair_t gdma_pair_t; +typedef struct gdma_channel_t gdma_channel_t; +typedef struct gdma_tx_channel_t gdma_tx_channel_t; +typedef struct gdma_rx_channel_t gdma_rx_channel_t; + +typedef struct gdma_group_t { + int group_id; // Group ID, index from 0 + gdma_hal_context_t hal; // HAL instance is at group level + portMUX_TYPE spinlock; // group level spinlock + gdma_pair_t *pairs[SOC_GDMA_PAIRS_PER_GROUP]; // handles of GDMA pairs + int pair_ref_counts[SOC_GDMA_PAIRS_PER_GROUP]; // reference count used to protect pair install/uninstall +} gdma_group_t; + +struct gdma_pair_t { + gdma_group_t *group; // which group the pair belongs to + int pair_id; // Pair ID, index from 0 + gdma_tx_channel_t *tx_chan; // pointer of tx channel in the pair + gdma_rx_channel_t *rx_chan; // pointer of rx channel in the pair + int occupy_code; // each bit indicates which channel has been occupied (an occupied channel will be skipped during channel search) + portMUX_TYPE spinlock; // pair level spinlock +}; + +struct gdma_channel_t { + gdma_pair_t *pair; // which pair the channel belongs to + intr_handle_t intr; // per-channel interrupt handle + portMUX_TYPE spinlock; // channel level spinlock + gdma_channel_direction_t direction; // channel direction + int periph_id; // Peripheral instance ID, indicates which peripheral is connected to this GDMA channel + size_t sram_alignment; // alignment for memory in SRAM + size_t psram_alignment; // alignment for memory in PSRAM + esp_err_t (*del)(gdma_channel_t *channel); // channel deletion function, it's polymorphic, see `gdma_del_tx_channel` or `gdma_del_rx_channel` +}; + +struct gdma_tx_channel_t { + gdma_channel_t base; // GDMA channel, base class + void *user_data; // user registered DMA event data + gdma_event_callback_t on_trans_eof; // TX EOF callback +}; + +struct gdma_rx_channel_t { + gdma_channel_t base; // GDMA channel, base class + void *user_data; // user registered DMA event data + gdma_event_callback_t on_recv_eof; // RX EOF callback +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/esp_etm.c b/components/esp_hw_support/esp_etm.c new file mode 100644 index 000000000000..ff7261eb738c --- /dev/null +++ b/components/esp_hw_support/esp_etm.c @@ -0,0 +1,321 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_ETM_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/soc_caps.h" +#include "soc/periph_defs.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_etm.h" +#include "hal/etm_hal.h" +#include "hal/etm_ll.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/etm_interface.h" + +#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +static const char *TAG = "etm"; + +typedef struct etm_platform_t etm_platform_t; +typedef struct etm_group_t etm_group_t; +typedef struct esp_etm_channel_t esp_etm_channel_t; + +struct etm_platform_t { + _lock_t mutex; // platform level mutex lock + etm_group_t *groups[SOC_ETM_GROUPS]; // etm group pool + int group_ref_counts[SOC_ETM_GROUPS]; // reference count used to protect group install/uninstall +}; + +struct etm_group_t { + int group_id; // hardware group id + etm_hal_context_t hal; // hardware abstraction layer context + portMUX_TYPE spinlock; // to protect per-group register level concurrent access + esp_etm_channel_t *chans[SOC_ETM_CHANNELS_PER_GROUP]; +}; + +typedef enum { + ETM_CHAN_FSM_INIT, + ETM_CHAN_FSM_ENABLE, +} etm_chan_fsm_t; + +struct esp_etm_channel_t { + int chan_id; // Channel ID + etm_group_t *group; // which group this channel belongs to + etm_chan_fsm_t fsm; // record ETM channel's driver state + esp_etm_event_handle_t event; // which event is connect to the channel + esp_etm_task_handle_t task; // which task is connect to the channel +}; + +// ETM driver platform, it's always a singleton +static etm_platform_t s_platform; + +static etm_group_t *etm_acquire_group_handle(int group_id) +{ + bool new_group = false; + etm_group_t *group = NULL; + + // prevent install ETM group concurrently + _lock_acquire(&s_platform.mutex); + if (!s_platform.groups[group_id]) { + group = heap_caps_calloc(1, sizeof(etm_group_t), ETM_MEM_ALLOC_CAPS); + if (group) { + new_group = true; + s_platform.groups[group_id] = group; // register to platform + // initialize ETM group members + group->group_id = group_id; + group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // enable APB access ETM registers + // if we have multiple ETM groups/instances, we assume the peripheral defines are continuous + periph_module_enable(PERIPH_ETM_MODULE + group_id); + periph_module_reset(PERIPH_ETM_MODULE + group_id); + // initialize HAL context + etm_hal_init(&group->hal); + } + } else { + group = s_platform.groups[group_id]; + } + if (group) { + // 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) at %p", group_id, group); + } + + return group; +} + +static void etm_release_group_handle(etm_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; // deregister from platform + periph_module_disable(PERIPH_ETM_MODULE + group_id); + } + _lock_release(&s_platform.mutex); + + if (do_deinitialize) { + free(group); + ESP_LOGD(TAG, "del group (%d)", group_id); + } +} + +static esp_err_t etm_chan_register_to_group(esp_etm_channel_t *chan) +{ + etm_group_t *group = NULL; + int chan_id = -1; + for (int i = 0; i < SOC_ETM_GROUPS; i++) { + group = etm_acquire_group_handle(i); + ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i); + // loop to search free channel in the group + portENTER_CRITICAL(&group->spinlock); + for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) { + if (!group->chans[j]) { + chan_id = j; + group->chans[j] = chan; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (chan_id < 0) { + etm_release_group_handle(group); + group = NULL; + } else { + chan->chan_id = chan_id; + chan->group = group; + break;; + } + } + ESP_RETURN_ON_FALSE(chan_id != -1, ESP_ERR_NOT_FOUND, TAG, "no free channel"); + return ESP_OK; +} + +static void etm_chan_unregister_from_group(esp_etm_channel_t *chan) +{ + etm_group_t *group = chan->group; + int chan_id = chan->chan_id; + portENTER_CRITICAL(&group->spinlock); + group->chans[chan_id] = NULL; + portEXIT_CRITICAL(&group->spinlock); + // channel has a reference on group, release it now + etm_release_group_handle(group); +} + +static esp_err_t etm_chan_destroy(esp_etm_channel_t *chan) +{ + if (chan->group) { + etm_chan_unregister_from_group(chan); + } + free(chan); + return ESP_OK; +} + +esp_err_t esp_etm_new_channel(const esp_etm_channel_config_t *config, esp_etm_channel_handle_t *ret_chan) +{ +#if CONFIG_ETM_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + esp_etm_channel_t *chan = NULL; + ESP_GOTO_ON_FALSE(config && ret_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid args"); + + chan = heap_caps_calloc(1, sizeof(esp_etm_channel_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(chan, ESP_ERR_NO_MEM, err, TAG, "no mem for channel"); + // register channel to the group, one group can have multiple channels + ESP_GOTO_ON_ERROR(etm_chan_register_to_group(chan), err, TAG, "register channel failed"); + etm_group_t *group = chan->group; + int group_id = group->group_id; + int chan_id = chan->chan_id; + + chan->fsm = ETM_CHAN_FSM_INIT; + ESP_LOGD(TAG, "new etm channel (%d,%d) at %p", group_id, chan_id, chan); + *ret_chan = chan; + return ESP_OK; + +err: + if (chan) { + etm_chan_destroy(chan); + } + return ret; +} + +esp_err_t esp_etm_del_channel(esp_etm_channel_handle_t chan) +{ + ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid args"); + ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel is not in init state"); + etm_group_t *group = chan->group; + int group_id = group->group_id; + int chan_id = chan->chan_id; + + // disconnect the channel from any event or task + etm_ll_channel_set_event(group->hal.regs, chan_id, 0); + etm_ll_channel_set_task(group->hal.regs, chan_id, 0); + + ESP_LOGD(TAG, "del etm channel (%d,%d)", group_id, chan_id); + // recycle memory resource + ESP_RETURN_ON_ERROR(etm_chan_destroy(chan), TAG, "destroy etm channel failed"); + return ESP_OK; +} + +esp_err_t esp_etm_channel_enable(esp_etm_channel_handle_t chan) +{ + ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel is not in init state"); + etm_group_t *group = chan->group; + etm_ll_enable_channel(group->hal.regs, chan->chan_id); + chan->fsm = ETM_CHAN_FSM_ENABLE; + return ESP_OK; +} + +esp_err_t esp_etm_channel_disable(esp_etm_channel_handle_t chan) +{ + ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(chan->fsm == ETM_CHAN_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); + etm_group_t *group = chan->group; + etm_ll_disable_channel(group->hal.regs, chan->chan_id); + chan->fsm = ETM_CHAN_FSM_INIT; + return ESP_OK; +} + +esp_err_t esp_etm_channel_connect(esp_etm_channel_handle_t chan, esp_etm_event_handle_t event, esp_etm_task_handle_t task) +{ + ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + etm_group_t *group = chan->group; + uint32_t event_id = 0; + uint32_t task_id = 0; + + // if the event/task is NULL, then the channel will disconnect from the event/task + if (event) { + event_id = event->event_id; + } + if (task) { + task_id = task->task_id; + } + etm_ll_channel_set_event(group->hal.regs, chan->chan_id, event_id); + etm_ll_channel_set_task(group->hal.regs, chan->chan_id, task_id); + chan->event = event; + chan->task = task; + ESP_LOGD(TAG, "event %"PRIu32" => channel %d", event_id, chan->chan_id); + ESP_LOGD(TAG, "channel %d => task %"PRIu32, chan->chan_id, task_id); + return ESP_OK; +} + +esp_err_t esp_etm_del_event(esp_etm_event_handle_t event) +{ + ESP_RETURN_ON_FALSE(event, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return event->del(event); +} + +esp_err_t esp_etm_del_task(esp_etm_task_handle_t task) +{ + ESP_RETURN_ON_FALSE(task, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return task->del(task); +} + +esp_err_t esp_etm_dump(FILE *out_stream) +{ + etm_group_t *group = NULL; + esp_etm_channel_handle_t etm_chan = NULL; + ESP_RETURN_ON_FALSE(out_stream, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + fprintf(out_stream, "===========ETM Dump Start==========\r\n"); + char line[80]; + size_t len = sizeof(line); + for (int i = 0; i < SOC_ETM_GROUPS; i++) { + group = etm_acquire_group_handle(i); + ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i); + etm_hal_context_t *hal = &group->hal; + for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) { + bool print_line = true; + portENTER_CRITICAL(&group->spinlock); + etm_chan = group->chans[j]; + if (etm_ll_is_channel_enabled(hal->regs, j)) { + if (!etm_chan) { + // in case the etm driver is bypassed and some channel is enabled in another way (e.g. by hal driver) + snprintf(line, len, "channel %d is enabled but not recorded\r\n", j); + } else { + // print which event and task the channel is connected to + snprintf(line, len, "channel %d: event %"PRIu32" ==> task %"PRIu32"\r\n", j, + etm_chan->event ? etm_chan->event->event_id : 0, + etm_chan->task ? etm_chan->task->task_id : 0); + } + } else { + if (etm_chan) { + // channel is created, but not enabled by `esp_etm_channel_enable` yet + snprintf(line, len, "channel %d is created but not enabled\r\n", j); + } else { + // a free channel, don't print anything + print_line = false; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (print_line) { + fputs(line, out_stream); + } + } + etm_release_group_handle(group); + } + fprintf(out_stream, "===========ETM Dump End============\r\n"); + return ESP_OK; +} diff --git a/components/esp_hw_support/include/esp_async_memcpy.h b/components/esp_hw_support/include/esp_async_memcpy.h index c45c61b2d154..6d1f06571be3 100644 --- a/components/esp_hw_support/include/esp_async_memcpy.h +++ b/components/esp_hw_support/include/esp_async_memcpy.h @@ -13,6 +13,7 @@ extern "C" { #include #include #include "esp_err.h" +#include "esp_etm.h" /** * @brief Type of async memcpy handle @@ -91,6 +92,8 @@ esp_err_t esp_async_memcpy_uninstall(async_memcpy_t asmcp); /** * @brief Send an asynchronous memory copy request * + * @note The callback function is invoked in interrupt context, never do blocking jobs in the callback. + * * @param[in] asmcp Handle of async memcpy driver that returned from esp_async_memcpy_install * @param[in] dst Destination address (copy to) * @param[in] src Source address (copy from) @@ -101,11 +104,33 @@ esp_err_t esp_async_memcpy_uninstall(async_memcpy_t asmcp); * - ESP_OK: Send memory copy request successfully * - ESP_ERR_INVALID_ARG: Send memory copy request failed because of invalid argument * - ESP_FAIL: Send memory copy request failed because of other error - * - * @note The callback function is invoked in interrupt context, never do blocking jobs in the callback. */ esp_err_t esp_async_memcpy(async_memcpy_t asmcp, void *dst, void *src, size_t n, async_memcpy_isr_cb_t cb_isr, void *cb_args); +/** + * @brief Async memory copy specific events that supported by the ETM module + */ +typedef enum { + ASYNC_MEMCPY_ETM_EVENT_COPY_DONE, /*!< memory copy finished */ +} async_memcpy_etm_event_t; + +/** + * @brief Get the ETM event handle for async memcpy done signal + * + * @note The created ETM event object can be deleted later by calling `esp_etm_del_event` + * + * @param[in] asmcp Handle of async memcpy driver that returned from `esp_async_memcpy_install` + * @param[in] event_type ETM event type + * @param[out] out_event Returned ETM event handle + * @return + * @return + * - ESP_OK: Get ETM event successfully + * - ESP_ERR_INVALID_ARG: Get ETM event failed because of invalid argument + * - ESP_ERR_NOT_SUPPORTED: Get ETM event failed because the DMA hardware doesn't support ETM submodule + * - ESP_FAIL: Get ETM event failed because of other error + */ +esp_err_t esp_async_memcpy_new_etm_event(async_memcpy_t asmcp, async_memcpy_etm_event_t event_type, esp_etm_event_handle_t *out_event); + #ifdef __cplusplus } #endif diff --git a/components/esp_hw_support/include/esp_etm.h b/components/esp_hw_support/include/esp_etm.h new file mode 100644 index 000000000000..c0757abf8aba --- /dev/null +++ b/components/esp_hw_support/include/esp_etm.h @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ETM channel handle + */ +typedef struct esp_etm_channel_t *esp_etm_channel_handle_t; + +/** + * @brief ETM event handle + */ +typedef struct esp_etm_event_t *esp_etm_event_handle_t; + +/** + * @brief ETM task handle + */ +typedef struct esp_etm_task_t *esp_etm_task_handle_t; + +/** + * @brief ETM channel configuration + */ +typedef struct { + +} esp_etm_channel_config_t; + +/** + * @brief Allocate an ETM channel + * + * @note The channel can later be freed by `esp_etm_del_channel` + * + * @param[in] config ETM channel configuration + * @param[out] ret_chan Returned ETM channel handle + * @return + * - ESP_OK: Allocate ETM channel successfully + * - ESP_ERR_INVALID_ARG: Allocate ETM channel failed because of invalid argument + * - ESP_ERR_NO_MEM: Allocate ETM channel failed because of out of memory + * - ESP_ERR_NOT_FOUND: Allocate ETM channel failed because all channels are used up and no more free one + * - ESP_FAIL: Allocate ETM channel failed because of other reasons + */ +esp_err_t esp_etm_new_channel(const esp_etm_channel_config_t *config, esp_etm_channel_handle_t *ret_chan); + +/** + * @brief Delete an ETM channel + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @return + * - ESP_OK: Delete ETM channel successfully + * - ESP_ERR_INVALID_ARG: Delete ETM channel failed because of invalid argument + * - ESP_FAIL: Delete ETM channel failed because of other reasons + */ +esp_err_t esp_etm_del_channel(esp_etm_channel_handle_t chan); + +/** + * @brief Enable ETM channel + * + * @note This function will transit the channel state from init to enable. + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @return + * - ESP_OK: Enable ETM channel successfully + * - ESP_ERR_INVALID_ARG: Enable ETM channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Enable ETM channel failed because the channel has been enabled already + * - ESP_FAIL: Enable ETM channel failed because of other reasons + */ +esp_err_t esp_etm_channel_enable(esp_etm_channel_handle_t chan); + +/** + * @brief Disable ETM channel + * + * @note This function will transit the channel state from enable to init. + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @return + * - ESP_OK: Disable ETM channel successfully + * - ESP_ERR_INVALID_ARG: Disable ETM channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Disable ETM channel failed because the channel is not enabled yet + * - ESP_FAIL: Disable ETM channel failed because of other reasons + */ +esp_err_t esp_etm_channel_disable(esp_etm_channel_handle_t chan); + +/** + * @brief Connect an ETM event to an ETM task via a previously allocated ETM channel + * + * @note Setting the ETM event/task handle to NULL means to disconnect the channel from any event/task + * + * @param[in] chan ETM channel handle that created by `esp_etm_new_channel` + * @param[in] event ETM event handle obtained from a driver/peripheral, e.g. `xxx_new_etm_event` + * @param[in] task ETM task handle obtained from a driver/peripheral, e.g. `xxx_new_etm_task` + * @return + * - ESP_OK: Connect ETM event and task to the channel successfully + * - ESP_ERR_INVALID_ARG: Connect ETM event and task to the channel failed because of invalid argument + * - ESP_FAIL: Connect ETM event and task to the channel failed because of other reasons + */ +esp_err_t esp_etm_channel_connect(esp_etm_channel_handle_t chan, esp_etm_event_handle_t event, esp_etm_task_handle_t task); + +/** + * @brief Delete ETM event + * + * @note Although the ETM event comes from various peripherals, we provide the same user API to delete the event handle seamlessly. + * + * @param[in] event ETM event handle obtained from a driver/peripheral, e.g. `xxx_new_etm_event` + * @return + * - ESP_OK: Delete ETM event successfully + * - ESP_ERR_INVALID_ARG: Delete ETM event failed because of invalid argument + * - ESP_FAIL: Delete ETM event failed because of other reasons + */ +esp_err_t esp_etm_del_event(esp_etm_event_handle_t event); + +/** + * @brief Delete ETM task + * + * @note Although the ETM task comes from various peripherals, we provide the same user API to delete the task handle seamlessly. + * + * @param[in] task ETM task handle obtained from a driver/peripheral, e.g. `xxx_new_etm_task` + * @return + * - ESP_OK: Delete ETM task successfully + * - ESP_ERR_INVALID_ARG: Delete ETM task failed because of invalid argument + * - ESP_FAIL: Delete ETM task failed because of other reasons + */ +esp_err_t esp_etm_del_task(esp_etm_task_handle_t task); + +/** + * @brief Dump ETM channel usages to the given IO stream + * + * @param[in] out_stream IO stream (e.g. stdout) + * @return + * - ESP_OK: Dump ETM channel usages successfully + * - ESP_ERR_INVALID_ARG: Dump ETM channel usages failed because of invalid argument + * - ESP_FAIL: Dump ETM channel usages failed because of other reasons + */ +esp_err_t esp_etm_dump(FILE *out_stream); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/include/esp_private/etm_interface.h b/components/esp_hw_support/include/esp_private/etm_interface.h new file mode 100644 index 000000000000..860d894760a2 --- /dev/null +++ b/components/esp_hw_support/include/esp_private/etm_interface.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct esp_etm_event_t esp_etm_event_t; +typedef struct esp_etm_task_t esp_etm_task_t; + +/** + * @brief List the peripherals that can trigger ETM task/event + */ +typedef enum { + ETM_TRIG_PERIPH_GPIO, /*!< ETM trigger source: GPIO */ + ETM_TRIG_PERIPH_GDMA, /*!< ETM trigger source: GDMA */ + ETM_TRIG_PERIPH_GPTIMER, /*!< ETM trigger source: GPTimer */ + ETM_TRIG_PERIPH_SYSTIMER, /*!< ETM trigger source: Systimer */ +} etm_trigger_peripheral_t; + +/** + * @brief ETM event interface definition + */ +struct esp_etm_event_t { + /** + * @brief Unique event ID + */ + uint32_t event_id; + + /** + * @brief ETM trigger peripheral + */ + etm_trigger_peripheral_t trig_periph; + + /** + * @brief Resource destroy + */ + esp_err_t (*del)(esp_etm_event_t *event); +}; + +/** + * @brief ETM task interface definition + */ +struct esp_etm_task_t { + /** + * @brief Unique task ID + */ + uint32_t task_id; + + /** + * @brief ETM trigger peripheral + */ + etm_trigger_peripheral_t trig_periph; + + /** + * @brief Resource destroy + */ + esp_err_t (*del)(esp_etm_task_t *task); +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/include/esp_private/gdma.h b/components/esp_hw_support/include/esp_private/gdma.h index f888cd5bc9bd..1d678550b0c9 100644 --- a/components/esp_hw_support/include/esp_private/gdma.h +++ b/components/esp_hw_support/include/esp_private/gdma.h @@ -5,12 +5,14 @@ */ // DO NOT USE THESE APIS IN ANY APPLICATIONS -// GDMA driver is not public for end users, but for ESP-IDF developpers. +// GDMA driver is not public for end users, but for ESP-IDF developers. #pragma once #include +#include "esp_etm.h" #include "soc/gdma_channel.h" +#include "hal/gdma_types.h" #include "esp_err.h" #ifdef __cplusplus @@ -325,6 +327,38 @@ esp_err_t gdma_append(gdma_channel_handle_t dma_chan); */ esp_err_t gdma_reset(gdma_channel_handle_t dma_chan); +/** + * @brief Get the ETM event for GDMA channel + * + * @note The created ETM event object can be deleted later by calling `esp_etm_del_event` + * + * @param[in] dma_chan GDMA channel handle, allocated by `gdma_new_channel` + * @param[in] event_type GDMA ETM event type + * @param[out] out_event Returned ETM event handle + * @return + * - ESP_OK: Get ETM event successfully + * - ESP_ERR_INVALID_ARG: Get ETM event failed because of invalid argument + * - ESP_ERR_NOT_SUPPORTED: Get ETM event failed because the GDMA hardware doesn't support ETM event + * - ESP_FAIL: Get ETM event failed because of other error + */ +esp_err_t gdma_new_etm_event(gdma_channel_handle_t dma_chan, gdma_etm_event_type_t event_type, esp_etm_event_handle_t *out_event); + +/** + * @brief Get the ETM task for GDMA channel + * + * @note The created ETM task object can be deleted later by calling `esp_etm_del_task` + * + * @param[in] dma_chan GDMA channel handle, allocated by `gdma_new_channel` + * @param[in] task_type GDMA ETM task type + * @param[out] out_task Returned ETM task handle + * @return + * - ESP_OK: Get ETM task successfully + * - ESP_ERR_INVALID_ARG: Get ETM task failed because of invalid argument + * - ESP_ERR_NOT_SUPPORTED: Get ETM task failed because the gdma hardware doesn't support ETM task + * - ESP_FAIL: Get ETM task failed because of other error + */ +esp_err_t gdma_new_etm_task(gdma_channel_handle_t dma_chan, gdma_etm_task_type_t task_type, esp_etm_task_handle_t *out_task); + #ifdef __cplusplus } #endif diff --git a/components/esp_hw_support/port/include/esp_async_memcpy_impl.h b/components/esp_hw_support/port/include/esp_async_memcpy_impl.h index ce9c1fb88cb2..9d1c76a41de3 100644 --- a/components/esp_hw_support/port/include/esp_async_memcpy_impl.h +++ b/components/esp_hw_support/port/include/esp_async_memcpy_impl.h @@ -12,9 +12,11 @@ extern "C" { #include #include "esp_err.h" #include "esp_intr_alloc.h" +#include "esp_etm.h" #include "soc/soc_caps.h" #include "hal/dma_types.h" #include "freertos/FreeRTOS.h" +#include "esp_async_memcpy.h" #if SOC_CP_DMA_SUPPORTED #include "hal/cp_dma_ll.h" @@ -92,6 +94,16 @@ esp_err_t async_memcpy_impl_stop(async_memcpy_impl_t *impl); */ esp_err_t async_memcpy_impl_restart(async_memcpy_impl_t *impl); +/** + * @brief Get ETM Event handle + * + * @param impl async mcp implementation layer context pointer + * @param event_type ETM event type + * @param out_event Returned ETM event handle + * @return ESP_OK on success, ESP_ERR_NOT_SUPPORTED if not supported in hardware, otherwise failed + */ +esp_err_t async_memcpy_impl_new_etm_event(async_memcpy_impl_t *impl, async_memcpy_etm_event_t event_type, esp_etm_event_handle_t *out_event); + /** * @brief check if buffer address is valid * @note This is related to underlying target (e.g. on esp32-s2, only buffer located in SRAM is supported) diff --git a/components/esp_hw_support/test_apps/etm/CMakeLists.txt b/components/esp_hw_support/test_apps/etm/CMakeLists.txt new file mode 100644 index 000000000000..d5c75a52b3a0 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/CMakeLists.txt @@ -0,0 +1,5 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(etm_test) diff --git a/components/esp_hw_support/test_apps/etm/README.md b/components/esp_hw_support/test_apps/etm/README.md new file mode 100644 index 000000000000..e3ba9c6759dc --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C6 | +| ----------------- | -------- | diff --git a/components/esp_hw_support/test_apps/etm/main/CMakeLists.txt b/components/esp_hw_support/test_apps/etm/main/CMakeLists.txt new file mode 100644 index 000000000000..fb291cc16ae2 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/CMakeLists.txt @@ -0,0 +1,23 @@ +set(srcs "test_app_main.c" + "test_etm_core.c") + +if(CONFIG_SOC_GPIO_SUPPORT_ETM) + list(APPEND srcs "test_gpio_etm.c") +endif() + +if(CONFIG_SOC_TIMER_SUPPORT_ETM) + list(APPEND srcs "test_gptimer_etm.c") +endif() + +if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM) + list(APPEND srcs "test_systimer_etm.c") +endif() + +if(CONFIG_SOC_GDMA_SUPPORT_ETM) + list(APPEND srcs "test_gdma_etm.c") +endif() + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + WHOLE_ARCHIVE) diff --git a/components/esp_hw_support/test_apps/etm/main/test_app_main.c b/components/esp_hw_support/test_apps/etm/main/test_app_main.c new file mode 100644 index 000000000000..be7beff893cb --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_app_main.c @@ -0,0 +1,60 @@ +/* + * 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" +#include "esp_newlib.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +// Some resources are lazy allocated in pulse_cnt 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) +{ + /* some FreeRTOS stuff is cleaned up by idle task */ + vTaskDelay(5); + + /* clean up some of the newlib's lazy allocations */ + esp_reent_cleanup(); + + 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/esp_hw_support/test_apps/etm/main/test_etm_core.c b/components/esp_hw_support/test_apps/etm/main/test_etm_core.c new file mode 100644 index 000000000000..4c2f850efd6a --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_etm_core.c @@ -0,0 +1,39 @@ +/* + * 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 "esp_etm.h" +#include "soc/soc_caps.h" +#include "esp_attr.h" + +TEST_CASE("etm_channel_install_uninstall", "[etm]") +{ + printf("install etm channels exhaustively\r\n"); + esp_etm_channel_handle_t etm_chans[SOC_ETM_GROUPS][SOC_ETM_CHANNELS_PER_GROUP]; + esp_etm_channel_config_t config = {}; + for (int i = 0; i < SOC_ETM_GROUPS; i++) { + for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) { + TEST_ESP_OK(esp_etm_new_channel(&config, &etm_chans[i][j])); + } + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, esp_etm_new_channel(&config, &etm_chans[0][0])); + } + + TEST_ESP_OK(esp_etm_channel_enable(etm_chans[0][0])); + TEST_ESP_OK(esp_etm_dump(stdout)); + + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_etm_del_channel(etm_chans[0][0])); + TEST_ESP_OK(esp_etm_channel_disable(etm_chans[0][0])); + + for (int i = 0; i < SOC_ETM_GROUPS; i++) { + for (int j = 0; j < SOC_ETM_CHANNELS_PER_GROUP; j++) { + TEST_ESP_OK(esp_etm_del_channel(etm_chans[i][j])); + } + } +} diff --git a/components/esp_hw_support/test_apps/etm/main/test_gdma_etm.c b/components/esp_hw_support/test_apps/etm/main/test_gdma_etm.c new file mode 100644 index 000000000000..a550de01927e --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_gdma_etm.c @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_utils.h" +#include "freertos/FreeRTOS.h" +#include "esp_attr.h" +#include "esp_etm.h" +#include "driver/gpio_etm.h" +#include "driver/gpio.h" +#include "esp_async_memcpy.h" + +TEST_CASE("async_memcpy_eof_event", "[etm]") +{ + const uint32_t output_gpio = 1; + // async_memcpy done ---> ETM channel A ---> GPIO toggle + printf("allocate etm channel\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + + printf("allocate GPIO etm task\r\n"); + esp_etm_task_handle_t gpio_task = NULL; + gpio_etm_task_config_t gpio_task_config = { + .action = GPIO_ETM_TASK_ACTION_TOG, + }; + TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task)); + // set gpio number for the gpio etm primitives + TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT, + .pin_bit_mask = 1ULL << output_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + + // put the GPIO into initial state + TEST_ESP_OK(gpio_set_level(output_gpio, 1)); + + printf("install async memcpy context\r\n"); + async_memcpy_t mcp_ctx = NULL; + async_memcpy_config_t config = ASYNC_MEMCPY_DEFAULT_CONFIG(); + TEST_ESP_OK(esp_async_memcpy_install(&config, &mcp_ctx)); + + printf("get async memcpy etm event handle\r\n"); + esp_etm_event_handle_t mcp_event = NULL; + TEST_ESP_OK(esp_async_memcpy_new_etm_event(mcp_ctx, ASYNC_MEMCPY_ETM_EVENT_COPY_DONE, &mcp_event)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, mcp_event, gpio_task)); + printf("enable etm channel\r\n"); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + + TEST_ESP_OK(esp_etm_dump(stdout)); + + const uint32_t buffer_size = 1024; + uint8_t *src_buf = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(src_buf); + uint8_t *dst_buf = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(dst_buf); + + printf("start memcpy\r\n"); + for (int j = 0; j < 19; j++) { + TEST_ESP_OK(esp_async_memcpy(mcp_ctx, dst_buf, src_buf, buffer_size, NULL, NULL)); + } + // simply wait for the last memcpy to finish + vTaskDelay(pdMS_TO_TICKS(1000)); + + // check the final GPIO level + TEST_ASSERT_EQUAL(0, gpio_get_level(output_gpio)); + + // delete etm primitives + TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_task(gpio_task)); + TEST_ESP_OK(esp_etm_del_event(mcp_event)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); + TEST_ESP_OK(esp_async_memcpy_uninstall(mcp_ctx)); + free(src_buf); + free(dst_buf); +} diff --git a/components/esp_hw_support/test_apps/etm/main/test_gpio_etm.c b/components/esp_hw_support/test_apps/etm/main/test_gpio_etm.c new file mode 100644 index 000000000000..ee80ca8a4dde --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_gpio_etm.c @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_utils.h" +#include "esp_attr.h" +#include "driver/gpio_etm.h" +#include "driver/gpio.h" + +TEST_CASE("gpio_etm_self_trigger", "[etm]") +{ + // GPIO any edge ---> EMT channel ---> GPIO toggle + const uint32_t input_gpio = 0; + const uint32_t output_gpio = 1; + printf("allocate etm channels\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a = NULL; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + + printf("allocate GPIO etm event and task\r\n"); + esp_etm_task_handle_t gpio_task = NULL; + esp_etm_event_handle_t gpio_event = NULL; + gpio_etm_event_config_t gpio_event_config = { + .edge = GPIO_ETM_EVENT_EDGE_ANY, + }; + TEST_ESP_OK(gpio_new_etm_event(&gpio_event_config, &gpio_event)); + gpio_etm_task_config_t gpio_task_config = { + .action = GPIO_ETM_TASK_ACTION_TOG, + }; + TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task)); + + // bind GPIO to the event and task + TEST_ESP_OK(gpio_etm_event_bind_gpio(gpio_event, input_gpio)); + TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT, // we want to read the GPIO value, so it should be input and output + .pin_bit_mask = 1ULL << output_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + // set the initial level + TEST_ESP_OK(gpio_set_level(output_gpio, 0)); + + gpio_config_t event_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT, // we want to simulate the edge signal by software, so it should be input and output + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .pin_bit_mask = 1ULL << input_gpio, + }; + TEST_ESP_OK(gpio_config(&event_gpio_config)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, gpio_event, gpio_task)); + + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + + for (int i = 0; i < 10; i++) { + TEST_ESP_OK(gpio_set_level(input_gpio, i & 0x01)); + vTaskDelay(pdMS_TO_TICKS(100)); + } + // check the final level + TEST_ASSERT_EQUAL(1, gpio_get_level(output_gpio)); + + vTaskDelay(pdMS_TO_TICKS(100)); + + for (int i = 0; i < 5; i++) { + TEST_ESP_OK(gpio_set_level(input_gpio, i & 0x01)); + vTaskDelay(pdMS_TO_TICKS(100)); + } + // check the final level + TEST_ASSERT_EQUAL(0, gpio_get_level(output_gpio)); + + // delete gpio etm task without remove all bounded GPIOs should fail + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_etm_del_task(gpio_task)); + // remove unrelated GPIO from the task should fail + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, gpio_etm_task_rm_gpio(gpio_task, 10)); + + // delete etm primitives + TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_task(gpio_task)); + TEST_ESP_OK(esp_etm_del_event(gpio_event)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); +} diff --git a/components/esp_hw_support/test_apps/etm/main/test_gptimer_etm.c b/components/esp_hw_support/test_apps/etm/main/test_gptimer_etm.c new file mode 100644 index 000000000000..c9caa57224a2 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_gptimer_etm.c @@ -0,0 +1,440 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_utils.h" +#include "esp_attr.h" +#include "driver/gptimer.h" +#include "driver/gpio_etm.h" +#include "driver/gpio.h" + +static bool on_gptimer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) +{ + return false; +} + +TEST_CASE("gptimer_etm_alarm_event_with_interrupt_enabled", "[etm]") +{ + const uint32_t output_gpio = 1; + // GPTimer alarm ---> ETM channel A ---> GPIO toggle + printf("allocate etm channel\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + + printf("allocate GPIO etm task\r\n"); + esp_etm_task_handle_t gpio_task = NULL; + gpio_etm_task_config_t gpio_task_config = { + .action = GPIO_ETM_TASK_ACTION_TOG, + }; + TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task)); + // set gpio number for the gpio etm primitives + TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << output_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + + printf("create a gptimer\r\n"); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + + printf("get gptimer etm event handle\r\n"); + esp_etm_event_handle_t gptimer_event = NULL; + TEST_ESP_OK(gptimer_new_etm_event(gptimer, GPTIMER_ETM_EVENT_ALARM_MATCH, &gptimer_event)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, gptimer_event, gpio_task)); + + printf("enable etm channel\r\n"); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + + printf("set timer alarm action\r\n"); + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = 100, // 100us per alarm event + .flags.auto_reload_on_alarm = true, + }; + TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config)); + + printf("register alarm callback\r\n"); + gptimer_event_callbacks_t cbs = { + .on_alarm = on_gptimer_alarm_cb, + }; + TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, NULL)); + + printf("enable and start timer\r\n"); + TEST_ESP_OK(gptimer_enable(gptimer)); + TEST_ESP_OK(gptimer_start(gptimer)); + + // delay sometime for us to view the waveform, should see a 5KHz square waveform + vTaskDelay(pdMS_TO_TICKS(1000)); + + // delete gptimer + TEST_ESP_OK(gptimer_stop(gptimer)); + TEST_ESP_OK(gptimer_disable(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); + + // delete etm primitives + TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio)); + TEST_ESP_OK(esp_etm_del_task(gpio_task)); + TEST_ESP_OK(esp_etm_del_event(gptimer_event)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); +} + +TEST_CASE("gptimer_etm_alarm_event_without_interrupt", "[etm]") +{ + const uint32_t output_gpio = 1; + // GPTimer alarm ---> ETM channel A ---> GPIO toggle + // GPTimer alarm ---> ETM channel B ---> GPTimer alarm reenable + printf("allocate etm channel\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a, etm_channel_b; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_b)); + + printf("allocate GPIO etm task\r\n"); + esp_etm_task_handle_t gpio_task = NULL; + gpio_etm_task_config_t gpio_task_config = { + .action = GPIO_ETM_TASK_ACTION_TOG, + }; + TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task)); + // set gpio number for the gpio etm primitives + TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << output_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + + printf("create a gptimer\r\n"); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + + printf("get gptimer etm event and task handle\r\n"); + esp_etm_event_handle_t gptimer_event = NULL; + TEST_ESP_OK(gptimer_new_etm_event(gptimer, GPTIMER_ETM_EVENT_ALARM_MATCH, &gptimer_event)); + esp_etm_task_handle_t gptimer_task = NULL; + TEST_ESP_OK(gptimer_new_etm_task(gptimer, GPTIMER_ETM_TASK_EN_ALARM, &gptimer_task)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, gptimer_event, gpio_task)); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_b, gptimer_event, gptimer_task)); + + printf("enable etm channel\r\n"); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_b)); + + printf("set timer alarm action\r\n"); + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = 100, // 100us per alarm event + .flags.auto_reload_on_alarm = true, + }; + TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config)); + + printf("enable and start timer\r\n"); + TEST_ESP_OK(gptimer_enable(gptimer)); + TEST_ESP_OK(gptimer_start(gptimer)); + + // delay sometime for us to view the waveform, should see a 5KHz square waveform + vTaskDelay(pdMS_TO_TICKS(1000)); + + // delete gptimer + TEST_ESP_OK(gptimer_stop(gptimer)); + TEST_ESP_OK(gptimer_disable(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); + + // delete etm primitives + TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio)); + TEST_ESP_OK(esp_etm_del_task(gpio_task)); + TEST_ESP_OK(esp_etm_del_task(gptimer_task)); + TEST_ESP_OK(esp_etm_del_event(gptimer_event)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_b)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_b)); +} + +TEST_CASE("gptimer_auto_reload_by_etm", "[etm]") +{ + const uint32_t output_gpio = 1; + // GPTimer alarm ---> ETM channel A ---> GPIO toggle + // GPTimer alarm ---> ETM channel B ---> GPTimer alarm reenable + // GPTimer alarm ---> ETM channel C ---> GPTimer reload + printf("allocate etm channel\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a, etm_channel_b, etm_channel_c; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_b)); + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_c)); + + printf("allocate GPIO etm task\r\n"); + esp_etm_task_handle_t gpio_task = NULL; + gpio_etm_task_config_t gpio_task_config = { + .action = GPIO_ETM_TASK_ACTION_TOG, + }; + TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task)); + // set gpio number for the gpio etm primitives + TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << output_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + + printf("create a gptimer\r\n"); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + + printf("get gptimer etm event and task handle\r\n"); + esp_etm_event_handle_t gptimer_event_alarm = NULL; + TEST_ESP_OK(gptimer_new_etm_event(gptimer, GPTIMER_ETM_EVENT_ALARM_MATCH, &gptimer_event_alarm)); + esp_etm_task_handle_t gptimer_task_en_alarm = NULL; + TEST_ESP_OK(gptimer_new_etm_task(gptimer, GPTIMER_ETM_TASK_EN_ALARM, &gptimer_task_en_alarm)); + esp_etm_task_handle_t gptimer_task_reload = NULL; + TEST_ESP_OK(gptimer_new_etm_task(gptimer, GPTIMER_ETM_TASK_RELOAD, &gptimer_task_reload)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, gptimer_event_alarm, gpio_task)); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_b, gptimer_event_alarm, gptimer_task_en_alarm)); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_c, gptimer_event_alarm, gptimer_task_reload)); + + printf("enable etm channel\r\n"); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_b)); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_c)); + + printf("dump the etm channel usage\r\n"); + TEST_ESP_OK(esp_etm_dump(stdout)); + + printf("set timer alarm action\r\n"); + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = 100, // 100us per alarm event + .flags.auto_reload_on_alarm = false, // reload will be done by ETM channel C + }; + TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config)); + + printf("enable and start timer\r\n"); + TEST_ESP_OK(gptimer_enable(gptimer)); + TEST_ESP_OK(gptimer_start(gptimer)); + + // delay sometime for us to view the waveform, should see a 5KHz square waveform + vTaskDelay(pdMS_TO_TICKS(1000)); + + // delete gptimer + TEST_ESP_OK(gptimer_stop(gptimer)); + TEST_ESP_OK(gptimer_disable(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); + + // delete etm primitives + TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio)); + TEST_ESP_OK(esp_etm_del_task(gpio_task)); + TEST_ESP_OK(esp_etm_del_task(gptimer_task_en_alarm)); + TEST_ESP_OK(esp_etm_del_task(gptimer_task_reload)); + TEST_ESP_OK(esp_etm_del_event(gptimer_event_alarm)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_b)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_c)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_b)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_c)); +} + +TEST_CASE("gptimer_etm_task_capture", "[etm]") +{ + const uint32_t input_gpio = 0; + // GPIO Posedge ---> ETM channel A ---> GPTimer capture + printf("allocate etm channel\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + + printf("allocate GPIO etm event\r\n"); + esp_etm_event_handle_t gpio_event = NULL; + gpio_etm_event_config_t gpio_event_config = { + .edge = GPIO_ETM_EVENT_EDGE_POS, + }; + TEST_ESP_OK(gpio_new_etm_event(&gpio_event_config, &gpio_event)); + // set gpio number for the gpio etm primitives + TEST_ESP_OK(gpio_etm_event_bind_gpio(gpio_event, input_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT, // we want to simulate the edge signal by software, so it should be input and output + .pin_bit_mask = 1ULL << input_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + TEST_ESP_OK(gpio_set_level(input_gpio, 0)); + + printf("create a gptimer\r\n"); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + + printf("get gptimer etm task handle\r\n"); + esp_etm_task_handle_t gptimer_task = NULL; + TEST_ESP_OK(gptimer_new_etm_task(gptimer, GPTIMER_ETM_TASK_CAPTURE, &gptimer_task)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, gpio_event, gptimer_task)); + + printf("enable etm channel\r\n"); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + + printf("enable and start gptimer\r\n"); + TEST_ESP_OK(gptimer_enable(gptimer)); + TEST_ESP_OK(gptimer_start(gptimer)); + + vTaskDelay(pdMS_TO_TICKS(500)); + + // simulate the edge signal by software + TEST_ESP_OK(gpio_set_level(input_gpio, 1)); + TEST_ESP_OK(gpio_set_level(input_gpio, 0)); + + uint64_t capture_value = 0; + TEST_ESP_OK(gptimer_get_captured_count(gptimer, &capture_value)); + printf("capture value: %llu\r\n", capture_value); + // should be around 500us + TEST_ASSERT_UINT_WITHIN(1000, 500000, capture_value); + + // delete gptimer + TEST_ESP_OK(gptimer_stop(gptimer)); + TEST_ESP_OK(gptimer_disable(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); + + // delete etm primitives + TEST_ESP_OK(esp_etm_del_task(gptimer_task)); + TEST_ESP_OK(esp_etm_del_event(gpio_event)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); +} + +TEST_CASE("gptimer_start_stop_by_etm_task", "[etm]") +{ + const uint32_t input_gpio = 0; + // GPIO pos edge ---> ETM channel A ---> GPTimer start + // GPIO neg edge ---> ETM channel B ---> GPTimer stop + printf("allocate etm channel\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a, etm_channel_b; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_b)); + + printf("allocate GPIO etm events\r\n"); + esp_etm_event_handle_t gpio_event_pos, gpio_event_neg; + gpio_etm_event_config_t gpio_event_config = { + .edge = GPIO_ETM_EVENT_EDGE_POS, + }; + TEST_ESP_OK(gpio_new_etm_event(&gpio_event_config, &gpio_event_pos)); + gpio_event_config.edge = GPIO_ETM_EVENT_EDGE_NEG; + TEST_ESP_OK(gpio_new_etm_event(&gpio_event_config, &gpio_event_neg)); + + // set gpio number for the gpio etm primitives + TEST_ESP_OK(gpio_etm_event_bind_gpio(gpio_event_pos, input_gpio)); + TEST_ESP_OK(gpio_etm_event_bind_gpio(gpio_event_neg, input_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT, // we want to simulate the edge signal by software, so it should be input and output + .pin_bit_mask = 1ULL << input_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + + // put the gpio into initial state + TEST_ESP_OK(gpio_set_level(input_gpio, 0)); + + printf("create a gptimer\r\n"); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + + printf("get gptimer etm task handle\r\n"); + esp_etm_task_handle_t gptimer_task_start, gptimer_task_stop; + TEST_ESP_OK(gptimer_new_etm_task(gptimer, GPTIMER_ETM_TASK_START_COUNT, &gptimer_task_start)); + TEST_ESP_OK(gptimer_new_etm_task(gptimer, GPTIMER_ETM_TASK_STOP_COUNT, &gptimer_task_stop)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, gpio_event_pos, gptimer_task_start)); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_b, gpio_event_neg, gptimer_task_stop)); + + printf("enable etm channel\r\n"); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_b)); + + printf("enable timer\r\n"); + TEST_ESP_OK(gptimer_enable(gptimer)); + + // trigger an pos-edge, this should start the gptimer + TEST_ESP_OK(gpio_set_level(input_gpio, 1)); + vTaskDelay(pdMS_TO_TICKS(500)); + uint64_t cur_count_val = 0; + TEST_ESP_OK(gptimer_get_raw_count(gptimer, &cur_count_val)); + printf("cur_count_val: %llu\r\n", cur_count_val); + TEST_ASSERT_UINT_WITHIN(900, 500000, cur_count_val); + + // trigger an neg-edge, this should stop the gptimer + TEST_ESP_OK(gpio_set_level(input_gpio, 0)); + uint64_t count_val_0 = 0; + TEST_ESP_OK(gptimer_get_raw_count(gptimer, &count_val_0)); + vTaskDelay(pdMS_TO_TICKS(500)); + uint64_t count_val_1 = 0; + TEST_ESP_OK(gptimer_get_raw_count(gptimer, &count_val_1)); + TEST_ASSERT_EQUAL(count_val_0, count_val_1); + + // delete gptimer + TEST_ESP_OK(gptimer_disable(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); + + // delete etm primitives + TEST_ESP_OK(esp_etm_del_task(gptimer_task_start)); + TEST_ESP_OK(esp_etm_del_task(gptimer_task_stop)); + TEST_ESP_OK(esp_etm_del_event(gpio_event_pos)); + TEST_ESP_OK(esp_etm_del_event(gpio_event_neg)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_b)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_b)); +} diff --git a/components/esp_hw_support/test_apps/etm/main/test_systimer_etm.c b/components/esp_hw_support/test_apps/etm/main/test_systimer_etm.c new file mode 100644 index 000000000000..69bf008e008c --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/main/test_systimer_etm.c @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_utils.h" +#include "freertos/FreeRTOS.h" +#include "esp_attr.h" +#include "esp_etm.h" +#include "driver/gpio_etm.h" +#include "driver/gpio.h" +#include "esp_timer.h" +#include "esp_systick_etm.h" + +TEST_CASE("rtos_systick_etm_event", "[etm]") +{ + // systimer alarm ---> EMT channel ---> GPIO toggle + const uint32_t output_gpio = 1; + printf("allocate etm channels\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a = NULL; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + + printf("allocate GPIO etm task\r\n"); + esp_etm_task_handle_t gpio_task = NULL; + gpio_etm_task_config_t gpio_task_config = { + .action = GPIO_ETM_TASK_ACTION_TOG, + }; + TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task)); + + // bind GPIO to the task + TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << output_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + + printf("acquire systick etm event\r\n"); + esp_etm_event_handle_t systick_event = NULL; + TEST_ESP_OK(esp_systick_new_etm_alarm_event(0, &systick_event)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, systick_event, gpio_task)); + + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + + // should see a 500Hz square wave on the GPIO (if RTOS systick is set to 1000Hz) + vTaskDelay(pdMS_TO_TICKS(1000)); + + // delete etm primitives + TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio)); + TEST_ESP_OK(esp_etm_del_task(gpio_task)); + TEST_ESP_OK(esp_etm_del_event(systick_event)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); +} + +static void periodic_timer_callback(void *arg) +{ +} + +TEST_CASE("esp_timer_etm_event", "[etm]") +{ + // systimer alarm ---> EMT channel ---> GPIO toggle + const uint32_t output_gpio = 1; + printf("allocate etm channels\r\n"); + esp_etm_channel_config_t etm_config = {}; + esp_etm_channel_handle_t etm_channel_a = NULL; + TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a)); + + printf("allocate GPIO etm task\r\n"); + esp_etm_task_handle_t gpio_task = NULL; + gpio_etm_task_config_t gpio_task_config = { + .action = GPIO_ETM_TASK_ACTION_TOG, + }; + TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task)); + + // bind GPIO to the task + TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio)); + + printf("initialize gpio\r\n"); + gpio_config_t task_gpio_config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT_OUTPUT, + .pin_bit_mask = 1ULL << output_gpio, + }; + TEST_ESP_OK(gpio_config(&task_gpio_config)); + + // put the GPIO into initial state + TEST_ESP_OK(gpio_set_level(output_gpio, 1)); + + printf("acquire esp_timer etm event\r\n"); + esp_etm_event_handle_t esp_timer_event = NULL; + TEST_ESP_OK(esp_timer_new_etm_alarm_event(&esp_timer_event)); + + printf("connect event and task to the channel\r\n"); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, esp_timer_event, gpio_task)); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a)); + + printf("create a periodic esp_timer\r\b"); + const esp_timer_create_args_t periodic_timer_args = { + .callback = periodic_timer_callback, + .name = "periodic" + }; + esp_timer_handle_t periodic_timer = NULL; + TEST_ESP_OK(esp_timer_create(&periodic_timer_args, &periodic_timer)); + TEST_ESP_OK(esp_timer_start_periodic(periodic_timer, 500000)); + + // should see a 1Hz square wave on the GPIO + vTaskDelay(pdMS_TO_TICKS(1200)); + + // check the final GPIO level + TEST_ASSERT_EQUAL(1, gpio_get_level(output_gpio)); + + TEST_ESP_OK(esp_timer_stop(periodic_timer)); + TEST_ESP_OK(esp_timer_delete(periodic_timer)); + + // delete etm primitives + TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio)); + TEST_ESP_OK(esp_etm_del_task(gpio_task)); + TEST_ESP_OK(esp_etm_del_event(esp_timer_event)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel_a)); +} diff --git a/components/esp_hw_support/test_apps/etm/pytest_etm.py b/components/esp_hw_support/test_apps/etm/pytest_etm.py new file mode 100644 index 000000000000..6a795fe95d88 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/pytest_etm.py @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_etm(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('*') + dut.expect_unity_test_output() diff --git a/components/esp_hw_support/test_apps/etm/sdkconfig.ci.release b/components/esp_hw_support/test_apps/etm/sdkconfig.ci.release new file mode 100644 index 000000000000..91d93f163e62 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/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/esp_hw_support/test_apps/etm/sdkconfig.defaults b/components/esp_hw_support/test_apps/etm/sdkconfig.defaults new file mode 100644 index 000000000000..b308cb2ddda0 --- /dev/null +++ b/components/esp_hw_support/test_apps/etm/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/esp_rom/esp32c6/ld/esp32c6.rom.ld b/components/esp_rom/esp32c6/ld/esp32c6.rom.ld index 88adb38f8310..5b5cf26a05e0 100644 --- a/components/esp_rom/esp32c6/ld/esp32c6.rom.ld +++ b/components/esp_rom/esp32c6/ld/esp32c6.rom.ld @@ -192,8 +192,10 @@ wdt_hal_is_enabled = 0x400003bc; ***************************************/ /* Functions */ -systimer_hal_init = 0x400003c0; -systimer_hal_deinit = 0x400003c4; +/* The following ROM functions are commented out because they're patched in the esp_rom_systimer.c */ +/* systimer_hal_init = 0x400003c0; */ +/* systimer_hal_deinit = 0x400003c4; */ + systimer_hal_set_tick_rate_ops = 0x400003c8; systimer_hal_get_counter_value = 0x400003cc; systimer_hal_get_time = 0x400003d0; diff --git a/components/esp_rom/patches/esp_rom_systimer.c b/components/esp_rom/patches/esp_rom_systimer.c index 87cad1e31375..bf930139a195 100644 --- a/components/esp_rom/patches/esp_rom_systimer.c +++ b/components/esp_rom/patches/esp_rom_systimer.c @@ -64,4 +64,20 @@ void systimer_hal_counter_value_advance(systimer_hal_context_t *hal, uint32_t co } #endif // CONFIG_IDF_TARGET_ESP32C2 +#if CONFIG_IDF_TARGET_ESP32C6 +void systimer_hal_init(systimer_hal_context_t *hal) +{ + hal->dev = &SYSTIMER; + systimer_ll_enable_clock(hal->dev, true); + systimer_ll_enable_etm(&SYSTIMER, true); +} + +void systimer_hal_deinit(systimer_hal_context_t *hal) +{ + systimer_ll_enable_etm(&SYSTIMER, false); + systimer_ll_enable_clock(hal->dev, false); + hal->dev = NULL; +} +#endif // CONFIG_IDF_TARGET_ESP32C6 + #endif // CONFIG_HAL_SYSTIMER_USE_ROM_IMPL diff --git a/components/esp_system/CMakeLists.txt b/components/esp_system/CMakeLists.txt index d8f6072f5f21..d36218768326 100644 --- a/components/esp_system/CMakeLists.txt +++ b/components/esp_system/CMakeLists.txt @@ -49,6 +49,10 @@ else() list(APPEND srcs "eh_frame_parser.c") endif() + if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM) + list(APPEND srcs "systick_etm.c") + endif() + idf_component_register(SRCS "${srcs}" INCLUDE_DIRS include PRIV_REQUIRES spi_flash esp_timer diff --git a/components/esp_system/include/esp_systick_etm.h b/components/esp_system/include/esp_systick_etm.h new file mode 100644 index 000000000000..e593fbc8e045 --- /dev/null +++ b/components/esp_system/include/esp_systick_etm.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_etm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the ETM event handle of systick hardware's alarm/heartbeat event + * + * @note The created ETM event object can be deleted later by calling `esp_etm_del_event` + * + * @param[in] core_id CPU core ID + * @param[out] out_event Returned ETM event handle + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t esp_systick_new_etm_alarm_event(int core_id, esp_etm_event_handle_t *out_event); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_system/systick_etm.c b/components/esp_system/systick_etm.c new file mode 100644 index 000000000000..45327eae9e3b --- /dev/null +++ b/components/esp_system/systick_etm.c @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_systick_etm.h" +#include "soc/soc_caps.h" +#include "soc/soc_etm_source.h" +#include "hal/systimer_ll.h" +#include "esp_private/etm_interface.h" + +#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +static const char *TAG = "systick-etm"; + +static esp_err_t systick_etm_event_del(esp_etm_event_t *event) +{ + free(event); + return ESP_OK; +} + +esp_err_t esp_systick_new_etm_alarm_event(int core_id, esp_etm_event_handle_t *out_event) +{ + esp_etm_event_t *event = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(out_event && core_id < SOC_CPU_CORES_NUM, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + event = heap_caps_calloc(1, sizeof(esp_etm_event_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(event, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM event"); + + // fill the ETM event object + uint32_t event_id = SYSTIMER_EVT_CNT_CMP0 + SYSTIMER_LL_ALARM_OS_TICK_CORE0 + core_id; + event->event_id = event_id; + event->trig_periph = ETM_TRIG_PERIPH_SYSTIMER; + event->del = systick_etm_event_del; + *out_event = event; + return ESP_OK; + +err: + if (event) { + systick_etm_event_del(event); + } + return ret; +} diff --git a/components/esp_timer/CMakeLists.txt b/components/esp_timer/CMakeLists.txt index 02a93c13dc31..f644d63f438b 100644 --- a/components/esp_timer/CMakeLists.txt +++ b/components/esp_timer/CMakeLists.txt @@ -10,6 +10,10 @@ elseif(CONFIG_ESP_TIMER_IMPL_SYSTIMER) list(APPEND srcs "src/esp_timer_impl_systimer.c") endif() +if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM) + list(APPEND srcs "src/esp_timer_etm.c") +endif() + idf_component_register(SRCS "${srcs}" INCLUDE_DIRS include PRIV_INCLUDE_DIRS private_include diff --git a/components/esp_timer/include/esp_timer.h b/components/esp_timer/include/esp_timer.h index 1fd8e049c4aa..7ee2c21b91b1 100644 --- a/components/esp_timer/include/esp_timer.h +++ b/components/esp_timer/include/esp_timer.h @@ -34,6 +34,7 @@ #include #include #include "esp_err.h" +#include "esp_etm.h" #include "sdkconfig.h" #ifdef __cplusplus @@ -311,6 +312,21 @@ void esp_timer_isr_dispatch_need_yield(void); */ bool esp_timer_is_active(esp_timer_handle_t timer); +/** + * @brief Get the ETM event handle of esp_timer underlying alarm event + * + * @note The created ETM event object can be deleted later by calling `esp_etm_del_event` + * + * @note The ETM event is generated by the underlying hardware -- systimer, + * therefore, if the esp_timer is not clocked by systimer, then no ETM event will be generated. + * + * @param[out] out_event Returned ETM event handle + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t esp_timer_new_etm_alarm_event(esp_etm_event_handle_t *out_event); + #ifdef __cplusplus } #endif diff --git a/components/esp_timer/src/esp_timer_etm.c b/components/esp_timer/src/esp_timer_etm.c new file mode 100644 index 000000000000..380382158202 --- /dev/null +++ b/components/esp_timer/src/esp_timer_etm.c @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_timer.h" +#include "soc/soc_etm_source.h" +#include "hal/systimer_ll.h" +#include "esp_private/etm_interface.h" + +#define ETM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +static const char *TAG = "esptimer-etm"; + +static esp_err_t esp_timer_etm_event_del(esp_etm_event_t *event) +{ + free(event); + return ESP_OK; +} + +esp_err_t esp_timer_new_etm_alarm_event(esp_etm_event_handle_t *out_event) +{ + esp_etm_event_t *event = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(out_event, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + event = heap_caps_calloc(1, sizeof(esp_etm_event_t), ETM_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(event, ESP_ERR_NO_MEM, err, TAG, "no memory for ETM event"); + + // fill the ETM event object + uint32_t event_id = SYSTIMER_EVT_CNT_CMP0 + SYSTIMER_LL_ALARM_CLOCK; + event->event_id = event_id; + event->trig_periph = ETM_TRIG_PERIPH_SYSTIMER; + event->del = esp_timer_etm_event_del; + *out_event = event; + return ESP_OK; + +err: + if (event) { + esp_timer_etm_event_del(event); + } + return ret; +} diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index 87bd8d0a5bc4..bcf4d99e0456 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -85,6 +85,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "emac_hal.c") endif() + if(CONFIG_SOC_ETM_SUPPORTED) + list(APPEND srcs "etm_hal.c") + endif() + if(CONFIG_SOC_ADC_DMA_SUPPORTED) list(APPEND srcs "adc_hal.c") endif() diff --git a/components/hal/esp32c6/include/hal/clk_gate_ll.h b/components/hal/esp32c6/include/hal/clk_gate_ll.h index 10c5a999ae63..5d664bbea9ce 100644 --- a/components/hal/esp32c6/include/hal/clk_gate_ll.h +++ b/components/hal/esp32c6/include/hal/clk_gate_ll.h @@ -56,6 +56,8 @@ static inline uint32_t periph_ll_get_clk_en_mask(periph_module_t periph) return PCR_GDMA_CLK_EN; case PERIPH_MCPWM0_MODULE: return PCR_PWM_CLK_EN; + case PERIPH_ETM_MODULE: + return PCR_ETM_CLK_EN; case PERIPH_AES_MODULE: return PCR_AES_CLK_EN; case PERIPH_SHA_MODULE: @@ -128,6 +130,8 @@ static inline uint32_t periph_ll_get_rst_en_mask(periph_module_t periph, bool en return PCR_GDMA_RST_EN; case PERIPH_MCPWM0_MODULE: return PCR_PWM_RST_EN; + case PERIPH_ETM_MODULE: + return PCR_ETM_RST_EN; case PERIPH_ECC_MODULE: return PCR_ECC_RST_EN; case PERIPH_TEMPSENSOR_MODULE: @@ -224,6 +228,8 @@ static uint32_t periph_ll_get_clk_en_reg(periph_module_t periph) return PCR_GDMA_CONF_REG; case PERIPH_MCPWM0_MODULE: return PCR_PWM_CONF_REG; + case PERIPH_ETM_MODULE: + return PCR_ETM_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: @@ -282,6 +288,8 @@ static uint32_t periph_ll_get_rst_en_reg(periph_module_t periph) return PCR_GDMA_CONF_REG; case PERIPH_MCPWM0_MODULE: return PCR_PWM_CONF_REG; + case PERIPH_ETM_MODULE: + return PCR_ETM_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: diff --git a/components/hal/esp32c6/include/hal/etm_ll.h b/components/hal/esp32c6/include/hal/etm_ll.h new file mode 100644 index 000000000000..9d95b485c271 --- /dev/null +++ b/components/hal/esp32c6/include/hal/etm_ll.h @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Note that most of the register operations in this layer are non-atomic operations. + +#pragma once + +#include +#include "hal/assert.h" +#include "hal/misc.h" +#include "soc/soc_etm_struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enable the clock for ETM module + * + * @param hw ETM register base address + * @param enable true to enable, false to disable + */ +static inline void etm_ll_enable_clock(soc_etm_dev_t *hw, bool enable) +{ + hw->clk_en.clk_en = enable; +} + +/** + * @brief Enable ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + */ +static inline void etm_ll_enable_channel(soc_etm_dev_t *hw, uint32_t chan) +{ + if (chan < 32) { + hw->ch_ena_ad0_set.val = 1 << chan; + } else { + hw->ch_ena_ad1_set.val = 1 << (chan - 32); + } +} + +/** + * @brief Disable ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + */ +static inline void etm_ll_disable_channel(soc_etm_dev_t *hw, uint32_t chan) +{ + if (chan < 32) { + hw->ch_ena_ad0_clr.val = 1 << chan; + } else { + hw->ch_ena_ad1_clr.val = 1 << (chan - 32); + } +} + +/** + * @brief Check whether the ETM channel is enabled or not + * + * @param hw ETM register base address + * @param chan Channel ID + * @return true if the channel is enabled, false otherwise + */ +static inline bool etm_ll_is_channel_enabled(soc_etm_dev_t *hw, uint32_t chan) +{ + if (chan < 32) { + return hw->ch_ena_ad0.val & (1 << chan); + } else { + return hw->ch_ena_ad1.val & (1 << (chan - 32)); + } +} + +/** + * @brief Set the input event for the ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + * @param event Event ID + */ +static inline void etm_ll_channel_set_event(soc_etm_dev_t *hw, uint32_t chan, uint32_t event) +{ + hw->channel[chan].evt_id.evt_id = event; +} + +/** + * @brief Set the output task for the ETM channel + * + * @param hw ETM register base address + * @param chan Channel ID + * @param task Task ID + */ +static inline void etm_ll_channel_set_task(soc_etm_dev_t *hw, uint32_t chan, uint32_t task) +{ + hw->channel[chan].task_id.task_id = task; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/esp32c6/include/hal/gdma_ll.h b/components/hal/esp32c6/include/hal/gdma_ll.h index f938c1600fc9..719c16f68f02 100644 --- a/components/hal/esp32c6/include/hal/gdma_ll.h +++ b/components/hal/esp32c6/include/hal/gdma_ll.h @@ -8,8 +8,10 @@ #include /* Required for NULL constant */ #include #include +#include "hal/gdma_types.h" #include "soc/gdma_struct.h" #include "soc/gdma_reg.h" +#include "soc/soc_etm_source.h" #ifdef __cplusplus extern "C" { @@ -34,6 +36,50 @@ extern "C" { #define GDMA_LL_EVENT_RX_SUC_EOF (1<<1) #define GDMA_LL_EVENT_RX_DONE (1<<0) +#define GDMA_LL_TX_ETM_EVENT_TABLE(group, chan, event) \ + (uint32_t[1][3][GDMA_ETM_EVENT_MAX]){{{ \ + [GDMA_ETM_EVENT_EOF] = GDMA_EVT_OUT_EOF_CH0, \ + }, \ + { \ + [GDMA_ETM_EVENT_EOF] = GDMA_EVT_OUT_EOF_CH1, \ + }, \ + { \ + [GDMA_ETM_EVENT_EOF] = GDMA_EVT_OUT_EOF_CH2, \ + }}}[group][chan][event] + +#define GDMA_LL_RX_ETM_EVENT_TABLE(group, chan, event) \ + (uint32_t[1][3][GDMA_ETM_EVENT_MAX]){{{ \ + [GDMA_ETM_EVENT_EOF] = GDMA_EVT_IN_SUC_EOF_CH0, \ + }, \ + { \ + [GDMA_ETM_EVENT_EOF] = GDMA_EVT_IN_SUC_EOF_CH1, \ + }, \ + { \ + [GDMA_ETM_EVENT_EOF] = GDMA_EVT_IN_SUC_EOF_CH2, \ + }}}[group][chan][event] + +#define GDMA_LL_TX_ETM_TASK_TABLE(group, chan, task) \ + (uint32_t[1][3][GDMA_ETM_TASK_MAX]){{{ \ + [GDMA_ETM_TASK_START] = GDMA_TASK_OUT_START_CH0, \ + }, \ + { \ + [GDMA_ETM_TASK_START] = GDMA_TASK_OUT_START_CH1, \ + }, \ + { \ + [GDMA_ETM_TASK_START] = GDMA_TASK_OUT_START_CH2, \ + }}}[group][chan][task] + +#define GDMA_LL_RX_ETM_TASK_TABLE(group, chan, task) \ + (uint32_t[1][3][GDMA_ETM_TASK_MAX]){{{ \ + [GDMA_ETM_TASK_START] = GDMA_TASK_IN_START_CH0, \ + }, \ + { \ + [GDMA_ETM_TASK_START] = GDMA_TASK_IN_START_CH1, \ + }, \ + { \ + [GDMA_ETM_TASK_START] = GDMA_TASK_IN_START_CH2, \ + }}}[group][chan][task] + ///////////////////////////////////// Common ///////////////////////////////////////// /** * @brief Enable DMA channel M2M mode (TX channel n forward data to RX channel n), disabled by default @@ -42,9 +88,9 @@ static inline void gdma_ll_enable_m2m_mode(gdma_dev_t *dev, uint32_t channel, bo { dev->channel[channel].in.in_conf0.mem_trans_en = enable; if (enable) { - // to enable m2m mode, the tx chan has to be the same to rx chan, and set to a valid value - dev->channel[channel].in.in_peri_sel.peri_in_sel = 0; - dev->channel[channel].out.out_peri_sel.peri_out_sel = 0; + // to enable m2m mode, the tx chan has to be the same to rx chan, and set to a dummy value + dev->channel[channel].in.in_peri_sel.peri_in_sel = 1; + dev->channel[channel].out.out_peri_sel.peri_out_sel = 1; } } @@ -260,6 +306,16 @@ static inline void gdma_ll_rx_connect_to_periph(gdma_dev_t *dev, uint32_t channe dev->channel[channel].in.in_peri_sel.peri_in_sel = periph_id; } +/** + * @brief Whether to enable the ETM subsystem for RX channel + * + * @note When ETM_EN is 1, only ETM tasks can be used to configure the transfer direction and enable the channel. + */ +static inline void gdma_ll_rx_enable_etm_task(gdma_dev_t *dev, uint32_t channel, bool enable) +{ + dev->channel[channel].in.in_conf0.in_etm_en = enable; +} + ///////////////////////////////////// TX ///////////////////////////////////////// /** * @brief Get DMA TX channel interrupt status word @@ -463,6 +519,16 @@ static inline void gdma_ll_tx_connect_to_periph(gdma_dev_t *dev, uint32_t channe dev->channel[channel].out.out_peri_sel.peri_out_sel = periph_id; } +/** + * @brief Whether to enable the ETM subsystem for TX channel + * + * @note When ETM_EN is 1, only ETM tasks can be used to configure the transfer direction and enable the channel. + */ +static inline void gdma_ll_tx_enable_etm_task(gdma_dev_t *dev, uint32_t channel, bool enable) +{ + dev->channel[channel].out.out_conf0.out_etm_en = enable; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32c6/include/hal/gpio_etm_ll.h b/components/hal/esp32c6/include/hal/gpio_etm_ll.h new file mode 100644 index 000000000000..6d380b27aede --- /dev/null +++ b/components/hal/esp32c6/include/hal/gpio_etm_ll.h @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Note that most of the register operations in this layer are non-atomic operations. + +#pragma once + +#include +#include "hal/assert.h" +#include "hal/misc.h" +#include "soc/gpio_ext_struct.h" +#include "soc/soc_etm_source.h" + +#define GPIO_LL_ETM_EVENT_ID_POS_EDGE(ch) (GPIO_EVT_CH0_RISE_EDGE + (ch)) +#define GPIO_LL_ETM_EVENT_ID_NEG_EDGE(ch) (GPIO_EVT_CH0_FALL_EDGE + (ch)) +#define GPIO_LL_ETM_EVENT_ID_ANY_EDGE(ch) (GPIO_EVT_CH0_ANY_EDGE + (ch)) + +#define GPIO_LL_ETM_TASK_ID_SET(ch) (GPIO_TASK_CH0_SET + (ch)) +#define GPIO_LL_ETM_TASK_ID_CLR(ch) (GPIO_TASK_CH0_CLEAR + (ch)) +#define GPIO_LL_ETM_TASK_ID_TOG(ch) (GPIO_TASK_CH0_TOGGLE + (ch)) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set which GPIO to be bounded to the event channel + * + * @param dev Register base address + * @param chan Channel number + * @param gpio_num GPIO number + */ +static inline void gpio_ll_etm_event_channel_set_gpio(gpio_etm_dev_t *dev, uint32_t chan, uint32_t gpio_num) +{ + dev->event_chn_cfg[chan].etm_ch0_event_sel = gpio_num; +} + +/** + * @brief Wether to enable the event channel + * + * @param dev Register base address + * @param chan Channel number + * @param enable True to enable, false to disable + */ +static inline void gpio_ll_etm_enable_event_channel(gpio_etm_dev_t *dev, uint32_t chan, bool enable) +{ + dev->event_chn_cfg[chan].etm_ch0_event_en = enable; +} + +/** + * @brief Set which GPIO to be bounded to the task channel + * + * @note One channel can be bounded to multiple different GPIOs + * + * @param dev Register base address + * @param chan Channel number + * @param gpio_num GPIO number + */ +static inline void gpio_ll_etm_gpio_set_task_channel(gpio_etm_dev_t *dev, uint32_t gpio_num, uint32_t chan) +{ + int g_p = gpio_num / 4; + int g_idx = gpio_num % 4; + uint32_t reg_val = dev->etm_task_pn_cfg[g_p].val; + reg_val &= ~(0x07 << (g_idx * 8 + 1)); + reg_val |= ((chan & 0x07) << (g_idx * 8 + 1)); + dev->etm_task_pn_cfg[g_p].val = reg_val; +} + +/** + * @brief Wether to enable the GPIO to be managed by the task channel + * + * @param dev Register base address + * @param gpio_num GPIO number + * @param enable True to enable, false to disable + */ +static inline void gpio_ll_etm_enable_task_gpio(gpio_etm_dev_t *dev, uint32_t gpio_num, bool enable) +{ + int g_p = gpio_num / 4; + int g_idx = gpio_num % 4; + uint32_t reg_val = dev->etm_task_pn_cfg[g_p].val; + reg_val &= ~(0x01 << (g_idx * 8)); + reg_val |= ((enable & 0x01) << (g_idx * 8)); + dev->etm_task_pn_cfg[g_p].val = reg_val; +} + +/** + * @brief Check whether a GPIO has been enabled and managed by a task channel + * + * @param dev Register base address + * @param gpio_num GPIO number + * @return True if enabled, false otherwise + */ +static inline bool gpio_ll_etm_is_task_gpio_enabled(gpio_etm_dev_t *dev, uint32_t gpio_num) +{ + int g_p = gpio_num / 4; + int g_idx = gpio_num % 4; + return dev->etm_task_pn_cfg[g_p].val & (0x01 << (g_idx * 8)); +} + +/** + * @brief Get the channel number that the GPIO is bounded to + * + * @param dev Register base address + * @param gpio_num GPIO number + * @return GPIO ETM Task channel number + */ +static inline uint32_t gpio_ll_etm_gpio_get_task_channel(gpio_etm_dev_t *dev, uint32_t gpio_num) +{ + int g_p = gpio_num / 4; + int g_idx = gpio_num % 4; + return (dev->etm_task_pn_cfg[g_p].val >> (g_idx * 8 + 1)) & 0x07; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/esp32c6/include/hal/systimer_ll.h b/components/hal/esp32c6/include/hal/systimer_ll.h index 632a8c11917d..618787be5d65 100644 --- a/components/hal/esp32c6/include/hal/systimer_ll.h +++ b/components/hal/esp32c6/include/hal/systimer_ll.h @@ -42,6 +42,13 @@ static inline soc_periph_systimer_clk_src_t systimer_ll_get_clock_source(void) return (PCR.systimer_func_clk_conf.systimer_func_clk_sel == 1) ? SYSTIMER_CLK_SRC_RC_FAST : SYSTIMER_CLK_SRC_XTAL; } +/********************** ETM *****************************/ + +__attribute__((always_inline)) static inline void systimer_ll_enable_etm(systimer_dev_t *dev, bool en) +{ + dev->conf.etm_en = en; +} + /******************* Counter *************************/ __attribute__((always_inline)) static inline void systimer_ll_enable_counter(systimer_dev_t *dev, uint32_t counter_id, bool en) diff --git a/components/hal/esp32c6/include/hal/timer_ll.h b/components/hal/esp32c6/include/hal/timer_ll.h index 96c5490a57c4..d76e95e2e27a 100644 --- a/components/hal/esp32c6/include/hal/timer_ll.h +++ b/components/hal/esp32c6/include/hal/timer_ll.h @@ -14,6 +14,7 @@ #include "hal/timer_types.h" #include "soc/timer_group_struct.h" #include "soc/pcr_struct.h" +#include "soc/soc_etm_source.h" #ifdef __cplusplus extern "C" { @@ -23,6 +24,32 @@ extern "C" { #define TIMER_LL_GET_HW(group_id) ((group_id == 0) ? (&TIMERG0) : (&TIMERG1)) #define TIMER_LL_EVENT_ALARM(timer_id) (1 << (timer_id)) +#define TIMER_LL_ETM_TASK_TABLE(group, timer, task) \ + (uint32_t [2][1][GPTIMER_ETM_TASK_MAX]){{{ \ + [GPTIMER_ETM_TASK_START_COUNT] = TIMER0_TASK_CNT_START_TIMER0, \ + [GPTIMER_ETM_TASK_STOP_COUNT] = TIMER0_TASK_CNT_STOP_TIMER0, \ + [GPTIMER_ETM_TASK_EN_ALARM] = TIMER0_TASK_ALARM_START_TIMER0, \ + [GPTIMER_ETM_TASK_RELOAD] = TIMER0_TASK_CNT_RELOAD_TIMER0, \ + [GPTIMER_ETM_TASK_CAPTURE] = TIMER0_TASK_CNT_CAP_TIMER0, \ + }}, \ + {{ \ + [GPTIMER_ETM_TASK_START_COUNT] = TIMER1_TASK_CNT_START_TIMER0, \ + [GPTIMER_ETM_TASK_STOP_COUNT] = TIMER1_TASK_CNT_STOP_TIMER0, \ + [GPTIMER_ETM_TASK_EN_ALARM] = TIMER1_TASK_ALARM_START_TIMER0, \ + [GPTIMER_ETM_TASK_RELOAD] = TIMER1_TASK_CNT_RELOAD_TIMER0, \ + [GPTIMER_ETM_TASK_CAPTURE] = TIMER1_TASK_CNT_CAP_TIMER0, \ + }}, \ + }[group][timer][task] + +#define TIMER_LL_ETM_EVENT_TABLE(group, timer, event) \ + (uint32_t [2][1][GPTIMER_ETM_EVENT_MAX]){{{ \ + [GPTIMER_ETM_EVENT_ALARM_MATCH] = TIMER0_EVT_CNT_CMP_TIMER0, \ + }}, \ + {{ \ + [GPTIMER_ETM_EVENT_ALARM_MATCH] = TIMER1_EVT_CNT_CMP_TIMER0, \ + }}, \ + }[group][timer][event] + /** * @brief Set clock source for timer * diff --git a/components/hal/etm_hal.c b/components/hal/etm_hal.c new file mode 100644 index 000000000000..5e90b7118909 --- /dev/null +++ b/components/hal/etm_hal.c @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/etm_hal.h" +#include "hal/etm_ll.h" + +void etm_hal_init(etm_hal_context_t *hal) +{ + hal->regs = &SOC_ETM; +} + +void etm_hal_deinit(etm_hal_context_t *hal) +{ + hal->regs = NULL; +} diff --git a/components/hal/include/hal/etm_hal.h b/components/hal/include/hal/etm_hal.h new file mode 100644 index 000000000000..2ba4529ca729 --- /dev/null +++ b/components/hal/include/hal/etm_hal.h @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/******************************************************************************* + * NOTICE + * The hal is not public api, don't use in application code. + * See readme.md in hal/include/hal/readme.md + ******************************************************************************/ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct soc_etm_dev_t *etm_soc_handle_t; // ETM SOC layer handle + +/** + * @brief HAL context type of ETM driver + */ +typedef struct { + etm_soc_handle_t regs; /*!< ETM Register base address */ +} etm_hal_context_t; + +/** + * @brief Initialize the ETM HAL driver + * + * @param hal: ETM HAL context + */ +void etm_hal_init(etm_hal_context_t *hal); + +/** + * @brief Deinitialize the ETM HAL driver + * + * @param hal: ETM HAL context + */ +void etm_hal_deinit(etm_hal_context_t *hal); + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/include/hal/gdma_types.h b/components/hal/include/hal/gdma_types.h new file mode 100644 index 000000000000..cd3c9f39c5ca --- /dev/null +++ b/components/hal/include/hal/gdma_types.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief GDMA channel events that supported by the ETM module + */ +typedef enum { + GDMA_ETM_EVENT_EOF, /*!< Event that the GDMA engine meets EOF descriptor */ + GDMA_ETM_EVENT_MAX, /*!< Maximum number of events */ +} gdma_etm_event_type_t; + +/** + * @brief GDMA channel tasks that supported by the ETM module + */ +typedef enum { + GDMA_ETM_TASK_START, /*!< Start the GDMA machine */ + GDMA_ETM_TASK_MAX, /*!< Maximum number of events */ +} gdma_etm_task_type_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/include/hal/timer_types.h b/components/hal/include/hal/timer_types.h index ea8982f33cf2..8c444874ae34 100644 --- a/components/hal/include/hal/timer_types.h +++ b/components/hal/include/hal/timer_types.h @@ -26,6 +26,26 @@ typedef enum { GPTIMER_COUNT_UP, /*!< Increase count value */ } gptimer_count_direction_t; +/** + * @brief GPTimer specific tasks that supported by the ETM module + */ +typedef enum { + GPTIMER_ETM_TASK_START_COUNT, /*!< Start the counter */ + GPTIMER_ETM_TASK_STOP_COUNT, /*!< Stop the counter */ + GPTIMER_ETM_TASK_EN_ALARM, /*!< Enable the alarm */ + GPTIMER_ETM_TASK_RELOAD, /*!< Reload preset value into counter */ + GPTIMER_ETM_TASK_CAPTURE, /*!< Capture current count value into specific register */ + GPTIMER_ETM_TASK_MAX, /*!< Maximum number of tasks */ +} gptimer_etm_task_type_t; + +/** + * @brief GPTimer specific events that supported by the ETM module + */ +typedef enum { + GPTIMER_ETM_EVENT_ALARM_MATCH, /*!< Count value matches the alarm target value */ + GPTIMER_ETM_EVENT_MAX, /*!< Maximum number of events */ +} gptimer_etm_event_type_t; + #ifdef __cplusplus } #endif diff --git a/components/hal/systimer_hal.c b/components/hal/systimer_hal.c index b3b071d4139a..3ef3337f26f9 100644 --- a/components/hal/systimer_hal.c +++ b/components/hal/systimer_hal.c @@ -16,10 +16,16 @@ void systimer_hal_init(systimer_hal_context_t *hal) { hal->dev = &SYSTIMER; systimer_ll_enable_clock(hal->dev, true); +#if SOC_SYSTIMER_SUPPORT_ETM + systimer_ll_enable_etm(&SYSTIMER, true); +#endif } void systimer_hal_deinit(systimer_hal_context_t *hal) { +#if SOC_SYSTIMER_SUPPORT_ETM + systimer_ll_enable_etm(&SYSTIMER, false); +#endif systimer_ll_enable_clock(hal->dev, false); hal->dev = NULL; } diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index a3afacf626e4..c3d9c9e1bc0e 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -23,6 +23,10 @@ config SOC_TWAI_SUPPORTED bool default y +config SOC_ETM_SUPPORTED + bool + default y + config SOC_BT_SUPPORTED bool default y @@ -243,6 +247,18 @@ config SOC_GDMA_PAIRS_PER_GROUP int default 3 +config SOC_GDMA_SUPPORT_ETM + bool + default y + +config SOC_ETM_GROUPS + int + default 1 + +config SOC_ETM_CHANNELS_PER_GROUP + int + default 50 + config SOC_GPIO_PORT int default 1 @@ -251,6 +267,18 @@ config SOC_GPIO_PIN_COUNT int default 31 +config SOC_GPIO_SUPPORT_ETM + bool + default y + +config SOC_GPIO_ETM_EVENTS_PER_GROUP + int + default 8 + +config SOC_GPIO_ETM_TASKS_PER_GROUP + int + default 8 + config SOC_GPIO_SUPPORT_RTC_INDEPENDENT bool default y @@ -675,6 +703,10 @@ config SOC_SYSTIMER_ALARM_MISS_COMPENSATE bool default y +config SOC_SYSTIMER_SUPPORT_ETM + bool + default y + config SOC_TIMER_GROUPS int default 2 diff --git a/components/soc/esp32c6/include/soc/gdma_channel.h b/components/soc/esp32c6/include/soc/gdma_channel.h index de9e2dd65949..6ae11185f821 100644 --- a/components/soc/esp32c6/include/soc/gdma_channel.h +++ b/components/soc/esp32c6/include/soc/gdma_channel.h @@ -14,3 +14,4 @@ #define SOC_GDMA_TRIG_PERIPH_AES0 (6) #define SOC_GDMA_TRIG_PERIPH_SHA0 (7) #define SOC_GDMA_TRIG_PERIPH_ADC0 (8) +#define SOC_GDMA_TRIG_PERIPH_PARLIO0 (9) diff --git a/components/soc/esp32c6/include/soc/gpio_ext_struct.h b/components/soc/esp32c6/include/soc/gpio_ext_struct.h index b06cd88027bf..7bcb54adf2c9 100644 --- a/components/soc/esp32c6/include/soc/gpio_ext_struct.h +++ b/components/soc/esp32c6/include/soc/gpio_ext_struct.h @@ -155,314 +155,7 @@ typedef union { uint32_t reserved_28:4; }; uint32_t val; -} gpio_etm_task_p0_cfg_reg_t; - -/** Type of etm_task_p1_cfg register - * Etm Configure Register to decide which GPIO been chosen - */ -typedef union { - struct { - /** etm_task_gpio4_en : R/W; bitpos: [0]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio4_en:1; - /** etm_task_gpio4_sel : R/W; bitpos: [3:1]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio4_sel:3; - uint32_t reserved_4:4; - /** etm_task_gpio5_en : R/W; bitpos: [8]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio5_en:1; - /** etm_task_gpio5_sel : R/W; bitpos: [11:9]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio5_sel:3; - uint32_t reserved_12:4; - /** etm_task_gpio6_en : R/W; bitpos: [16]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio6_en:1; - /** etm_task_gpio6_sel : R/W; bitpos: [19:17]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio6_sel:3; - uint32_t reserved_20:4; - /** etm_task_gpio7_en : R/W; bitpos: [24]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio7_en:1; - /** etm_task_gpio7_sel : R/W; bitpos: [27:25]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio7_sel:3; - uint32_t reserved_28:4; - }; - uint32_t val; -} gpio_etm_task_p1_cfg_reg_t; - -/** Type of etm_task_p2_cfg register - * Etm Configure Register to decide which GPIO been chosen - */ -typedef union { - struct { - /** etm_task_gpio8_en : R/W; bitpos: [0]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio8_en:1; - /** etm_task_gpio8_sel : R/W; bitpos: [3:1]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio8_sel:3; - uint32_t reserved_4:4; - /** etm_task_gpio9_en : R/W; bitpos: [8]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio9_en:1; - /** etm_task_gpio9_sel : R/W; bitpos: [11:9]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio9_sel:3; - uint32_t reserved_12:4; - /** etm_task_gpio10_en : R/W; bitpos: [16]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio10_en:1; - /** etm_task_gpio10_sel : R/W; bitpos: [19:17]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio10_sel:3; - uint32_t reserved_20:4; - /** etm_task_gpio11_en : R/W; bitpos: [24]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio11_en:1; - /** etm_task_gpio11_sel : R/W; bitpos: [27:25]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio11_sel:3; - uint32_t reserved_28:4; - }; - uint32_t val; -} gpio_etm_task_p2_cfg_reg_t; - -/** Type of etm_task_p3_cfg register - * Etm Configure Register to decide which GPIO been chosen - */ -typedef union { - struct { - /** etm_task_gpio12_en : R/W; bitpos: [0]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio12_en:1; - /** etm_task_gpio12_sel : R/W; bitpos: [3:1]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio12_sel:3; - uint32_t reserved_4:4; - /** etm_task_gpio13_en : R/W; bitpos: [8]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio13_en:1; - /** etm_task_gpio13_sel : R/W; bitpos: [11:9]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio13_sel:3; - uint32_t reserved_12:4; - /** etm_task_gpio14_en : R/W; bitpos: [16]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio14_en:1; - /** etm_task_gpio14_sel : R/W; bitpos: [19:17]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio14_sel:3; - uint32_t reserved_20:4; - /** etm_task_gpio15_en : R/W; bitpos: [24]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio15_en:1; - /** etm_task_gpio15_sel : R/W; bitpos: [27:25]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio15_sel:3; - uint32_t reserved_28:4; - }; - uint32_t val; -} gpio_etm_task_p3_cfg_reg_t; - -/** Type of etm_task_p4_cfg register - * Etm Configure Register to decide which GPIO been chosen - */ -typedef union { - struct { - /** etm_task_gpio16_en : R/W; bitpos: [0]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio16_en:1; - /** etm_task_gpio16_sel : R/W; bitpos: [3:1]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio16_sel:3; - uint32_t reserved_4:4; - /** etm_task_gpio17_en : R/W; bitpos: [8]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio17_en:1; - /** etm_task_gpio17_sel : R/W; bitpos: [11:9]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio17_sel:3; - uint32_t reserved_12:4; - /** etm_task_gpio18_en : R/W; bitpos: [16]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio18_en:1; - /** etm_task_gpio18_sel : R/W; bitpos: [19:17]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio18_sel:3; - uint32_t reserved_20:4; - /** etm_task_gpio19_en : R/W; bitpos: [24]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio19_en:1; - /** etm_task_gpio19_sel : R/W; bitpos: [27:25]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio19_sel:3; - uint32_t reserved_28:4; - }; - uint32_t val; -} gpio_etm_task_p4_cfg_reg_t; - -/** Type of etm_task_p5_cfg register - * Etm Configure Register to decide which GPIO been chosen - */ -typedef union { - struct { - /** etm_task_gpio20_en : R/W; bitpos: [0]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio20_en:1; - /** etm_task_gpio20_sel : R/W; bitpos: [3:1]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio20_sel:3; - uint32_t reserved_4:4; - /** etm_task_gpio21_en : R/W; bitpos: [8]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio21_en:1; - /** etm_task_gpio21_sel : R/W; bitpos: [11:9]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio21_sel:3; - uint32_t reserved_12:4; - /** etm_task_gpio22_en : R/W; bitpos: [16]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio22_en:1; - /** etm_task_gpio22_sel : R/W; bitpos: [19:17]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio22_sel:3; - uint32_t reserved_20:4; - /** etm_task_gpio23_en : R/W; bitpos: [24]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio23_en:1; - /** etm_task_gpio23_sel : R/W; bitpos: [27:25]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio23_sel:3; - uint32_t reserved_28:4; - }; - uint32_t val; -} gpio_etm_task_p5_cfg_reg_t; - -/** Type of etm_task_p6_cfg register - * Etm Configure Register to decide which GPIO been chosen - */ -typedef union { - struct { - /** etm_task_gpio24_en : R/W; bitpos: [0]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio24_en:1; - /** etm_task_gpio24_sel : R/W; bitpos: [3:1]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio24_sel:3; - uint32_t reserved_4:4; - /** etm_task_gpio25_en : R/W; bitpos: [8]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio25_en:1; - /** etm_task_gpio25_sel : R/W; bitpos: [11:9]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio25_sel:3; - uint32_t reserved_12:4; - /** etm_task_gpio26_en : R/W; bitpos: [16]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio26_en:1; - /** etm_task_gpio26_sel : R/W; bitpos: [19:17]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio26_sel:3; - uint32_t reserved_20:4; - /** etm_task_gpio27_en : R/W; bitpos: [24]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio27_en:1; - /** etm_task_gpio27_sel : R/W; bitpos: [27:25]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio27_sel:3; - uint32_t reserved_28:4; - }; - uint32_t val; -} gpio_etm_task_p6_cfg_reg_t; - -/** Type of etm_task_p7_cfg register - * Etm Configure Register to decide which GPIO been chosen - */ -typedef union { - struct { - /** etm_task_gpio28_en : R/W; bitpos: [0]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio28_en:1; - /** etm_task_gpio28_sel : R/W; bitpos: [3:1]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio28_sel:3; - uint32_t reserved_4:4; - /** etm_task_gpio29_en : R/W; bitpos: [8]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio29_en:1; - /** etm_task_gpio29_sel : R/W; bitpos: [11:9]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio29_sel:3; - uint32_t reserved_12:4; - /** etm_task_gpio30_en : R/W; bitpos: [16]; default: 0; - * Enable bit of GPIO response etm task. - */ - uint32_t etm_task_gpio30_en:1; - /** etm_task_gpio30_sel : R/W; bitpos: [19:17]; default: 0; - * GPIO choose a etm task channel. - */ - uint32_t etm_task_gpio30_sel:3; - uint32_t reserved_20:12; - }; - uint32_t val; -} gpio_etm_task_p7_cfg_reg_t; - +} gpio_etm_task_pn_cfg_reg_t; /** Group: Version Register */ /** Type of version register @@ -491,17 +184,10 @@ typedef struct { volatile gpio_glitch_filter_chn_reg_t glitch_filter_chn[8]; } gpio_glitch_filter_dev_t; -typedef struct { +typedef struct gpio_etm_dev_t { volatile gpio_etm_event_chn_cfg_reg_t event_chn_cfg[8]; uint32_t reserved_080[8]; - volatile gpio_etm_task_p0_cfg_reg_t etm_task_p0_cfg; - volatile gpio_etm_task_p1_cfg_reg_t etm_task_p1_cfg; - volatile gpio_etm_task_p2_cfg_reg_t etm_task_p2_cfg; - volatile gpio_etm_task_p3_cfg_reg_t etm_task_p3_cfg; - volatile gpio_etm_task_p4_cfg_reg_t etm_task_p4_cfg; - volatile gpio_etm_task_p5_cfg_reg_t etm_task_p5_cfg; - volatile gpio_etm_task_p6_cfg_reg_t etm_task_p6_cfg; - volatile gpio_etm_task_p7_cfg_reg_t etm_task_p7_cfg; + volatile gpio_etm_task_pn_cfg_reg_t etm_task_pn_cfg[8]; } gpio_etm_dev_t; typedef struct gpio_ext_dev_t { @@ -517,6 +203,7 @@ typedef struct gpio_ext_dev_t { extern gpio_sd_dev_t SDM; extern gpio_glitch_filter_dev_t GLITCH_FILTER; extern gpio_etm_dev_t GPIO_ETM; +extern gpio_ext_dev_t GPIO_EXT; #ifndef __cplusplus _Static_assert(sizeof(gpio_ext_dev_t) == 0x100, "Invalid size of gpio_ext_dev_t structure"); diff --git a/components/soc/esp32c6/include/soc/periph_defs.h b/components/soc/esp32c6/include/soc/periph_defs.h index 826ebe6dd615..0214b03077ef 100644 --- a/components/soc/esp32c6/include/soc/periph_defs.h +++ b/components/soc/esp32c6/include/soc/periph_defs.h @@ -40,6 +40,7 @@ typedef enum { PERIPH_DS_MODULE, PERIPH_GDMA_MODULE, PERIPH_MCPWM0_MODULE, + PERIPH_ETM_MODULE, PERIPH_SYSTIMER_MODULE, PERIPH_SARADC_MODULE, PERIPH_TEMPSENSOR_MODULE, diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index dd5d8f27fc78..d1ed6f241f39 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -31,6 +31,7 @@ #define SOC_PCNT_SUPPORTED 1 #define SOC_MCPWM_SUPPORTED 1 #define SOC_TWAI_SUPPORTED 1 +#define SOC_ETM_SUPPORTED 1 #define SOC_BT_SUPPORTED 1 #define SOC_ASYNC_MEMCPY_SUPPORTED 1 #define SOC_USB_SERIAL_JTAG_SUPPORTED 1 @@ -136,16 +137,25 @@ See TRM DS chapter for more details */ #define SOC_DS_KEY_CHECK_MAX_WAIT_US (1100) -// TODO: IDF-5319 (Copy from esp32c3, need check) /*-------------------------- GDMA CAPS -------------------------------------*/ #define SOC_GDMA_GROUPS (1U) // Number of GDMA groups #define SOC_GDMA_PAIRS_PER_GROUP (3) // Number of GDMA pairs in each group +#define SOC_GDMA_SUPPORT_ETM (1) // Support ETM submodule + +/*-------------------------- ETM CAPS --------------------------------------*/ +#define SOC_ETM_GROUPS 1U // Number of ETM groups +#define SOC_ETM_CHANNELS_PER_GROUP 50 // Number of ETM channels in the group /*-------------------------- GPIO CAPS ---------------------------------------*/ // ESP32-C6 has 1 GPIO peripheral #define SOC_GPIO_PORT (1U) #define SOC_GPIO_PIN_COUNT (31) +// GPIO peripheral has the ETM extension +#define SOC_GPIO_SUPPORT_ETM 1 +#define SOC_GPIO_ETM_EVENTS_PER_GROUP 8 +#define SOC_GPIO_ETM_TASKS_PER_GROUP 8 + // Target has the full LP IO subsystem // On ESP32-C6, Digital IOs have their own registers to control pullup/down capability, independent of LP registers. #define SOC_GPIO_SUPPORT_RTC_INDEPENDENT (1) @@ -249,7 +259,7 @@ #define SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP (3) ///< The number of GPIO synchros that each group has #define SOC_MCPWM_SWSYNC_CAN_PROPAGATE (1) ///< Software sync event can be routed to its output #define SOC_MCPWM_SUPPORT_ETM (1) ///< Support ETM (Event Task Matrix) -#define SOC_MCPWM_CAPTURE_CLK_FROM_GROUP (1) ///< Capture timer shares clock with other PWM timers +#define SOC_MCPWM_CAPTURE_CLK_FROM_GROUP (1) ///< Capture timer shares clock with other PWM timers #define SOC_MCPWM_CLK_SUPPORT_PLL160M (1) ///< Support PLL160M as clock source #define SOC_MCPWM_CLK_SUPPORT_XTAL (1) ///< Support XTAL as clock source @@ -339,6 +349,7 @@ #define SOC_SYSTIMER_SUPPORT_RC_FAST 1 // Systimer can use RC_FAST clock source #define SOC_SYSTIMER_INT_LEVEL 1 // Systimer peripheral uses level interrupt #define SOC_SYSTIMER_ALARM_MISS_COMPENSATE 1 // Systimer peripheral can generate interrupt immediately if t(target) > t(current) +#define SOC_SYSTIMER_SUPPORT_ETM 1 // Systimer comparator can generate ETM event /*--------------------------- TIMER GROUP CAPS ---------------------------------------*/ #define SOC_TIMER_GROUPS (2) diff --git a/components/soc/esp32c6/ld/esp32c6.peripherals.ld b/components/soc/esp32c6/ld/esp32c6.peripherals.ld index 92c6203c47b4..2a9acb4b10ac 100644 --- a/components/soc/esp32c6/ld/esp32c6.peripherals.ld +++ b/components/soc/esp32c6/ld/esp32c6.peripherals.ld @@ -46,7 +46,7 @@ PROVIDE ( HMAC = 0x6008D000 ); PROVIDE ( IO_MUX = 0x60090000 ); PROVIDE ( GPIO = 0x60091000 ); -PROVIDE ( GPIO_EXT = 0x60091f00 ); /*ESP32C6-TODO*/ +PROVIDE ( GPIO_EXT = 0x60091f00 ); PROVIDE ( SDM = 0x60091f00 ); PROVIDE ( GLITCH_FILTER = 0x60091f30 ); PROVIDE ( GPIO_ETM = 0x60091f60 ); diff --git a/docs/_static/diagrams/etm/etm_channel.diag b/docs/_static/diagrams/etm/etm_channel.diag new file mode 100644 index 000000000000..3519a97aa712 --- /dev/null +++ b/docs/_static/diagrams/etm/etm_channel.diag @@ -0,0 +1,22 @@ +blockdiag etm_channel { + default_fontsize = 12; + node_width = 200; + node_height = 40; + + event_gpio [label = "GPIO Event", shape = "beginpoint", stacked]; + other_events [shape = "dots"]; + event_others [label = "Periph Event", shape = "beginpoint", stacked]; + channels0 [label = "Channel-N", stacked]; + channels1 [label = "Channel-M", stacked]; + other_channels [shape = "dots"]; + task_gpio [label = "GPIO Task", shape = "endpoint", stacked]; + task_others [label = "Other Tasks", shape = "endpoint", stacked]; + other_tasks [shape = "dots"]; + + event_gpio -> channels0; + event_others -> channels1; + other_events -> other_channels; + channels0 -> task_gpio; + channels1 -> task_others; + other_channels -> other_tasks; +} diff --git a/docs/conf_common.py b/docs/conf_common.py index 29fefa1784da..b02df355667f 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -80,6 +80,8 @@ DAC_DOCS = ['api-reference/peripherals/dac.rst'] +ETM_DOCS = ['api-reference/peripherals/etm.rst'] + TEMP_SENSOR_DOCS = ['api-reference/peripherals/temp_sensor.rst'] TOUCH_SENSOR_DOCS = ['api-reference/peripherals/touch_pad.rst'] @@ -164,6 +166,7 @@ 'SOC_PCNT_SUPPORTED':PCNT_DOCS, 'SOC_RMT_SUPPORTED':RMT_DOCS, 'SOC_DAC_SUPPORTED':DAC_DOCS, + 'SOC_ETM_SUPPORTED':ETM_DOCS, 'SOC_TOUCH_SENSOR_SUPPORTED':TOUCH_SENSOR_DOCS, 'SOC_ULP_SUPPORTED':ULP_DOCS, 'SOC_RISCV_COPROC_SUPPORTED':RISCV_COPROC_DOCS, diff --git a/docs/docs_not_updated/esp32c6.txt b/docs/docs_not_updated/esp32c6.txt index 8e0e6baf8af7..a135eacbe578 100644 --- a/docs/docs_not_updated/esp32c6.txt +++ b/docs/docs_not_updated/esp32c6.txt @@ -100,7 +100,6 @@ api-reference/peripherals/hmac api-reference/peripherals/usb_device api-reference/peripherals/sdspi_host api-reference/peripherals/dac -api-reference/peripherals/gptimer api-reference/peripherals/touch_element api-reference/peripherals/lcd api-reference/peripherals/secure_element diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index e7711082e797..28088465f97e 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -67,7 +67,10 @@ INPUT = \ $(PROJECT_PATH)/components/driver/include/driver/dac_types.h \ $(PROJECT_PATH)/components/driver/include/driver/dedic_gpio.h \ $(PROJECT_PATH)/components/driver/include/driver/gpio.h \ + $(PROJECT_PATH)/components/driver/include/driver/gpio_etm.h \ $(PROJECT_PATH)/components/driver/include/driver/gptimer.h \ + $(PROJECT_PATH)/components/driver/include/driver/gptimer_etm.h \ + $(PROJECT_PATH)/components/driver/include/driver/gptimer_types.h \ $(PROJECT_PATH)/components/driver/include/driver/i2c.h \ $(PROJECT_PATH)/components/driver/include/driver/i2s_common.h \ $(PROJECT_PATH)/components/driver/include/driver/i2s_pdm.h \ @@ -133,6 +136,7 @@ INPUT = \ $(PROJECT_PATH)/components/esp_hw_support/include/esp_chip_info.h \ $(PROJECT_PATH)/components/esp_hw_support/include/esp_cpu.h \ $(PROJECT_PATH)/components/esp_hw_support/include/esp_crc.h \ + $(PROJECT_PATH)/components/esp_hw_support/include/esp_etm.h \ $(PROJECT_PATH)/components/esp_hw_support/include/esp_hmac.h \ $(PROJECT_PATH)/components/esp_hw_support/include/esp_intr_alloc.h \ $(PROJECT_PATH)/components/esp_hw_support/include/esp_mac.h \ @@ -160,6 +164,7 @@ INPUT = \ $(PROJECT_PATH)/components/esp_system/include/esp_ipc_isr.h \ $(PROJECT_PATH)/components/esp_system/include/esp_ipc.h \ $(PROJECT_PATH)/components/esp_system/include/esp_system.h \ + $(PROJECT_PATH)/components/esp_system/include/esp_systick_etm.h \ $(PROJECT_PATH)/components/esp_system/include/esp_task_wdt.h \ $(PROJECT_PATH)/components/esp_timer/include/esp_timer.h \ $(PROJECT_PATH)/components/esp_wifi/include/esp_mesh.h \ diff --git a/docs/en/api-reference/peripherals/etm.rst b/docs/en/api-reference/peripherals/etm.rst new file mode 100644 index 000000000000..23e9a7f15aa6 --- /dev/null +++ b/docs/en/api-reference/peripherals/etm.rst @@ -0,0 +1,148 @@ +Event Task Matrix (ETM) +======================= + +Introduction +------------ + +Normally, if a peripheral X needs to notify peripheral Y of a particular event, this could only be done via a CPU interrupt from peripheral X (where the CPU notifies peripheral Y on behalf of peripheral X). However, in time critical applications, the latency introduced by CPU interrupts is non-negligible. The Event Task Matrix (ETM) module allows subset of peripherals to notify each other of events directly (i.e., without CPU intervention). This allows precise (and low latency) synchronization between peripherals, and lessens the CPU's work load (as the CPU no longer needs handle these events). + +.. blockdiag:: /../_static/diagrams/etm/etm_channel.diag + :caption: ETM channels Overview + :align: center + +The ETM module has multiple programmable channels, they're used to connect a particular **Event** to a particular **Task**. When an event is activated, the ETM channel will trigger the corresponding task automatically. Peripherals that support ETM functionality will provide their or unique set of events and tasks to be connected by the ETM. An ETM channel can connect any event to any task (even looping back an event to a task of on the same peripheral). However, an ETM channel can only connect one event to one task any time (i.e., 1 to 1 relation). If you want to use different events to trigger the same task, you can set up more ETM channels. + +Typically, with the help of ETM module, you can implement features like: + +- Toggle the GPIO when a timer alarm event happens +- Start an ADC conversion when a pulse edge is detected on a GPIO + +Functional Overview +------------------- + +The following sections of this document cover the typical steps to configure and use the ETM module. + +- :ref:`etm-channel-allocation` - describes how to install and uninstall ETM channel +- :ref:`etm-event` - describes how to allocate a new ETM event handle or fetch an existing handle from various peripherals +- :ref:`etm-task` - describes how to allocate a new ETM task handle or fetch an existing handle from various peripherals +- :ref:`etm-channel-control` - describes common ETM channel control functions +- :ref:`etm-thread-safety` - lists which APIs are guaranteed to be thread safe by the driver. +- :ref:`etm-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior + +.. _etm-channel-allocation: + +ETM Channel Allocation +^^^^^^^^^^^^^^^^^^^^^^ + +There're many identical ETM channels in {IDF_TARGET_NAME} [1]_, each channel is represented by :cpp:type:`esp_etm_channel_handle_t` in the software. The ETM core driver manages all available hardware resources in a pool, so that you don't need to care about which channel is in use and which is not. The ETM core driver will allocate a channel for you when you call :cpp:func:`esp_etm_new_channel` and delete it when you call :cpp:func:`esp_etm_del_channel`. All requirements needed for allocating a channel are provided in :cpp:type:`esp_etm_channel_config_t`. + +Before deleting an ETM channel, please disable it by :cpp:func:`esp_etm_channel_disable` in advance or make sure it has not been enabled yet by :cpp:func:`esp_etm_channel_enable`. + +.. _etm-event: + +ETM Event +^^^^^^^^^ + +ETM Event abstracts the event source and is represented by :cpp:type:`esp_etm_event_handle_t` in the software. ETM event can be generated from a variety of peripherals, thus the way to get the event handle differs from peripherals. When an ETM event is no longer used, you should call :cpp:func:`esp_etm_channel_connect` with a ``NULL`` event handle to disconnect it and then call :cpp:func:`esp_etm_del_event` to free the event resource. + +GPIO Events +~~~~~~~~~~~ + +GPIO **edge** event is the most common event type, it can be generated by any GPIO pin. You can call :cpp:func:`gpio_new_etm_event` to create a GPIO event handle, with the configurations provided in :cpp:type:`gpio_etm_event_config_t`: + +- :cpp:member:`gpio_etm_event_config_t::edge` decides which edge will trigger the event, supported edge types are listed in the :cpp:type:`gpio_etm_event_edge_t`. + +You need to build a connection between the GPIO ETM event handle and the GPIO number. So you should call :cpp:func:`gpio_etm_event_bind_gpio` afterwards. Please note, only the ETM event handle that created by :cpp:func:`gpio_new_etm_event` can set a GPIO number. Calling this function with other kind of ETM event will return :c:macro:`ESP_ERR_INVALID_ARG` error. Needless to say, this function won't help do the GPIO initialization, you still need to call :cpp:func:`gpio_config` to set the property like direction, pull up/down mode separately. + +Other Peripheral Events +~~~~~~~~~~~~~~~~~~~~~~~ + +.. list:: + + :SOC_SYSTIMER_SUPPORT_ETM: - You can call :cpp:func:`esp_systick_new_etm_alarm_event` to get the ETM event from RTOS Systick, one per CPU core. + :SOC_SYSTIMER_SUPPORT_ETM: - Refer to :doc:`ESP Timer ` for how to get the ETM event handle from esp_timer. + :SOC_TIMER_SUPPORT_ETM: - Refer to :doc:`GPTimer ` for how to get the ETM event handle from GPTimer. + :SOC_GDMA_SUPPORT_ETM: - Refer to :doc:`Async Memory Copy ` for how to get the ETM event handle from async memcpy. + +.. _etm-task: + +ETM Task +^^^^^^^^ + +ETM Task abstracts the task action and is represented by :cpp:type:`esp_etm_task_handle_t` in the software. ETM task can be assigned to a variety of peripherals, thus the way to get the task handle differs from peripherals. When an ETM task is no longer used, you should call :cpp:func:`esp_etm_channel_connect` with a ``NULL`` task handle to disconnect it and then call :cpp:func:`esp_etm_del_task` to free the task resource. + +GPIO Tasks +~~~~~~~~~~ + +GPIO task is the most common task type, one GPIO task can even manage multiple GPIOs. When tha task gets activated by the ETM channel, all managed GPIOs can set/clear/toggle at the same time. You can call :cpp:func:`gpio_new_etm_task` to create a GPIO task handle, with the configurations provided in :cpp:type:`gpio_etm_task_config_t`: + +- :cpp:member:`gpio_etm_task_config_t::action` decides what the GPIO action would be taken by the ETM task. Supported actions are listed in the :cpp:type:`gpio_etm_task_action_t`. + +To build a connection between the GPIO ETM task and the GPIO number, you should call :cpp:func:`gpio_etm_task_add_gpio`. You can call this function by several times if you want the task handle to manage more GPIOs. Please note, only the ETM task handle that created by :cpp:func:`gpio_new_etm_task` can manage a GPIO. Calling this function with other kind of ETM task will return :c:macro:`ESP_ERR_INVALID_ARG` error. Needless to say, this function won't help do the GPIO initialization, you still need to call :cpp:func:`gpio_config` to set the property like direction, pull up/down mode separately. + +Before you call :cpp:func:`esp_etm_del_task` to delete the GPIO ETM task, make sure that all previously added GPIOs are removed by :cpp:func:`gpio_etm_task_rm_gpio` in advance. + +Other Peripheral Tasks +~~~~~~~~~~~~~~~~~~~~~~ + +.. list:: + + :SOC_TIMER_SUPPORT_ETM: - Refer to :doc:`GPTimer ` for how to get the ETM task handle from GPTimer. + +.. _etm-channel-control: + +ETM Channel Control +^^^^^^^^^^^^^^^^^^^ + +Connect Event and Task +~~~~~~~~~~~~~~~~~~~~~~ + +An ETM event has no association with an ETM task, until they're connected to the same ETM channel by calling :cpp:func:`esp_etm_channel_connect`. Specially, calling the function with a ``NULL`` task/event handle, means to disconnect the channel from any task or event. Note that, this function can be called either before or after the channel is enabled. But calling this function at runtime to change the connection can be dangerous, because the channel may be in the middle of a cycle, and the new connection may not take effect immediately. + +Enable and Disable Channel +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can call :cpp:func:`esp_etm_channel_enable` and :cpp:func:`esp_etm_channel_disable` to enable and disable the ETM channel from working. + +ETM Channel Profiling +~~~~~~~~~~~~~~~~~~~~~ + +To check if the ETM channels are set with proper events and tasks, you can call :cpp:func:`esp_etm_dump` to dump all working ETM channels with their associated events and tasks. The dumping format is like: + +:: + + ===========ETM Dump Start========== + channel 0: event 48 ==> task 17 + channel 1: event 48 ==> task 90 + channel 2: event 48 ==> task 94 + ===========ETM Dump End============ + +The digital ID printed in the dump information is defined in the ``soc/soc_etm_source.h`` file. + +.. _etm-thread-safety: + +Thread Safety +^^^^^^^^^^^^^ + +The factory functions like :cpp:func:`esp_etm_new_channel` and :cpp:func:`gpio_new_etm_task` are guaranteed to be thread safe by the driver, which means, you can call it from different RTOS tasks without protection by extra locks. + +No functions are allowed to run within ISR environment. + +Other functions that take :cpp:type:`esp_etm_channel_handle_t`, :cpp:type:`esp_etm_task_handle_t` and :cpp:type:`esp_etm_event_handle_t` as the first positional parameter, are not treated as thread safe, which means you should avoid calling them from multiple tasks. + +.. _etm-kconfig-options: + +Kconfig Options +^^^^^^^^^^^^^^^ + +- :ref:`CONFIG_ETM_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size as well. + +API Reference +------------- + +.. include-build-file:: inc/esp_etm.inc +.. include-build-file:: inc/gpio_etm.inc +.. include-build-file:: inc/esp_systick_etm.inc + +.. [1] + Different ESP chip series might have different numbers of ETM channels. For more details, please refer to *{IDF_TARGET_NAME} Technical Reference Manual* > Chapter *Event Task Matrix (ETM)* [`PDF <{IDF_TARGET_TRM_EN_URL}#evntaskmatrix>`__]. The driver will not forbid you from applying for more channels, but it will return error when all available hardware resources are used up. Please always check the return value when doing channel allocation (i.e. :cpp:func:`esp_etm_new_channel`). diff --git a/docs/en/api-reference/peripherals/gptimer.rst b/docs/en/api-reference/peripherals/gptimer.rst index c06e0ffbfc4b..8f69d9fedfa3 100644 --- a/docs/en/api-reference/peripherals/gptimer.rst +++ b/docs/en/api-reference/peripherals/gptimer.rst @@ -17,16 +17,19 @@ Functional Overview The following sections of this document cover the typical steps to install and operate a timer: -- :ref:`gptimer-resource-allocation` - covers which parameters should be set up to get a timer handle and how to recycle the resources when GPTimer finishes working. -- :ref:`set-and-get-count-value` - covers how to force the timer counting from a start point and how to get the count value at anytime. -- :ref:`set-up-alarm-action` - covers the parameters that should be set up to enable the alarm event. -- :ref:`gptimer-register-event-callbacks` - covers how to hook user specific code to the alarm event callback function. -- :ref:`enable-and-disable-timer` - covers how to enable and disable the timer. -- :ref:`start-and-stop-timer` - shows some typical use cases that start the timer with different alarm behavior. -- :ref:`gptimer-power-management` - describes how different source clock selections can affect power consumption. -- :ref:`gptimer-iram-safe` - describes tips on how to make the timer interrupt and IO control functions work better along with a disabled cache. -- :ref:`gptimer-thread-safety` - lists which APIs are guaranteed to be thread safe by the driver. -- :ref:`gptimer-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior. +.. list:: + + - :ref:`gptimer-resource-allocation` - covers which parameters should be set up to get a timer handle and how to recycle the resources when GPTimer finishes working. + - :ref:`set-and-get-count-value` - covers how to force the timer counting from a start point and how to get the count value at anytime. + - :ref:`set-up-alarm-action` - covers the parameters that should be set up to enable the alarm event. + - :ref:`gptimer-register-event-callbacks` - covers how to hook user specific code to the alarm event callback function. + - :ref:`enable-and-disable-timer` - covers how to enable and disable the timer. + - :ref:`start-and-stop-timer` - shows some typical use cases that start the timer with different alarm behavior. + :SOC_TIMER_SUPPORT_ETM: - :ref:`gptimer-etm-event-and-task` - describes what the events and tasks can be connected to the ETM channel. + - :ref:`gptimer-power-management` - describes how different source clock selections can affect power consumption. + - :ref:`gptimer-iram-safe` - describes tips on how to make the timer interrupt and IO control functions work better along with a disabled cache. + - :ref:`gptimer-thread-safety` - lists which APIs are guaranteed to be thread safe by the driver. + - :ref:`gptimer-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior. .. _gptimer-resource-allocation: @@ -40,7 +43,6 @@ A GPTimer instance is represented by :cpp:type:`gptimer_handle_t`. The driver be To install a timer instance, there is a configuration structure that needs to be given in advance: :cpp:type:`gptimer_config_t`: - :cpp:member:`gptimer_config_t::clk_src` selects the source clock for the timer. The available clocks are listed in :cpp:type:`gptimer_clock_source_t`, you can only pick one of them. For the effect on power consumption of different clock source, please refer to Section :ref:`gptimer-power-management`. - - :cpp:member:`gptimer_config_t::direction` sets the counting direction of the timer, supported directions are listed in :cpp:type:`gptimer_count_direction_t`, you can only pick one of them. - :cpp:member:`gptimer_config_t::resolution_hz` sets the resolution of the internal counter. Each count step is equivalent to **1 / resolution_hz** seconds. @@ -256,7 +258,23 @@ Alarm value can be updated dynamically inside the ISR handler callback, by chang ESP_ERROR_CHECK(gptimer_enable(gptimer)); ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config)); -.. _gptimer-power-management: + +.. only:: SOC_TIMER_SUPPORT_ETM + + .. _gptimer-etm-event-and-task: + + ETM Event and Task + ^^^^^^^^^^^^^^^^^^ + + GPTimer is able to generate various events that can interact with the :doc:`ETM ` module. The supported events are listed in the :cpp:type:`gptimer_etm_event_type_t`. You can call :cpp:func:`gptimer_new_etm_event` to get the corresponding ETM event handle. Likewise, GPTimer exposes several tasks that can be triggered by other ETM events. The supported tasks are listed in the :cpp:type:`gptimer_etm_task_type_t`. You can call :cpp:func:`gptimer_new_etm_task` to get the corresponding ETM task handle. + + For how to connect the event and task to an ETM channel, please refer to the :doc:`ETM ` documentation. + + .. _gptimer-power-management: + +.. only:: not SOC_TIMER_SUPPORT_ETM + + .. _gptimer-power-management: Power Management ^^^^^^^^^^^^^^^^ @@ -319,12 +337,17 @@ Kconfig Options Application Examples -------------------- -* Typical use cases of GPTimer are listed in the example :example:`peripherals/timer_group/gptimer`. +.. list:: + + - Typical use cases of GPTimer are listed in the example :example:`peripherals/timer_group/gptimer`. + :SOC_TIMER_SUPPORT_ETM: - GPTimer capture external event's timestamp, with the help of ETM module: :example:`peripherals/timer_group/gptimer_capture_hc_sr04`. API Reference ------------- .. include-build-file:: inc/gptimer.inc +.. include-build-file:: inc/gptimer_etm.inc +.. include-build-file:: inc/gptimer_types.inc .. include-build-file:: inc/timer_types.inc .. [1] diff --git a/docs/en/api-reference/peripherals/index.rst b/docs/en/api-reference/peripherals/index.rst index ec8e0c13e671..f210eed53e41 100644 --- a/docs/en/api-reference/peripherals/index.rst +++ b/docs/en/api-reference/peripherals/index.rst @@ -11,6 +11,7 @@ Peripherals API :SOC_ADC_SUPPORTED: adc_calibration clk_tree :SOC_DAC_SUPPORTED: dac + :SOC_ETM_SUPPORTED: etm gpio gptimer :SOC_DEDICATED_GPIO_SUPPORTED: dedic_gpio diff --git a/docs/en/api-reference/system/async_memcpy.rst b/docs/en/api-reference/system/async_memcpy.rst index d5a481357179..3c00558cf9f8 100644 --- a/docs/en/api-reference/system/async_memcpy.rst +++ b/docs/en/api-reference/system/async_memcpy.rst @@ -74,6 +74,15 @@ Uninstall driver (optional) :cpp:func:`esp_async_memcpy_uninstall` is used to uninstall asynchronous memcpy driver. It's not necessary to uninstall the driver after each memcpy operation. If you know your application won't use this driver anymore, then this API can recycle the memory for you. +.. only:: SOC_GDMA_SUPPORT_ETM + + ETM Event + --------- + + Async memory copy is able to generate an event when one async memcpy operation is done. This event can be used to interact with the :doc:`ETM ` module. You can call :cpp:func:`esp_async_memcpy_new_etm_event` to get the ETM event handle. + + For how to connect the event to an ETM channel, please refer to the :doc:`ETM ` documentation. + API Reference ------------- diff --git a/docs/en/api-reference/system/esp_timer.rst b/docs/en/api-reference/system/esp_timer.rst index 115f4493efe3..d19d419dafb6 100644 --- a/docs/en/api-reference/system/esp_timer.rst +++ b/docs/en/api-reference/system/esp_timer.rst @@ -61,6 +61,15 @@ Callback functions Timer callbacks which are processed by ``ESP_TIMER_ISR`` method should not call the context switch call - ``portYIELD_FROM_ISR()``, instead of this you should use the :cpp:func:`esp_timer_isr_dispatch_need_yield` function. The context switch will be done after all ISR dispatch timers have been processed, if required by the system. +.. only:: SOC_SYSTIMER_SUPPORT_ETM + + ETM Event + --------- + + The esp_timer is constructed based on a hardware timer called *systimer*, which is able to generate the alarm event and interact with the :doc:`ETM ` module. You can call :cpp:func:`esp_timer_new_etm_alarm_event` to get the corresponding ETM event handle. + + For how to connect the event to an ETM channel, please refer to the :doc:`ETM ` documentation. + esp_timer during the light sleep -------------------------------- diff --git a/docs/zh_CN/api-reference/peripherals/etm.rst b/docs/zh_CN/api-reference/peripherals/etm.rst new file mode 100644 index 000000000000..d21a98420565 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/etm.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/peripherals/etm.rst diff --git a/docs/zh_CN/api-reference/peripherals/gptimer.rst b/docs/zh_CN/api-reference/peripherals/gptimer.rst index 6d144568e0d9..76621bff6a3f 100644 --- a/docs/zh_CN/api-reference/peripherals/gptimer.rst +++ b/docs/zh_CN/api-reference/peripherals/gptimer.rst @@ -17,16 +17,19 @@ 下文介绍了配置和操作定时器的常规步骤: -- :ref:`gptimer-resource-allocation` - 获取定时器句柄应设置的参数,以及如何在通用定时器完成工作时回收资源。 -- :ref:`set-and-get-count-value` - 如何强制定时器从起点开始计数,以及如何随时获取计数值。 -- :ref:`set-up-alarm-action` - 启动警报事件应设置的参数。 -- :ref:`gptimer-register-event-callbacks` - 如何将用户的特定代码挂载到警报事件回调函数。 -- :ref:`enable-and-disable-timer` - 如何使能和禁用定时器。 -- :ref:`start-and-stop-timer` - 通过不同报警行为启动定时器的典型使用场景。 -- :ref:`gptimer-power-management` - 选择不同的时钟源将会如何影响功耗。 -- :ref:`gptimer-iram-safe` - 在 cache 禁用的情况下,如何更好地让定时器处理中断事务以及实现 IO 控制功能。 -- :ref:`gptimer-thread-safety` - 驱动程序保证哪些 API 线程安全。 -- :ref:`gptimer-kconfig-options` - 支持的 Kconfig 选项,这些选项会对驱动程序行为产生不同影响。 +.. list:: + + - :ref:`gptimer-resource-allocation` - 获取定时器句柄应设置的参数,以及如何在通用定时器完成工作时回收资源。 + - :ref:`set-and-get-count-value` - 如何强制定时器从起点开始计数,以及如何随时获取计数值。 + - :ref:`set-up-alarm-action` - 启动警报事件应设置的参数。 + - :ref:`gptimer-register-event-callbacks` - 如何将用户的特定代码挂载到警报事件回调函数。 + - :ref:`enable-and-disable-timer` - 如何使能和禁用定时器。 + - :ref:`start-and-stop-timer` - 通过不同报警行为启动定时器的典型使用场景。 + :SOC_TIMER_SUPPORT_ETM: - :ref:`gptimer-etm-event-and-task` - 定时器提供了哪些事件和任务可以连接到 ETM 通道上。 + - :ref:`gptimer-power-management` - 选择不同的时钟源将会如何影响功耗。 + - :ref:`gptimer-iram-safe` - 在 cache 禁用的情况下,如何更好地让定时器处理中断事务以及实现 IO 控制功能。 + - :ref:`gptimer-thread-safety` - 驱动程序保证哪些 API 线程安全。 + - :ref:`gptimer-kconfig-options` - 支持的 Kconfig 选项,这些选项会对驱动程序行为产生不同影响。 .. _gptimer-resource-allocation: @@ -256,7 +259,23 @@ ESP_ERROR_CHECK(gptimer_enable(gptimer)); ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config)); -.. _gptimer-power-management: + +.. only:: SOC_TIMER_SUPPORT_ETM + + .. _gptimer-etm-event-and-task: + + ETM 事件与任务 + ^^^^^^^^^^^^^^ + + 定时器可以产生多种事件,这些事件可以连接到 :doc:`ETM ` 模块。:cpp:type:`gptimer_etm_event_type_t` 中列出了定时器能够产生的事件类型。用户可以通过调用 :cpp:func:`gptimer_new_etm_event` 来获得相应事件的 ETM event 句柄。同样地,定时器还公开了一些可被其他事件触发然后自动执行的任务。:cpp:type:`gptimer_etm_task_type_t` 中列出了定时器能够支持的任务类型。 用户可以通过调用 :cpp:func:`gptimer_new_etm_task` 来获得相应任务的 ETM task 句柄。 + + 关于如何将定时器事件和任务连接到 ETM 通道中,请参阅 :doc:`ETM ` 文档。 + + .. _gptimer-power-management: + +.. only:: not SOC_TIMER_SUPPORT_ETM + + .. _gptimer-power-management: 电源管理 ^^^^^^^^^^^^^^^^^ @@ -319,12 +338,17 @@ Kconfig 选项 应用示例 ------------------ -* 示例 :example:`peripherals/timer_group/gptimer` 中列出了通用定时器的典型用例。 +.. list:: + + - 示例 :example:`peripherals/timer_group/gptimer` 中列出了通用定时器的典型用例。 + :SOC_TIMER_SUPPORT_ETM: - 示例 :example:`peripherals/timer_group/gptimer_capture_hc_sr04` 展示了如何在 ETM 模块的帮助下,用定时器捕获外部事件的时间戳。 API 参考 ------------------- .. include-build-file:: inc/gptimer.inc +.. include-build-file:: inc/gptimer_etm.inc +.. include-build-file:: inc/gptimer_types.inc .. include-build-file:: inc/timer_types.inc .. [1] diff --git a/docs/zh_CN/api-reference/peripherals/index.rst b/docs/zh_CN/api-reference/peripherals/index.rst index bc68e2f6b9e6..2a9a8d69c6fe 100644 --- a/docs/zh_CN/api-reference/peripherals/index.rst +++ b/docs/zh_CN/api-reference/peripherals/index.rst @@ -11,6 +11,7 @@ :SOC_ADC_SUPPORTED: adc_calibration clk_tree :SOC_DAC_SUPPORTED: dac + :SOC_ETM_SUPPORTED: etm gpio gptimer :SOC_DEDICATED_GPIO_SUPPORTED: dedic_gpio diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index a71dbc31bf27..a2c07640f19d 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -193,6 +193,10 @@ examples/peripherals/temp_sensor: disable: - if: SOC_TEMP_SENSOR_SUPPORTED != 1 +examples/peripherals/timer_group/gptimer_capture_hc_sr04: + disable: + - if: SOC_TIMER_SUPPORT_ETM != 1 + examples/peripherals/touch_sensor: disable: - if: SOC_TOUCH_SENSOR_SUPPORTED != 1 diff --git a/examples/peripherals/timer_group/gptimer_capture_hc_sr04/CMakeLists.txt b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/CMakeLists.txt new file mode 100644 index 000000000000..0176118ad860 --- /dev/null +++ b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(gptimer_etm_capture) diff --git a/examples/peripherals/timer_group/gptimer_capture_hc_sr04/README.md b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/README.md new file mode 100644 index 000000000000..692651d778f6 --- /dev/null +++ b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/README.md @@ -0,0 +1,86 @@ +| Supported Targets | ESP32-C6 | +| ----------------- | -------- | + +# HC-SR04 Example based on GPTimer Capture and ETM + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The general purpose timer can capture internal timer count value into dedicated registers. This feature can be used to measure the time between two events. With the help of ETM (Event Task Matrix) peripheral, we can route different events to trigger the GPTimer's capture task. In this example, we configure one GPIO to detect any edge signals and then notify the GPTimer to capture the timestamp at once, all done by hardware. + +This example also shows how to decode the pulse width signals generated from a common HC-SR04 sonar sensor -- [HC-SR04](https://www.sparkfun.com/products/15569). + +The signal that HC-SR04 produces (and what can be handled by this example) is a simple pulse whose width indicates the measured distance. An excitation pulse is required to send to HC-SR04 on `Trig` pin to begin a new measurement. Then the pulse described above will appear on the `Echo` pin after a while. + +Typical signals: + +```text +Trig +-----+ + | | + | | + -----+ +----------------------- +Echo +-----+ + | | + | | + -----------------+ +----------- + + +---------------------------------------> + Timeline +``` + +## How to Use Example + +### Hardware Required + +* A development board with ESP SOC chip that supports ETM peripheral +* An HC-SR04 sensor module + +Connection : + +```text + +------+ +--------------------------------------+ ++-------+ | | | +| | VCC +--------------+ 5V | ++-------+ | | | + + Echo +----=====>----+ HC_SR04_ECHO_GPIO (internal pull up) | + | | | | + + Trig +----<=====----+ HC_SR04_TRIG_GPIO | ++-------| | | | +| | GND +--------------+ GND | ++-------| | | | + +------+ +--------------------------------------+ +``` + +You can change the GPIO number according to your board, by `HC_SR04_TRIG_GPIO` and `HC_SR04_ECHO_GPIO` in the [source code](main/gptimer_capture_hc_sr04.c). + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +```text +I (339) cpu_start: Starting scheduler. +I (344) example: Configure trig gpio +I (344) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (354) example: Configure echo gpio +I (354) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:3 +I (364) example: Create etm event handle for echo gpio +I (374) example: Create gptimer handle +I (384) example: Get gptimer etm task handle (capture) +I (384) example: Create ETM channel then connect gpio event and gptimer task +I (394) example: Install GPIO edge interrupt +I (404) example: Enable etm channel and gptimer +I (404) example: Start gptimer +I (424) example: Measured distance: 93.40cm +I (934) example: Measured distance: 103.93cm +I (1444) example: Measured distance: 102.09cm +I (1954) example: Measured distance: 122.47cm +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/timer_group/gptimer_capture_hc_sr04/main/CMakeLists.txt b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/main/CMakeLists.txt new file mode 100644 index 000000000000..1580e9084ed6 --- /dev/null +++ b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "gptimer_capture_hc_sr04.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/timer_group/gptimer_capture_hc_sr04/main/gptimer_capture_hc_sr04.c b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/main/gptimer_capture_hc_sr04.c new file mode 100644 index 000000000000..b1268c848060 --- /dev/null +++ b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/main/gptimer_capture_hc_sr04.c @@ -0,0 +1,151 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gptimer.h" +#include "driver/gpio.h" +#include "driver/gpio_etm.h" +#include "esp_log.h" + +static const char *TAG = "example"; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define HC_SR04_TRIG_GPIO 0 +#define HC_SR04_ECHO_GPIO 1 + +#define EXAMPLE_GPTIMER_RESOLUTION_HZ 1000000 // 1MHz, 1 tick = 1us + +/** + * @brief User defined context, to be passed to GPIO ISR callback function. + */ +typedef struct { + gptimer_handle_t gptimer; + TaskHandle_t task_to_notify; + gpio_num_t echo_gpio; +} gpio_callback_user_data_t; + +/** + * @brief GPIO ISR callback function, called when there's any edge detected on the GPIO. + */ +static void hc_sr04_echo_callback(void *user_data) +{ + static uint64_t cap_val_end_of_sample = 0; + static uint64_t cap_val_begin_of_sample = 0; + gpio_callback_user_data_t *callback_user_data = (gpio_callback_user_data_t *)user_data; + gpio_num_t echo_gpio = callback_user_data->echo_gpio; + TaskHandle_t task_to_notify = callback_user_data->task_to_notify; + gptimer_handle_t gptimer = callback_user_data->gptimer; + + if (gpio_get_level(echo_gpio) == 1) { + // store the timestamp when pos edge is detected + gptimer_get_captured_count(gptimer, &cap_val_begin_of_sample); + cap_val_end_of_sample = cap_val_begin_of_sample; + } else { + gptimer_get_captured_count(gptimer, &cap_val_end_of_sample); + uint32_t tof_ticks = (uint32_t)(cap_val_end_of_sample - cap_val_begin_of_sample); + + // notify the task to calculate the distance + xTaskNotifyFromISR(task_to_notify, tof_ticks, eSetValueWithOverwrite, NULL); + } +} + +/** + * @brief generate single pulse on Trig pin to start a new sample + */ +static void gen_trig_output(void) +{ + gpio_set_level(HC_SR04_TRIG_GPIO, 1); // set high + esp_rom_delay_us(10); + gpio_set_level(HC_SR04_TRIG_GPIO, 0); // set low +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Configure trig gpio"); + gpio_config_t trig_io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << HC_SR04_TRIG_GPIO, + }; + ESP_ERROR_CHECK(gpio_config(&trig_io_conf)); + // drive low by default + ESP_ERROR_CHECK(gpio_set_level(HC_SR04_TRIG_GPIO, 0)); + + ESP_LOGI(TAG, "Configure echo gpio"); + gpio_config_t echo_io_conf = { + .mode = GPIO_MODE_INPUT, + .intr_type = GPIO_INTR_ANYEDGE, // capture signal on both edge + .pull_up_en = true, // pull up internally + .pin_bit_mask = 1ULL << HC_SR04_ECHO_GPIO, + }; + ESP_ERROR_CHECK(gpio_config(&echo_io_conf)); + + ESP_LOGI(TAG, "Create etm event handle for echo gpio"); + esp_etm_event_handle_t gpio_event = NULL; + gpio_etm_event_config_t gpio_event_config = { + .edge = GPIO_ETM_EVENT_EDGE_ANY, // capture signal on both edge + }; + ESP_ERROR_CHECK(gpio_new_etm_event(&gpio_event_config, &gpio_event)); + ESP_ERROR_CHECK(gpio_etm_event_bind_gpio(gpio_event, HC_SR04_ECHO_GPIO)); + + ESP_LOGI(TAG, "Create gptimer handle"); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = EXAMPLE_GPTIMER_RESOLUTION_HZ, + }; + ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer)); + + ESP_LOGI(TAG, "Get gptimer etm task handle (capture)"); + esp_etm_task_handle_t gptimer_capture_task = NULL; + ESP_ERROR_CHECK(gptimer_new_etm_task(gptimer, GPTIMER_ETM_TASK_CAPTURE, &gptimer_capture_task)); + + ESP_LOGI(TAG, "Create ETM channel then connect gpio event and gptimer task"); + esp_etm_channel_handle_t etm_chan = NULL; + esp_etm_channel_config_t etm_chan_config = {}; + ESP_ERROR_CHECK(esp_etm_new_channel(&etm_chan_config, &etm_chan)); + // GPIO any edge ==> ETM channel ==> GPTimer capture task + ESP_ERROR_CHECK(esp_etm_channel_connect(etm_chan, gpio_event, gptimer_capture_task)); + + ESP_LOGI(TAG, "Install GPIO edge interrupt"); + ESP_ERROR_CHECK(gpio_install_isr_service(0)); + gpio_callback_user_data_t callback_user_data = { + .gptimer = gptimer, + .echo_gpio = HC_SR04_ECHO_GPIO, + .task_to_notify = xTaskGetCurrentTaskHandle(), + }; + ESP_ERROR_CHECK(gpio_isr_handler_add(HC_SR04_ECHO_GPIO, hc_sr04_echo_callback, &callback_user_data)); + + ESP_LOGI(TAG, "Enable etm channel and gptimer"); + ESP_ERROR_CHECK(esp_etm_channel_enable(etm_chan)); + ESP_ERROR_CHECK(gptimer_enable(gptimer)); + + // Print the ETM channel usage + ESP_ERROR_CHECK(esp_etm_dump(stdout)); + + ESP_LOGI(TAG, "Start gptimer"); + ESP_ERROR_CHECK(gptimer_start(gptimer)); + + uint32_t tof_ticks; + while (1) { + // trigger the sensor to start a new sample + gen_trig_output(); + // wait for echo done signal + if (xTaskNotifyWait(0x00, ULONG_MAX, &tof_ticks, pdMS_TO_TICKS(1000)) == pdTRUE) { + if (tof_ticks > 35000) { + // out of range + continue; + } + // convert the pulse width into measure distance + float distance = (float) tof_ticks / 58.0f; + ESP_LOGI(TAG, "Measured distance: %.2fcm", distance); + } + vTaskDelay(pdMS_TO_TICKS(500)); + } +} diff --git a/examples/peripherals/timer_group/gptimer_capture_hc_sr04/pytest_gptimer_capture_example.py b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/pytest_gptimer_capture_example.py new file mode 100644 index 000000000000..83b7945edf14 --- /dev/null +++ b/examples/peripherals/timer_group/gptimer_capture_hc_sr04/pytest_gptimer_capture_example.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 +@pytest.mark.generic +def test_gptimer_capture(dut: Dut) -> None: + dut.expect_exact('Configure trig gpio') + dut.expect_exact('Configure echo gpio') + dut.expect_exact('Create etm event handle for echo gpio') + dut.expect_exact('Create gptimer handle') + dut.expect_exact('Get gptimer etm task handle (capture)') + dut.expect_exact('Create ETM channel then connect gpio event and gptimer task') + dut.expect_exact('Install GPIO edge interrupt') + dut.expect_exact('Enable etm channel and gptimer') + dut.expect_exact('Start gptimer') diff --git a/tools/mocks/esp_hw_support/include/esp_etm.h b/tools/mocks/esp_hw_support/include/esp_etm.h new file mode 100644 index 000000000000..38963482db83 --- /dev/null +++ b/tools/mocks/esp_hw_support/include/esp_etm.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * NOTE: this is not the original header file from the esp_hw_support component. + * It is a stripped-down copy to support mocking. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ETM channel handle + */ +typedef struct esp_etm_channel_t *esp_etm_channel_handle_t; + +/** + * @brief ETM event handle + */ +typedef struct esp_etm_event_t *esp_etm_event_handle_t; + +/** + * @brief ETM task handle + */ +typedef struct esp_etm_task_t *esp_etm_task_handle_t; + +#ifdef __cplusplus +} +#endif diff --git a/tools/mocks/esp_timer/CMakeLists.txt b/tools/mocks/esp_timer/CMakeLists.txt index c9dac893d292..39fe5a78f49a 100644 --- a/tools/mocks/esp_timer/CMakeLists.txt +++ b/tools/mocks/esp_timer/CMakeLists.txt @@ -4,6 +4,10 @@ message(STATUS "building ESP TIMER MOCKS") idf_component_get_property(original_esp_timer_dir esp_timer COMPONENT_OVERRIDEN_DIR) -idf_component_mock(INCLUDE_DIRS "${original_esp_timer_dir}/include" +set(include_dirs + "${original_esp_timer_dir}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../esp_hw_support/include") + +idf_component_mock(INCLUDE_DIRS ${include_dirs} REQUIRES esp_common MOCK_HEADER_FILES ${original_esp_timer_dir}/include/esp_timer.h)