esp32: add implementation of esp_timer based on TG0 LAC timer

Closes: IDF-979
This commit is contained in:
Konstantin Kondrashov
2020-02-06 14:00:18 +08:00
committed by Angus Gratton
parent 67b0a79167
commit 739eb05bb9
63 changed files with 1261 additions and 504 deletions

View File

@@ -0,0 +1,509 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sys/param.h>
#include <string.h>
#include "soc/soc.h"
#include "esp_types.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_task.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/xtensa_api.h"
#include "esp_timer.h"
#include "esp_timer_impl.h"
#include "sdkconfig.h"
#ifdef CONFIG_ESP_TIMER_PROFILING
#define WITH_PROFILING 1
#endif
#ifndef NDEBUG
// Enable built-in checks in queue.h in debug builds
#define INVARIANTS
#endif
#include "sys/queue.h"
#define EVENT_ID_DELETE_TIMER 0xF0DE1E1E
#define TIMER_EVENT_QUEUE_SIZE 16
struct esp_timer {
uint64_t alarm;
uint64_t period;
union {
esp_timer_cb_t callback;
uint32_t event_id;
};
void* arg;
#if WITH_PROFILING
const char* name;
size_t times_triggered;
size_t times_armed;
uint64_t total_callback_run_time;
#endif // WITH_PROFILING
LIST_ENTRY(esp_timer) list_entry;
};
static bool is_initialized(void);
static esp_err_t timer_insert(esp_timer_handle_t timer);
static esp_err_t timer_remove(esp_timer_handle_t timer);
static bool timer_armed(esp_timer_handle_t timer);
static void timer_list_lock(void);
static void timer_list_unlock(void);
#if WITH_PROFILING
static void timer_insert_inactive(esp_timer_handle_t timer);
static void timer_remove_inactive(esp_timer_handle_t timer);
#endif // WITH_PROFILING
static const char* TAG = "esp_timer";
// list of currently armed timers
static LIST_HEAD(esp_timer_list, esp_timer) s_timers =
LIST_HEAD_INITIALIZER(s_timers);
#if WITH_PROFILING
// list of unarmed timers, used only to be able to dump statistics about
// all the timers
static LIST_HEAD(esp_inactive_timer_list, esp_timer) s_inactive_timers =
LIST_HEAD_INITIALIZER(s_timers);
#endif
// task used to dispatch timer callbacks
static TaskHandle_t s_timer_task;
// counting semaphore used to notify the timer task from ISR
static SemaphoreHandle_t s_timer_semaphore;
#if CONFIG_SPIRAM_USE_MALLOC
// memory for s_timer_semaphore
static StaticQueue_t s_timer_semaphore_memory;
#endif
// lock protecting s_timers, s_inactive_timers
static portMUX_TYPE s_timer_lock = portMUX_INITIALIZER_UNLOCKED;
esp_err_t esp_timer_create(const esp_timer_create_args_t* args,
esp_timer_handle_t* out_handle)
{
if (!is_initialized()) {
return ESP_ERR_INVALID_STATE;
}
if (args == NULL || args->callback == NULL || out_handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_timer_handle_t result = (esp_timer_handle_t) calloc(1, sizeof(*result));
if (result == NULL) {
return ESP_ERR_NO_MEM;
}
result->callback = args->callback;
result->arg = args->arg;
#if WITH_PROFILING
result->name = args->name;
timer_insert_inactive(result);
#endif
*out_handle = result;
return ESP_OK;
}
esp_err_t IRAM_ATTR esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us)
{
if (timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!is_initialized() || timer_armed(timer)) {
return ESP_ERR_INVALID_STATE;
}
timer_list_lock();
timer->alarm = esp_timer_get_time() + timeout_us;
timer->period = 0;
#if WITH_PROFILING
timer->times_armed++;
#endif
esp_err_t err = timer_insert(timer);
timer_list_unlock();
return err;
}
esp_err_t IRAM_ATTR esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us)
{
if (timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!is_initialized() || timer_armed(timer)) {
return ESP_ERR_INVALID_STATE;
}
timer_list_lock();
period_us = MAX(period_us, esp_timer_impl_get_min_period_us());
timer->alarm = esp_timer_get_time() + period_us;
timer->period = period_us;
#if WITH_PROFILING
timer->times_armed++;
#endif
esp_err_t err = timer_insert(timer);
timer_list_unlock();
return err;
}
esp_err_t IRAM_ATTR esp_timer_stop(esp_timer_handle_t timer)
{
if (timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!is_initialized() || !timer_armed(timer)) {
return ESP_ERR_INVALID_STATE;
}
return timer_remove(timer);
}
esp_err_t esp_timer_delete(esp_timer_handle_t timer)
{
if (timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (timer_armed(timer)) {
return ESP_ERR_INVALID_STATE;
}
timer_list_lock();
timer->event_id = EVENT_ID_DELETE_TIMER;
timer->alarm = esp_timer_get_time();
timer->period = 0;
timer_insert(timer);
timer_list_unlock();
return ESP_OK;
}
static IRAM_ATTR esp_err_t timer_insert(esp_timer_handle_t timer)
{
#if WITH_PROFILING
timer_remove_inactive(timer);
#endif
esp_timer_handle_t it, last = NULL;
if (LIST_FIRST(&s_timers) == NULL) {
LIST_INSERT_HEAD(&s_timers, timer, list_entry);
} else {
LIST_FOREACH(it, &s_timers, list_entry) {
if (timer->alarm < it->alarm) {
LIST_INSERT_BEFORE(it, timer, list_entry);
break;
}
last = it;
}
if (it == NULL) {
assert(last);
LIST_INSERT_AFTER(last, timer, list_entry);
}
}
if (timer == LIST_FIRST(&s_timers)) {
esp_timer_impl_set_alarm(timer->alarm);
}
return ESP_OK;
}
static IRAM_ATTR esp_err_t timer_remove(esp_timer_handle_t timer)
{
timer_list_lock();
LIST_REMOVE(timer, list_entry);
timer->alarm = 0;
timer->period = 0;
#if WITH_PROFILING
timer_insert_inactive(timer);
#endif
timer_list_unlock();
return ESP_OK;
}
#if WITH_PROFILING
static IRAM_ATTR void timer_insert_inactive(esp_timer_handle_t timer)
{
/* May be locked or not, depending on where this is called from.
* Lock recursively.
*/
timer_list_lock();
esp_timer_handle_t head = LIST_FIRST(&s_inactive_timers);
if (head == NULL) {
LIST_INSERT_HEAD(&s_inactive_timers, timer, list_entry);
} else {
/* Insert as head element as this is the fastest thing to do.
* Removal is O(1) anyway.
*/
LIST_INSERT_BEFORE(head, timer, list_entry);
}
timer_list_unlock();
}
static IRAM_ATTR void timer_remove_inactive(esp_timer_handle_t timer)
{
timer_list_lock();
LIST_REMOVE(timer, list_entry);
timer_list_unlock();
}
#endif // WITH_PROFILING
static IRAM_ATTR bool timer_armed(esp_timer_handle_t timer)
{
return timer->alarm > 0;
}
static IRAM_ATTR void timer_list_lock(void)
{
portENTER_CRITICAL_SAFE(&s_timer_lock);
}
static IRAM_ATTR void timer_list_unlock(void)
{
portEXIT_CRITICAL_SAFE(&s_timer_lock);
}
static void timer_process_alarm(esp_timer_dispatch_t dispatch_method)
{
/* unused, provision to allow running callbacks from ISR */
(void) dispatch_method;
timer_list_lock();
uint64_t now = esp_timer_impl_get_time();
esp_timer_handle_t it = LIST_FIRST(&s_timers);
while (it != NULL &&
it->alarm < now) {
LIST_REMOVE(it, list_entry);
if (it->event_id == EVENT_ID_DELETE_TIMER) {
free(it);
it = LIST_FIRST(&s_timers);
continue;
}
if (it->period > 0) {
it->alarm += it->period;
timer_insert(it);
} else {
it->alarm = 0;
#if WITH_PROFILING
timer_insert_inactive(it);
#endif
}
#if WITH_PROFILING
uint64_t callback_start = now;
#endif
esp_timer_cb_t callback = it->callback;
void* arg = it->arg;
timer_list_unlock();
(*callback)(arg);
timer_list_lock();
now = esp_timer_impl_get_time();
#if WITH_PROFILING
it->times_triggered++;
it->total_callback_run_time += now - callback_start;
#endif
it = LIST_FIRST(&s_timers);
}
esp_timer_handle_t first = LIST_FIRST(&s_timers);
if (first) {
esp_timer_impl_set_alarm(first->alarm);
}
timer_list_unlock();
}
static void timer_task(void* arg)
{
while (true){
int res = xSemaphoreTake(s_timer_semaphore, portMAX_DELAY);
assert(res == pdTRUE);
timer_process_alarm(ESP_TIMER_TASK);
}
}
static void IRAM_ATTR timer_alarm_handler(void* arg)
{
int need_yield;
if (xSemaphoreGiveFromISR(s_timer_semaphore, &need_yield) != pdPASS) {
ESP_EARLY_LOGD(TAG, "timer queue overflow");
return;
}
if (need_yield == pdTRUE) {
portYIELD_FROM_ISR();
}
}
static IRAM_ATTR bool is_initialized(void)
{
return s_timer_task != NULL;
}
esp_err_t esp_timer_init(void)
{
esp_err_t err;
if (is_initialized()) {
return ESP_ERR_INVALID_STATE;
}
#if CONFIG_SPIRAM_USE_MALLOC
memset(&s_timer_semaphore_memory, 0, sizeof(StaticQueue_t));
s_timer_semaphore = xSemaphoreCreateCountingStatic(TIMER_EVENT_QUEUE_SIZE, 0, &s_timer_semaphore_memory);
#else
s_timer_semaphore = xSemaphoreCreateCounting(TIMER_EVENT_QUEUE_SIZE, 0);
#endif
if (!s_timer_semaphore) {
err = ESP_ERR_NO_MEM;
goto out;
}
int ret = xTaskCreatePinnedToCore(&timer_task, "esp_timer",
ESP_TASK_TIMER_STACK, NULL, ESP_TASK_TIMER_PRIO, &s_timer_task, PRO_CPU_NUM);
if (ret != pdPASS) {
err = ESP_ERR_NO_MEM;
goto out;
}
err = esp_timer_impl_init(&timer_alarm_handler);
if (err != ESP_OK) {
goto out;
}
return ESP_OK;
out:
if (s_timer_task) {
vTaskDelete(s_timer_task);
s_timer_task = NULL;
}
if (s_timer_semaphore) {
vSemaphoreDelete(s_timer_semaphore);
s_timer_semaphore = NULL;
}
return ESP_ERR_NO_MEM;
}
esp_err_t esp_timer_deinit(void)
{
if (!is_initialized()) {
return ESP_ERR_INVALID_STATE;
}
/* Check if there are any active timers */
if (!LIST_EMPTY(&s_timers)) {
return ESP_ERR_INVALID_STATE;
}
/* We can only check if there are any timers which are not deleted if
* profiling is enabled.
*/
#if WITH_PROFILING
if (!LIST_EMPTY(&s_inactive_timers)) {
return ESP_ERR_INVALID_STATE;
}
#endif
esp_timer_impl_deinit();
vTaskDelete(s_timer_task);
s_timer_task = NULL;
vSemaphoreDelete(s_timer_semaphore);
s_timer_semaphore = NULL;
return ESP_OK;
}
static void print_timer_info(esp_timer_handle_t t, char** dst, size_t* dst_size)
{
size_t cb = snprintf(*dst, *dst_size,
#if WITH_PROFILING
"%-12s %12lld %12lld %9d %9d %12lld\n",
t->name, t->period, t->alarm,
t->times_armed, t->times_triggered, t->total_callback_run_time);
/* keep this in sync with the format string, used in esp_timer_dump */
#define TIMER_INFO_LINE_LEN 78
#else
"timer@%p %12lld %12lld\n", t, t->period, t->alarm);
#define TIMER_INFO_LINE_LEN 46
#endif
*dst += cb;
*dst_size -= cb;
}
esp_err_t esp_timer_dump(FILE* stream)
{
/* Since timer lock is a critical section, we don't want to print directly
* to stdout, since that may cause a deadlock if stdout is interrupt-driven
* (via the UART driver). Allocate sufficiently large chunk of memory first,
* print to it, then dump this memory to stdout.
*/
esp_timer_handle_t it;
/* First count the number of timers */
size_t timer_count = 0;
timer_list_lock();
LIST_FOREACH(it, &s_timers, list_entry) {
++timer_count;
}
#if WITH_PROFILING
LIST_FOREACH(it, &s_inactive_timers, list_entry) {
++timer_count;
}
#endif
timer_list_unlock();
/* Allocate the memory for this number of timers. Since we have unlocked,
* we may find that there are more timers. There's no bulletproof solution
* for this (can't allocate from a critical section), but we allocate
* slightly more and the output will be truncated if that is not enough.
*/
size_t buf_size = TIMER_INFO_LINE_LEN * (timer_count + 3);
char* print_buf = calloc(1, buf_size + 1);
if (print_buf == NULL) {
return ESP_ERR_NO_MEM;
}
/* Print to the buffer */
timer_list_lock();
char* pos = print_buf;
LIST_FOREACH(it, &s_timers, list_entry) {
print_timer_info(it, &pos, &buf_size);
}
#if WITH_PROFILING
LIST_FOREACH(it, &s_inactive_timers, list_entry) {
print_timer_info(it, &pos, &buf_size);
}
#endif
timer_list_unlock();
/* Print the buffer */
fputs(print_buf, stream);
free(print_buf);
return ESP_OK;
}
int64_t IRAM_ATTR esp_timer_get_next_alarm(void)
{
int64_t next_alarm = INT64_MAX;
timer_list_lock();
esp_timer_handle_t it = LIST_FIRST(&s_timers);
if (it) {
next_alarm = it->alarm;
}
timer_list_unlock();
return next_alarm;
}
int64_t IRAM_ATTR esp_timer_get_time(void)
{
return (int64_t) esp_timer_impl_get_time();
}

View File

@@ -0,0 +1,432 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sys/param.h"
#include "esp_timer_impl.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_system.h"
#include "esp_task.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp32/clk.h"
#include "soc/frc_timer_reg.h"
#include "soc/rtc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
/**
* @file esp_timer_frc.c
* @brief Implementation of chip-specific part of esp_timer
*
* This implementation uses FRC2 (legacy) timer of the ESP32. This timer is
* a 32-bit up-counting timer, with a programmable compare value (called 'alarm'
* hereafter). When the timer reaches compare value, interrupt is raised.
* The timer can be configured to produce an edge or a level interrupt.
*
* In this implementation the timer is used for two purposes:
* 1. To generate interrupts at certain moments — the upper layer of esp_timer
* uses this to trigger callbacks of esp_timer objects.
*
* 2. To keep track of time relative to application start. This facility is
* used both by the upper layer of esp_timer and by time functions, such as
* gettimeofday.
*
* Whenever an esp_timer timer is armed (configured to fire once or
* periodically), timer_insert function of the upper layer calls
* esp_timer_impl_set_alarm to enable the interrupt at the required moment.
* This implementation sets up the timer interrupt to fire at the earliest of
* two moments:
* a) the time requested by upper layer
* b) the time when the timer count reaches 0xffffffff (i.e. is about to overflow)
*
* Whenever the interrupt fires and timer overflow is detected, interrupt hander
* increments s_time_base_us variable, which is used for timekeeping.
*
* When the interrupt fires, the upper layer is notified, and it dispatches
* the callbacks (if any timers have expired) and sets new alarm value (if any
* timers are still active).
*
* At any point in time, esp_timer_impl_get_time will return the current timer
* value (expressed in microseconds) plus s_time_base_us. To account for the
* case when the timer counter has overflown, but the interrupt has not fired
* yet (for example, because interupts are temporarily disabled),
* esp_timer_impl_get_time will also check timer overflow flag, and will add
* s_timer_us_per_overflow to the returned value.
*
*/
/* Timer is clocked from APB. To allow for integer scaling factor between ticks
* and microseconds, divider 1 is used. 16 or 256 would not work for APB
* frequencies such as 40 or 26 or 2 MHz.
*/
#define TIMER_DIV 1
#define TIMER_DIV_CFG FRC_TIMER_PRESCALER_1
/* ALARM_OVERFLOW_VAL is used as timer alarm value when there are not timers
* enabled which need to fire within the next timer overflow period. This alarm
* is used to perform timekeeping (i.e. to track timer overflows).
* Due to the 0xffffffff cannot recognize the real overflow or the scenario that
* ISR happens follow set_alarm, so change the ALARM_OVERFLOW_VAL to resolve this problem.
* Set it to 0xefffffffUL. The remain 0x10000000UL(about 3 second) is enough to handle ISR.
*/
#define DEFAULT_ALARM_OVERFLOW_VAL 0xefffffffUL
/* Provision to set lower overflow value for unit testing. Lowering the
* overflow value helps check for race conditions which occur near overflow
* moment.
*/
#ifndef ESP_TIMER_DYNAMIC_OVERFLOW_VAL
#define ALARM_OVERFLOW_VAL DEFAULT_ALARM_OVERFLOW_VAL
#else
static uint32_t s_alarm_overflow_val = DEFAULT_ALARM_OVERFLOW_VAL;
#define ALARM_OVERFLOW_VAL (s_alarm_overflow_val)
#endif
static const char* TAG = "esp_timer_impl";
// Interrupt handle returned by the interrupt allocator
static intr_handle_t s_timer_interrupt_handle;
// Function from the upper layer to be called when the interrupt happens.
// Registered in esp_timer_impl_init.
static intr_handler_t s_alarm_handler;
// Time in microseconds from startup to the moment
// when timer counter was last equal to 0. This variable is updated each time
// when timer overflows, and when APB frequency switch is performed.
static uint64_t s_time_base_us;
// Number of timer ticks per microsecond. Calculated from APB frequency.
static uint32_t s_timer_ticks_per_us;
// Period between timer overflows, in microseconds.
// Equal to 2^32 / s_timer_ticks_per_us.
static uint32_t s_timer_us_per_overflow;
// When frequency switch happens, timer counter is reset to 0, s_time_base_us
// is updated, and alarm value is re-calculated based on the new APB frequency.
// However because the frequency switch can happen before the final
// interrupt handler is invoked, interrupt handler may see a different alarm
// value than the one which caused an interrupt. This can cause interrupt handler
// to consider that the interrupt has happened due to timer overflow, incrementing
// s_time_base_us. To avoid this, frequency switch hook sets this flag if
// it needs to set timer alarm value to ALARM_OVERFLOW_VAL. Interrupt handler
// will not increment s_time_base_us if this flag is set.
static bool s_mask_overflow;
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
// If DFS is enabled, upon the first frequency change this value is set to the
// difference between esp_timer value and RTC timer value. On every subsequent
// frequency change, s_time_base_us is adjusted to maintain the same difference
// between esp_timer and RTC timer. (All mentioned values are in microseconds.)
static uint64_t s_rtc_time_diff = 0;
#endif
// Spinlock used to protect access to static variables above and to the hardware
// registers.
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
//Use FRC_TIMER_LOAD_VALUE(1) instead of UINT32_MAX, convenience to change FRC TIMER for future
#define TIMER_IS_AFTER_OVERFLOW(a) (ALARM_OVERFLOW_VAL < (a) && (a) <= FRC_TIMER_LOAD_VALUE(1))
// Check if timer overflow has happened (but was not handled by ISR yet)
static inline bool IRAM_ATTR timer_overflow_happened(void)
{
return ((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) != 0 &&
((REG_READ(FRC_TIMER_ALARM_REG(1)) == ALARM_OVERFLOW_VAL && TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))) && !s_mask_overflow) ||
(!TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_ALARM_REG(1))) && TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))))));
}
static inline void IRAM_ATTR timer_count_reload(void)
{
//this function should be only called the real overflow happened. And the count cannot be very approach to 0xffffffff.
assert(TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))));
/* Restart the timer count by current time count minus ALARM_OVERFLOW_VAL(0xefffffff), it may cause error, if current tick is near boundary.
* But even if the error happen 100% per overflow(the distance of each real overflow is about 50 second),
* the error is 0.0125us*N per 50s(the FRC time clock is 80MHz), the N is the ticks run by the line following,
* Normally, N is less than 10, assume N is 10, so the error accumulation is only 6.48ms per month.
* In fact, if the CPU frequency is large than 80MHz. The error accumulation will be more less than 6.48ms per month.
* so It can be adopted.
*/
REG_WRITE(FRC_TIMER_LOAD_REG(1), REG_READ(FRC_TIMER_COUNT_REG(1)) - ALARM_OVERFLOW_VAL);
}
void esp_timer_impl_lock(void)
{
portENTER_CRITICAL(&s_time_update_lock);
}
void esp_timer_impl_unlock(void)
{
portEXIT_CRITICAL(&s_time_update_lock);
}
uint64_t IRAM_ATTR esp_timer_impl_get_time(void)
{
uint32_t timer_val;
uint64_t time_base;
uint32_t ticks_per_us;
bool overflow;
do {
/* Read all values needed to calculate current time */
timer_val = REG_READ(FRC_TIMER_COUNT_REG(1));
time_base = s_time_base_us;
overflow = timer_overflow_happened();
ticks_per_us = s_timer_ticks_per_us;
/* Read them again and compare */
/* In this function, do not call timer_count_reload() when overflow is true.
* Because there's remain count enough to allow FRC_TIMER_COUNT_REG grow
*/
if (REG_READ(FRC_TIMER_COUNT_REG(1)) > timer_val &&
time_base == *((volatile uint64_t*) &s_time_base_us) &&
ticks_per_us == *((volatile uint32_t*) &s_timer_ticks_per_us) &&
overflow == timer_overflow_happened()) {
break;
}
/* If any value has changed (other than the counter increasing), read again */
} while(true);
uint64_t result = time_base
+ timer_val / ticks_per_us;
return result;
}
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
// Use calculated alarm value if it is less than ALARM_OVERFLOW_VAL.
// Note that if by the time we update ALARM_REG, COUNT_REG value is higher,
// interrupt will not happen for another ALARM_OVERFLOW_VAL timer ticks,
// so need to check if alarm value is too close in the future (e.g. <2 us away).
int32_t offset = s_timer_ticks_per_us * 2;
do {
// Adjust current time if overflow has happened
if (timer_overflow_happened() ||
((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0))) {
// 1. timer_overflow_happened() checks overflow with the interrupt flag.
// 2. During several loops, the counter can be higher than the alarm and even step over ALARM_OVERFLOW_VAL boundary (the interrupt flag is not set).
timer_count_reload();
s_time_base_us += s_timer_us_per_overflow;
}
s_mask_overflow = false;
int64_t cur_count = REG_READ(FRC_TIMER_COUNT_REG(1));
// Alarm time relative to the moment when counter was 0
int64_t time_after_timebase_us = (int64_t)timestamp - s_time_base_us;
// Calculate desired timer compare value (may exceed 2^32-1)
int64_t compare_val = time_after_timebase_us * s_timer_ticks_per_us;
compare_val = MAX(compare_val, cur_count + offset);
uint32_t alarm_reg_val = ALARM_OVERFLOW_VAL;
if (compare_val < ALARM_OVERFLOW_VAL) {
alarm_reg_val = (uint32_t) compare_val;
}
REG_WRITE(FRC_TIMER_ALARM_REG(1), alarm_reg_val);
int64_t delta = (int64_t)alarm_reg_val - (int64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
if (delta <= 0) {
/*
When the timestamp is a bit less than the current counter then the alarm = current_counter + offset.
But due to CPU_freq in some case can be equal APB_freq the offset time can not exceed the overhead
(the alarm will be less than the counter) and it leads to the infinity loop.
To exclude this behavior to the offset was added the delta to have the opportunity to go through it.
*/
offset += abs((int)delta) + s_timer_ticks_per_us * 2;
} else {
break;
}
} while (1);
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
static void IRAM_ATTR timer_alarm_isr(void *arg)
{
portENTER_CRITICAL_ISR(&s_time_update_lock);
// Timekeeping: adjust s_time_base_us if counter has passed ALARM_OVERFLOW_VAL
if (timer_overflow_happened()) {
timer_count_reload();
s_time_base_us += s_timer_us_per_overflow;
}
s_mask_overflow = false;
// Clear interrupt status
REG_WRITE(FRC_TIMER_INT_REG(1), FRC_TIMER_INT_CLR);
// Set alarm to the next overflow moment. Later, upper layer function may
// call esp_timer_impl_set_alarm to change this to an earlier value.
REG_WRITE(FRC_TIMER_ALARM_REG(1), ALARM_OVERFLOW_VAL);
if ((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0)) {
/*
This check excludes the case when the alarm can be less than the counter.
Without this check, it is possible because DPORT uses 4-lvl, and users can use the 5 Hi-interrupt,
they can interrupt this function between FRC_TIMER_INT_CLR and setting the alarm = ALARM_OVERFLOW_VAL
that lead to the counter will go ahead leaving the alarm behind.
*/
timer_count_reload();
s_time_base_us += s_timer_us_per_overflow;
}
portEXIT_CRITICAL_ISR(&s_time_update_lock);
// Call the upper layer handler
(*s_alarm_handler)(arg);
}
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
{
portENTER_CRITICAL_ISR(&s_time_update_lock);
/* Bail out if the timer is not initialized yet */
if (s_timer_interrupt_handle == NULL) {
portEXIT_CRITICAL_ISR(&s_time_update_lock);
return;
}
uint32_t new_ticks_per_us = apb_ticks_per_us / TIMER_DIV;
uint32_t alarm = REG_READ(FRC_TIMER_ALARM_REG(1));
uint32_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
uint64_t ticks_to_alarm = alarm - count;
uint64_t new_ticks = (ticks_to_alarm * new_ticks_per_us) / s_timer_ticks_per_us;
uint32_t new_alarm_val;
if (alarm > count && new_ticks <= ALARM_OVERFLOW_VAL) {
new_alarm_val = new_ticks;
} else {
new_alarm_val = ALARM_OVERFLOW_VAL;
if (alarm != ALARM_OVERFLOW_VAL) {
s_mask_overflow = true;
}
}
REG_WRITE(FRC_TIMER_ALARM_REG(1), new_alarm_val);
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
s_time_base_us += count / s_timer_ticks_per_us;
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
// Due to the extra time required to read RTC time, don't attempt this
// adjustment when switching to a higher frequency (which usually
// happens in an interrupt).
if (new_ticks_per_us < s_timer_ticks_per_us) {
uint64_t rtc_time = esp_clk_rtc_time();
uint64_t new_rtc_time_diff = s_time_base_us - rtc_time;
if (s_rtc_time_diff != 0) {
uint64_t correction = new_rtc_time_diff - s_rtc_time_diff;
s_time_base_us -= correction;
} else {
s_rtc_time_diff = new_rtc_time_diff;
}
}
#endif // CONFIG_PM_DFS_USE_RTC_TIMER_REF
s_timer_ticks_per_us = new_ticks_per_us;
s_timer_us_per_overflow = ALARM_OVERFLOW_VAL / new_ticks_per_us;
portEXIT_CRITICAL_ISR(&s_time_update_lock);
}
void esp_timer_impl_advance(int64_t time_us)
{
assert(time_us > 0 && "negative adjustments not supported yet");
portENTER_CRITICAL(&s_time_update_lock);
uint64_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
/* Trigger an ISR to handle past alarms and set new one.
* ISR handler will run once we exit the critical section.
*/
REG_WRITE(FRC_TIMER_ALARM_REG(1), 0);
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
s_time_base_us += count / s_timer_ticks_per_us + time_us;
portEXIT_CRITICAL(&s_time_update_lock);
}
esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
{
s_alarm_handler = alarm_handler;
esp_err_t err = esp_intr_alloc(ETS_TIMER2_INTR_SOURCE,
ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM,
&timer_alarm_isr, NULL, &s_timer_interrupt_handle);
if (err != ESP_OK) {
ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err);
return err;
}
uint32_t apb_freq = rtc_clk_apb_freq_get();
s_timer_ticks_per_us = apb_freq / 1000000 / TIMER_DIV;
assert(s_timer_ticks_per_us > 0
&& apb_freq % TIMER_DIV == 0
&& "APB frequency does not result in a valid ticks_per_us value");
s_timer_us_per_overflow = ALARM_OVERFLOW_VAL / s_timer_ticks_per_us;
s_time_base_us = 0;
REG_WRITE(FRC_TIMER_ALARM_REG(1), ALARM_OVERFLOW_VAL);
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
REG_WRITE(FRC_TIMER_CTRL_REG(1),
TIMER_DIV_CFG | FRC_TIMER_ENABLE | FRC_TIMER_LEVEL_INT);
REG_WRITE(FRC_TIMER_INT_REG(1), FRC_TIMER_INT_CLR);
ESP_ERROR_CHECK( esp_intr_enable(s_timer_interrupt_handle) );
return ESP_OK;
}
void esp_timer_impl_deinit(void)
{
esp_intr_disable(s_timer_interrupt_handle);
REG_WRITE(FRC_TIMER_CTRL_REG(1), 0);
REG_WRITE(FRC_TIMER_ALARM_REG(1), 0);
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
esp_intr_free(s_timer_interrupt_handle);
s_timer_interrupt_handle = NULL;
}
// FIXME: This value is safe for 80MHz APB frequency.
// Should be modified to depend on clock frequency.
uint64_t IRAM_ATTR esp_timer_impl_get_min_period_us(void)
{
return 50;
}
#ifdef ESP_TIMER_DYNAMIC_OVERFLOW_VAL
uint32_t esp_timer_impl_get_overflow_val(void)
{
return s_alarm_overflow_val;
}
void esp_timer_impl_set_overflow_val(uint32_t overflow_val)
{
s_alarm_overflow_val = overflow_val;
/* update alarm value */
esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000);
}
#endif // ESP_TIMER_DYNAMIC_OVERFLOW_VAL
uint64_t esp_timer_impl_get_counter_reg(void)
{
return (uint64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
}
uint64_t esp_timer_impl_get_alarm_reg(void)
{
return (uint64_t)REG_READ(FRC_TIMER_ALARM_REG(1));
}
void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq")));
void esp_timer_private_advance(int64_t time_us) __attribute__((alias("esp_timer_impl_advance")));
void esp_timer_private_lock(void) __attribute__((alias("esp_timer_impl_lock")));
void esp_timer_private_unlock(void) __attribute__((alias("esp_timer_impl_unlock")));

View File

@@ -0,0 +1,282 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sys/param.h"
#include "esp_timer_impl.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_system.h"
#include "esp_task.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp32/clk.h"
#include "driver/periph_ctrl.h"
#include "soc/soc.h"
#include "soc/timer_group_reg.h"
#include "soc/rtc.h"
#include "freertos/FreeRTOS.h"
/**
* @file esp_timer_lac.c
* @brief Implementation of chip-specific part of esp_timer
*
* This implementation uses TG0 LAC timer of the ESP32. This timer is
* a 64-bit up-counting timer, with a programmable compare value (called 'alarm'
* hereafter). When the timer reaches compare value, interrupt is raised.
* The timer can be configured to produce an edge or a level interrupt.
*/
/* Selects which Timer Group peripheral to use */
#define LACT_MODULE 0
#if LACT_MODULE == 0
#define INTR_SOURCE_LACT ETS_TG0_LACT_LEVEL_INTR_SOURCE
#define PERIPH_LACT PERIPH_TIMG0_MODULE
#elif LACT_MODULE == 1
#define INTR_SOURCE_LACT ETS_TG1_LACT_LEVEL_INTR_SOURCE
#define PERIPH_LACT PERIPH_TIMG1_MODULE
#else
#error "Incorrect the number of LACT module (only 0 or 1)"
#endif
/* Desired number of timer ticks per microsecond.
* This value should be small enough so that all possible APB frequencies
* could be divided by it without remainder.
* On the other hand, the smaller this value is, the longer we need to wait
* after setting UPDATE_REG before the timer value can be read.
* If TICKS_PER_US == 1, then we need to wait up to 1 microsecond, which
* makes esp_timer_impl_get_time function take too much time.
* The value TICKS_PER_US == 2 allows for most of the APB frequencies, and
* allows reading the counter quickly enough.
*/
#define TICKS_PER_US 2
/* Shorter register names, used in this file */
#define CONFIG_REG (TIMG_LACTCONFIG_REG(LACT_MODULE))
#define RTC_STEP_REG (TIMG_LACTRTC_REG(LACT_MODULE))
#define ALARM_LO_REG (TIMG_LACTALARMLO_REG(LACT_MODULE))
#define ALARM_HI_REG (TIMG_LACTALARMHI_REG(LACT_MODULE))
#define COUNT_LO_REG (TIMG_LACTLO_REG(LACT_MODULE))
#define COUNT_HI_REG (TIMG_LACTHI_REG(LACT_MODULE))
#define UPDATE_REG (TIMG_LACTUPDATE_REG(LACT_MODULE))
#define LOAD_REG (TIMG_LACTLOAD_REG(LACT_MODULE))
#define LOAD_LO_REG (TIMG_LACTLOADLO_REG(LACT_MODULE))
#define LOAD_HI_REG (TIMG_LACTLOADHI_REG(LACT_MODULE))
#define INT_ENA_REG (TIMG_INT_ENA_TIMERS_REG(LACT_MODULE))
#define INT_ST_REG (TIMG_INT_ST_TIMERS_REG(LACT_MODULE))
#define INT_CLR_REG (TIMG_INT_CLR_TIMERS_REG(LACT_MODULE))
/* Helper type to convert between a 64-bit value and a pair of 32-bit values without shifts and masks */
typedef struct {
union {
struct {
uint32_t lo;
uint32_t hi;
};
uint64_t val;
};
} timer_64b_reg_t;
static const char* TAG = "esp_timer_impl";
/* Interrupt handle returned by the interrupt allocator */
static intr_handle_t s_timer_interrupt_handle;
/* Function from the upper layer to be called when the interrupt happens.
* Registered in esp_timer_impl_init.
*/
static intr_handler_t s_alarm_handler;
/* Spinlock used to protect access to the hardware registers. */
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
void esp_timer_impl_lock(void)
{
portENTER_CRITICAL(&s_time_update_lock);
}
void esp_timer_impl_unlock(void)
{
portEXIT_CRITICAL(&s_time_update_lock);
}
uint64_t IRAM_ATTR esp_timer_impl_get_counter_reg(void)
{
uint32_t lo, hi;
uint32_t lo_start = REG_READ(COUNT_LO_REG);
uint32_t div = REG_GET_FIELD(CONFIG_REG, TIMG_LACT_DIVIDER);
/* The peripheral doesn't have a bit to indicate that the update is done, so we poll the
* lower 32 bit part of the counter until it changes, or a timeout expires.
*/
REG_WRITE(UPDATE_REG, 1);
do {
lo = REG_READ(COUNT_LO_REG);
} while (lo == lo_start && div-- > 0);
/* Since this function is called without a critical section, verify that LO and HI
* registers are consistent. That is, if an interrupt happens between reading LO and
* HI registers, and esp_timer_impl_get_time is called from an ISR, then try to
* detect this by the change in LO register value, and re-read both registers.
*/
do {
lo_start = lo;
hi = REG_READ(COUNT_HI_REG);
lo = REG_READ(COUNT_LO_REG);
} while (lo != lo_start);
timer_64b_reg_t result = {
.lo = lo,
.hi = hi
};
return result.val;
}
uint64_t IRAM_ATTR esp_timer_impl_get_time(void)
{
return esp_timer_impl_get_counter_reg() / TICKS_PER_US;
}
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
int64_t offset = TICKS_PER_US * 2;
uint64_t now_time = esp_timer_impl_get_counter_reg();
timer_64b_reg_t alarm = { .val = MAX(timestamp * TICKS_PER_US, now_time + offset) };
do {
REG_CLR_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
REG_WRITE(ALARM_LO_REG, alarm.lo);
REG_WRITE(ALARM_HI_REG, alarm.hi);
REG_SET_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
now_time = esp_timer_impl_get_counter_reg();
int64_t delta = (int64_t)alarm.val - (int64_t)now_time;
if (delta <= 0 && REG_GET_FIELD(INT_ST_REG, TIMG_LACT_INT_ST) == 0) {
// new alarm is less than the counter and the interrupt flag is not set
offset += abs((int)delta) + TICKS_PER_US * 2;
alarm.val = now_time + offset;
} else {
// finish if either (alarm > counter) or the interrupt flag is already set.
break;
}
} while(1);
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
static void IRAM_ATTR timer_alarm_isr(void *arg)
{
/* Clear interrupt status */
REG_WRITE(INT_CLR_REG, TIMG_LACT_INT_CLR);
/* Call the upper layer handler */
(*s_alarm_handler)(arg);
}
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
{
portENTER_CRITICAL(&s_time_update_lock);
assert(apb_ticks_per_us >= 3 && "divider value too low");
assert(apb_ticks_per_us % TICKS_PER_US == 0 && "APB frequency (in MHz) should be divisible by TICK_PER_US");
REG_SET_FIELD(CONFIG_REG, TIMG_LACT_DIVIDER, apb_ticks_per_us / TICKS_PER_US);
portEXIT_CRITICAL(&s_time_update_lock);
}
void esp_timer_impl_advance(int64_t time_diff_us)
{
portENTER_CRITICAL(&s_time_update_lock);
uint64_t now = esp_timer_impl_get_time();
timer_64b_reg_t dst = { .val = (now + time_diff_us) * TICKS_PER_US };
REG_WRITE(LOAD_LO_REG, dst.lo);
REG_WRITE(LOAD_HI_REG, dst.hi);
REG_WRITE(LOAD_REG, 1);
portEXIT_CRITICAL(&s_time_update_lock);
}
esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
{
s_alarm_handler = alarm_handler;
periph_module_enable(PERIPH_LACT);
/* Reset the state */
REG_WRITE(CONFIG_REG, 0);
REG_WRITE(LOAD_LO_REG, 0);
REG_WRITE(LOAD_HI_REG, 0);
REG_WRITE(ALARM_LO_REG, UINT32_MAX);
REG_WRITE(ALARM_HI_REG, UINT32_MAX);
REG_WRITE(LOAD_REG, 1);
REG_SET_BIT(INT_CLR_REG, TIMG_LACT_INT_CLR);
esp_err_t err = esp_intr_alloc(INTR_SOURCE_LACT,
ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM,
&timer_alarm_isr, NULL, &s_timer_interrupt_handle);
if (err != ESP_OK) {
ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err);
return err;
}
/* In theory, this needs a shared spinlock with the timer group driver.
* However since esp_timer_impl_init is called early at startup, this
* will not cause issues in practice.
*/
REG_SET_BIT(INT_ENA_REG, TIMG_LACT_INT_ENA);
esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000);
REG_SET_BIT(CONFIG_REG, TIMG_LACT_INCREASE |
TIMG_LACT_LEVEL_INT_EN |
TIMG_LACT_EN);
// Set the step for the sleep mode when the timer will work
// from a slow_clk frequency instead of the APB frequency.
uint32_t slowclk_ticks_per_us = esp_clk_slowclk_cal_get() * TICKS_PER_US;
REG_SET_FIELD(RTC_STEP_REG, TIMG_LACT_RTC_STEP_LEN, slowclk_ticks_per_us);
ESP_ERROR_CHECK( esp_intr_enable(s_timer_interrupt_handle) );
return ESP_OK;
}
void esp_timer_impl_deinit(void)
{
REG_WRITE(CONFIG_REG, 0);
REG_SET_BIT(INT_CLR_REG, TIMG_LACT_INT_CLR);
/* TODO: also clear TIMG_LACT_INT_ENA; however see the note in esp_timer_impl_init. */
esp_intr_disable(s_timer_interrupt_handle);
esp_intr_free(s_timer_interrupt_handle);
s_timer_interrupt_handle = NULL;
}
/* FIXME: This value is safe for 80MHz APB frequency, should be modified to depend on clock frequency. */
uint64_t IRAM_ATTR esp_timer_impl_get_min_period_us(void)
{
return 50;
}
uint64_t esp_timer_impl_get_alarm_reg(void)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
timer_64b_reg_t alarm = {
.lo = REG_READ(ALARM_LO_REG),
.hi = REG_READ(ALARM_HI_REG)
};
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
return alarm.val;
}
void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq")));
void esp_timer_private_advance(int64_t time_us) __attribute__((alias("esp_timer_impl_advance")));
void esp_timer_private_lock(void) __attribute__((alias("esp_timer_impl_lock")));
void esp_timer_private_unlock(void) __attribute__((alias("esp_timer_impl_unlock")));

View File

@@ -0,0 +1,249 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sys/param.h>
#include "esp_timer_impl.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "soc/rtc.h"
#include "soc/systimer_reg.h"
#include "soc/periph_defs.h"
#include "freertos/FreeRTOS.h"
/**
* @file esp_timer_systimer.c
* @brief Implementation of chip-specific part of esp_timer
*
* This implementation uses SYSTIMER of the ESP32-S2. This timer is
* a 64-bit up-counting timer, with a programmable compare value (called 'alarm'
* hereafter). When the timer reaches compare value, interrupt is raised.
* The timer can be configured to produce an edge or a level interrupt.
*/
/* esp_timer uses the 2 compare unit of SYSTIMER. */
#define INTR_SOURCE_LACT (ETS_SYSTIMER_TARGET2_EDGE_INTR_SOURCE)
// Registers
#define COUNT_LO_REG (SYSTIMER_VALUE_LO_REG)
#define COUNT_HI_REG (SYSTIMER_VALUE_HI_REG)
#define LOAD_LO_REG (SYSTIMER_LOAD_LO_REG)
#define LOAD_HI_REG (SYSTIMER_LOAD_HI_REG)
#define ALARM_LO_REG (SYSTIMER_TARGET2_LO_REG)
#define ALARM_HI_REG (SYSTIMER_TARGET2_HI_REG)
// Macros
#define ENABLE_CLK() (REG_SET_BIT(SYSTIMER_CONF_REG, SYSTIMER_CLK_EN))
#define ENABLE_INT() (REG_SET_BIT(SYSTIMER_INT_ENA_REG, SYSTIMER_INT2_ENA))
#define DISABLE_INT() (REG_CLR_BIT(SYSTIMER_INT_ENA_REG, SYSTIMER_INT2_ENA))
#define GET_INT_FLAG() (REG_GET_FIELD(SYSTIMER_INT_RAW_REG, SYSTIMER_INT2_RAW))
#define CLEAR_INT() (REG_WRITE(SYSTIMER_INT_CLR_REG, SYSTIMER_INT2_CLR))
#define DISABLE_COMPARE_UNIT() (REG_WRITE(SYSTIMER_TARGET2_CONF_REG, 0))
#define ENABLE_COMPARE_UNIT() (REG_WRITE(SYSTIMER_TARGET2_CONF_REG, SYSTIMER_TARGET2_WORK_EN))
#define APPLY_LOADED_VAL() (REG_SET_BIT(SYSTIMER_LOAD_REG, SYSTIMER_TIMER_LOAD))
#define SETTING_STEP_FOR_PLL_SRC(step) (REG_SET_FIELD(SYSTIMER_STEP_REG, SYSTIMER_TIMER_PLL_STEP, step))
#define SETTING_STEP_FOR_XTAL_SRC(step) (REG_SET_FIELD(SYSTIMER_STEP_REG, SYSTIMER_TIMER_XTAL_STEP, step))
#define UPDATE_COUNT_REG() (REG_WRITE(SYSTIMER_UPDATE_REG, SYSTIMER_TIMER_UPDATE))
#define GET_FLAG_UPDATED_COUNT_REG() (REG_GET_BIT(SYSTIMER_UPDATE_REG, SYSTIMER_TIMER_VALUE_VALID))
/* Helper type to convert between a 64-bit value and a pair of 32-bit values without shifts and masks */
typedef struct {
union {
struct {
uint32_t lo;
uint32_t hi;
};
uint64_t val;
};
} timer_64b_reg_t;
static const char* TAG = "esp_timer_impl";
/* Interrupt handle returned by the interrupt allocator */
static intr_handle_t s_timer_interrupt_handle;
/* Function from the upper layer to be called when the interrupt happens.
* Registered in esp_timer_impl_init.
*/
static intr_handler_t s_alarm_handler;
/* Number of timer ticks per microsecond. */
#define TICKS_PER_US (APB_CLK_FREQ / 1000000)
/* Spinlock used to protect access to the hardware registers. */
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
void esp_timer_impl_lock(void)
{
portENTER_CRITICAL(&s_time_update_lock);
}
void esp_timer_impl_unlock(void)
{
portEXIT_CRITICAL(&s_time_update_lock);
}
uint64_t IRAM_ATTR esp_timer_impl_get_counter_reg(void)
{
uint32_t lo, lo_start, hi;
/* Set the "update" bit and wait for acknowledgment */
UPDATE_COUNT_REG();
while (GET_FLAG_UPDATED_COUNT_REG() == 0) {
;
}
/* Read LO, HI, then LO again, check that LO returns the same value.
* This accounts for the case when an interrupt may happen between reading
* HI and LO values, and this function may get called from the ISR.
* In this case, the repeated read will return consistent values.
*/
lo_start = REG_READ(COUNT_LO_REG);
do {
lo = lo_start;
hi = REG_READ(COUNT_HI_REG);
lo_start = REG_READ(COUNT_LO_REG);
} while (lo_start != lo);
timer_64b_reg_t result = {
.lo = lo,
.hi = hi
};
return result.val;
}
uint64_t IRAM_ATTR esp_timer_impl_get_time(void)
{
return esp_timer_impl_get_counter_reg() / TICKS_PER_US;
}
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
int64_t offset = TICKS_PER_US * 2;
uint64_t now_time = esp_timer_impl_get_counter_reg();
timer_64b_reg_t alarm = { .val = MAX(timestamp * TICKS_PER_US, now_time + offset) };
do {
DISABLE_COMPARE_UNIT();
REG_WRITE(ALARM_LO_REG, alarm.lo);
REG_WRITE(ALARM_HI_REG, alarm.hi);
ENABLE_COMPARE_UNIT();
now_time = esp_timer_impl_get_counter_reg();
int64_t delta = (int64_t)alarm.val - (int64_t)now_time;
if (delta <= 0 && GET_INT_FLAG() == 0) {
// new alarm is less than the counter and the interrupt flag is not set
offset += abs((int)delta) + TICKS_PER_US * 2;
alarm.val = now_time + offset;
} else {
// finish if either (alarm > counter) or the interrupt flag is already set.
break;
}
} while(1);
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
static void IRAM_ATTR timer_alarm_isr(void *arg)
{
// clear the interrupt
CLEAR_INT();
/* Call the upper layer handler */
(*s_alarm_handler)(arg);
}
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
{
/* If this function was called when switching APB clock to PLL, don't need
* do anything: the SYSTIMER_TIMER_PLL_STEP is already correct.
* If this was called when switching APB clock to XTAL, need to adjust
* XTAL_STEP value accordingly.
*/
if (apb_ticks_per_us != TICKS_PER_US) {
assert((TICKS_PER_US % apb_ticks_per_us) == 0 && "TICK_PER_US should be divisible by APB frequency (in MHz)");
SETTING_STEP_FOR_XTAL_SRC(TICKS_PER_US / apb_ticks_per_us);
}
}
void esp_timer_impl_advance(int64_t time_us)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
timer_64b_reg_t new_count = { .val = esp_timer_impl_get_counter_reg() + time_us * TICKS_PER_US };
REG_WRITE(LOAD_LO_REG, new_count.lo);
REG_WRITE(LOAD_HI_REG, new_count.hi);
APPLY_LOADED_VAL();
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
{
s_alarm_handler = alarm_handler;
esp_err_t err = esp_intr_alloc(INTR_SOURCE_LACT,
ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_EDGE,
&timer_alarm_isr, NULL, &s_timer_interrupt_handle);
if (err != ESP_OK) {
ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err);
return err;
}
ENABLE_CLK();
/* Configure the counter:
* - increment by 1 when running from PLL (80 ticks per microsecond),
* - increment by 2 when running from XTAL (40 ticks per microsecond).
* Note that if the APB frequency is derived from XTAL with divider != 1,
* XTAL_STEP needs to be adjusted accordingly. For example, if
* the APB frequency is XTAL/4 = 10 MHz, then XTAL_STEP should be set to 8.
* This is handled in esp_timer_impl_update_apb_freq function above.
*/
assert(rtc_clk_xtal_freq_get() == 40 && TICKS_PER_US == 80
&& "update the following code to support other XTAL:APB frequency ratios");
SETTING_STEP_FOR_PLL_SRC(1);
SETTING_STEP_FOR_XTAL_SRC(2);
/* TODO: if SYSTIMER is used for anything else, access to SYSTIMER_INT_ENA_REG has to be
* protected by a shared spinlock. Since this code runs as part of early startup, this
* is practically not an issue. Same applies to SYSTIMER_CLK_EN above.
*/
ENABLE_INT();
ESP_ERROR_CHECK(esp_intr_enable(s_timer_interrupt_handle));
return ESP_OK;
}
void esp_timer_impl_deinit(void)
{
esp_intr_disable(s_timer_interrupt_handle);
DISABLE_COMPARE_UNIT();
/* TODO: may need a spinlock, see the note related to SYSTIMER_INT_ENA_REG in esp_timer_impl_init */
DISABLE_INT();
esp_intr_free(s_timer_interrupt_handle);
s_timer_interrupt_handle = NULL;
}
uint64_t IRAM_ATTR esp_timer_impl_get_min_period_us(void)
{
return 50;
}
uint64_t esp_timer_impl_get_alarm_reg(void)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
timer_64b_reg_t alarm = {
.lo = REG_READ(ALARM_LO_REG),
.hi = REG_READ(ALARM_HI_REG)
};
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
return alarm.val;
}
void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq")));
void esp_timer_private_advance(int64_t time_us) __attribute__((alias("esp_timer_impl_advance")));
void esp_timer_private_lock(void) __attribute__((alias("esp_timer_impl_lock")));
void esp_timer_private_unlock(void) __attribute__((alias("esp_timer_impl_unlock")));

View File

@@ -0,0 +1,129 @@
// Copyright 2010-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* ets_timer module implements a set of legacy timer APIs which are
* used by the WiFi driver. This is done on top of the newer esp_timer APIs.
* Applications should not use ets_timer functions, as they may change without
* notice.
*/
#include <string.h>
#include "esp_types.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "soc/frc_timer_reg.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/xtensa_api.h"
#include "sdkconfig.h"
#include "esp_timer.h"
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/rom/ets_sys.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/ets_sys.h"
#endif
/* We abuse 'timer_arg' field of ETSTimer structure to hold a pointer to esp_timer */
#define ESP_TIMER(p_ets_timer) ((esp_timer_handle_t) (p_ets_timer)->timer_arg)
/* We abuse 'timer_expire' field of ETSTimer structure to hold a magic value
* signifying that the contents of the timer was zeroed out.
*/
#define TIMER_INITIALIZED_FIELD(p_ets_timer) ((p_ets_timer)->timer_expire)
#define TIMER_INITIALIZED_VAL 0x12121212
static IRAM_ATTR bool timer_initialized(ETSTimer *ptimer)
{
return TIMER_INITIALIZED_FIELD(ptimer) == TIMER_INITIALIZED_VAL;
}
void ets_timer_setfn(ETSTimer *ptimer, ETSTimerFunc *pfunction, void *parg)
{
if (!timer_initialized(ptimer)) {
memset(ptimer, 0, sizeof(*ptimer));
TIMER_INITIALIZED_FIELD(ptimer) = TIMER_INITIALIZED_VAL;
}
if (ESP_TIMER(ptimer) == NULL) {
const esp_timer_create_args_t create_args = {
.callback = pfunction,
.arg = parg,
.name = "ETSTimer",
.dispatch_method = ESP_TIMER_TASK
};
ESP_ERROR_CHECK( esp_timer_create(&create_args, (esp_timer_handle_t*)&(ptimer->timer_arg)) );
}
}
void IRAM_ATTR ets_timer_arm_us(ETSTimer *ptimer, uint32_t time_us, bool repeat_flag)
{
assert(timer_initialized(ptimer));
esp_timer_stop(ESP_TIMER(ptimer)); // no error check
if (!repeat_flag) {
ESP_ERROR_CHECK( esp_timer_start_once(ESP_TIMER(ptimer), time_us) );
} else {
ESP_ERROR_CHECK( esp_timer_start_periodic(ESP_TIMER(ptimer), time_us) );
}
}
void IRAM_ATTR ets_timer_arm(ETSTimer *ptimer, uint32_t time_ms, bool repeat_flag)
{
uint64_t time_us = 1000LL * (uint64_t) time_ms;
assert(timer_initialized(ptimer));
esp_timer_stop(ESP_TIMER(ptimer)); // no error check
if (!repeat_flag) {
ESP_ERROR_CHECK( esp_timer_start_once(ESP_TIMER(ptimer), time_us) );
} else {
ESP_ERROR_CHECK( esp_timer_start_periodic(ESP_TIMER(ptimer), time_us) );
}
}
void ets_timer_done(ETSTimer *ptimer)
{
if (timer_initialized(ptimer)) {
esp_timer_delete(ESP_TIMER(ptimer));
ptimer->timer_arg = NULL;
TIMER_INITIALIZED_FIELD(ptimer) = 0;
}
}
void IRAM_ATTR ets_timer_disarm(ETSTimer *ptimer)
{
if (timer_initialized(ptimer)) {
esp_timer_stop(ESP_TIMER(ptimer));
}
}
void ets_timer_init(void)
{
}
void ets_timer_deinit(void)
{
}
void os_timer_setfn(ETSTimer *ptimer, ETSTimerFunc *pfunction, void *parg) __attribute__((alias("ets_timer_setfn")));
void os_timer_disarm(ETSTimer *ptimer) __attribute__((alias("ets_timer_disarm")));
void os_timer_arm_us(ETSTimer *ptimer,uint32_t u_seconds,bool repeat_flag) __attribute__((alias("ets_timer_arm_us")));
void os_timer_arm(ETSTimer *ptimer,uint32_t milliseconds,bool repeat_flag) __attribute__((alias("ets_timer_arm")));
void os_timer_done(ETSTimer *ptimer) __attribute__((alias("ets_timer_done")));