diff --git a/cpu/esp32/Makefile.dep b/cpu/esp32/Makefile.dep index a85d61af3f..078200bee3 100644 --- a/cpu/esp32/Makefile.dep +++ b/cpu/esp32/Makefile.dep @@ -95,6 +95,11 @@ ifneq (,$(filter pm_layered,$(USEMODULE))) USEMODULE += periph_rtc endif +ifneq (,$(filter periph_rtt,$(USEMODULE))) + USEMODULE += periph_rtt_hw_sys + USEMODULE += periph_rtt_hw_rtc +endif + ifneq (,$(filter shell,$(USEMODULE))) USEMODULE += ps endif diff --git a/cpu/esp32/Makefile.features b/cpu/esp32/Makefile.features index 50faf56698..c7100987a5 100644 --- a/cpu/esp32/Makefile.features +++ b/cpu/esp32/Makefile.features @@ -6,3 +6,4 @@ FEATURES_PROVIDED += arch_esp32 FEATURES_PROVIDED += esp_wifi_enterprise FEATURES_PROVIDED += periph_adc_ctrl FEATURES_PROVIDED += periph_rtc +FEATURES_PROVIDED += periph_rtt diff --git a/cpu/esp32/doc.txt b/cpu/esp32/doc.txt index e56d63f2bd..db1204eaa7 100644 --- a/cpu/esp32/doc.txt +++ b/cpu/esp32/doc.txt @@ -38,7 +38,7 @@ 5. [PWM Channels](#esp32_pwm_channels) 6. [SPI Interfaces](#esp32_spi_interfaces) 7. [Timers](#esp32_timers) - 8. [RTC Timer](#esp32_rtc_timer) + 8. [RTT Implementation](#esp32_rtt_counter) 9. [UART Interfaces](#esp32_uart_interfaces) 10. [CAN Interfaces](#esp32_can_interfaces) 11. [Power Management](#esp32_power_management) @@ -120,8 +120,7 @@ Module | Default | Short description [esp_log_startup](#esp32_esp_log_module) | not used | enable additional startup information [esp_log_tagged](#esp32_esp_log_module) | not used | add additional information to the log output [esp_now](#esp32_esp_now_network_interface) | not used | enable the ESP-NOW network device -[esp_rtc_timer](#esp32_rtc_timer) | not used | enable RTC hardware timer with internal 150 kHz RC oscillator -[esp_rtc_timer_32k](#esp32_rtc_timer) | not used | enable RTC hardware timer with external 32.768 kHz crystal. +[esp_rtc_timer_32k](#esp32_rtt_counter) | not used | use RTC timer with external 32.768 kHz crystal as RTT [esp_spi_ram](#esp32_spi_ram) | not used | enable SPI RAM [esp_spiffs](#esp32_spiffs_device) | not used | enable SPIFFS for on-board flash memory [esp_wifi](#esp32_wifi_network_interface) | not used | enable the Wifi network device in WPA2 personal mode @@ -422,7 +421,6 @@ esp_log_colored | Enable colored log output, see section [Log output](#esp32_esp esp_log_startup | Enable additional startup information, see section [Log output](#esp32_esp_log_module). esp_log_tagged | Add additional information to the log output, see section [Log output](#esp32_esp_log_module). esp_now | Enable the built-in WiFi module with the ESP-NOW protocol as `netdev` network device, see section [ESP-NOW Network Interface](#esp32_esp_now_network_interface). -esp_rtc_timer | Enable RTC hardware timer with internal 150 kHz RC oscillator. esp_rtc_timer_32k | Enable RTC hardware timer with external 32.768 kHz crystal. esp_spiffs | Enable the optional SPIFFS drive in on-board flash memory, see section [SPIFFS Device](#esp32_spiffs_device). esp_spi_ram | Enable the optional SPI RAM, see section [SPI RAM Modules](#esp32_spi_ram). @@ -839,37 +837,31 @@ to application's makefile. Timers are MCU built-in features and not board-specific. There is nothing to be configured. -\anchor esp32_rtc_timer -## RTC Timer  [[TOC](#esp32_toc)] +\anchor esp32_rtt_counter +## RTT implementation  [[TOC](#esp32_toc)] -The RTC hardware timer of the ESP32 can be clocked with either an external -32.768 kHz crystal or the internal adjustable 150 kHz RC oscillator. If the -the external 32.768 kHz crystal is not available, the internal 150 kHz RC -oscillator is used automatically. However, since this internal 150 kHz RC -oscillator is not very accurate, the RTC low-level driver uses by default -the PLL-controlled 64-bit microsecond system timer to emulate the RTC timer. +The RTT peripheral low-level driver provides a RTT (Real Time Timer) with +a frequency of 32.768 kHz. It either uses the RTC hardware timer if an +external 32.768 kHz crystal is connected to the ESP32 or the PLL-controlled +64-bit microsecond system timer to emulate the RTC timer. -To allow the use of the RTC hardware timer for boards with an external -32 kHz crystal, the pseudomodules `esp_rtc_timer` and `esp_rtc_timer_32k` -can be used to control which timer is used by the RTC low-level driver -as following: +Whether an external 32.768 kHz crystal is connected to the ESP32 is +specified as a feature by the board definition using the pseudomodule +`esp_rtc_timer_32k`. If the feature `esp_rtc_timer_32k` is defined but the +external 32.768 kHz crystal is not recognized during startup, the +PLL controlled 64 bit microsecond system timer is used to emulate the +RTC timer. -- **esp_rtc_timer**: - Use always the RTC hardware timer with the **internal 150 kHz RC** oscillator. +The RTT is retained during light and deep sleep as well as during a restart. +The RTC hardware timer is used for this purpose, regardless of whether an +external 32.768 kHz crystal is connected to the ESP32 or the internal 150 kHz +RC oscillator is used. All current timer values are saved in the RTC memory +before entering a sleep mode or restart and are restored after when waking up +or restarting. -- **esp_rtc_timer_32k**: - Use the RTC hardware timer with the **external 32.768 kHz crystal**. If the - external 32.768 kHz crystal is not available, the RTC hardware timer - is used with the internal 150 kHz RC oscillator. - -If none of the modules above is enabled, the PLL-driven **emulated RTC timer** -is used. In this case, the RTC hardware timer with the internal RC 150 kHz -oscillator is only used in deep sleep mode and during a reset. - -@note The accuracy of the emulated RTC timer is better than the accuracy of the -RTC hardware timer with the internal 150 kHz RC oscillator. If you have not -connected an external 32.768 kHz crystal, you should use the default -configuration. +@note The RTT implementation is also used to implement a RTC (Real Time Clock) +peripheral. For this purpose the module `rt_rtc` is automatically enabled +when the feature `periph_rtc` is used. \anchor esp32_uart_interfaces ## UART Interfaces  [[TOC](#esp32_toc)] diff --git a/cpu/esp32/include/periph_cpu.h b/cpu/esp32/include/periph_cpu.h index 9cda64b2f1..b70c2ace93 100644 --- a/cpu/esp32/include/periph_cpu.h +++ b/cpu/esp32/include/periph_cpu.h @@ -409,6 +409,27 @@ typedef struct { #define RNG_DATA_REG_ADDR (0x3ff75144) /** @} */ +/** + * @name RTT and RTC configuration + * @{ + */ + +/** + * @brief RTT frequency definition + * + * The RTT frequency is always 32.768 kHz even if no external crystal is + * connected. In this case the RTT value counted with the internal 150 kHz + * RC oscillator is converted to a value for an RTT with 32.768 kHz. + */ +#define RTT_FREQUENCY (32768UL) + +/** + * @brief RTT is a 32-bit counter + */ +#define RTT_MAX_VALUE (0xFFFFFFFFUL) + +/** @} */ + /** * @name SPI configuration * diff --git a/cpu/esp32/include/rtt_arch.h b/cpu/esp32/include/rtt_arch.h new file mode 100644 index 0000000000..4aee0b4f5e --- /dev/null +++ b/cpu/esp32/include/rtt_arch.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32 + * @{ + * + * @file + * @brief Architecture specific RTT functions for ESP32 + * + * The RTT peripheral driver implements a 32-bit RTT counter with a frequency + * of 32.768 kHz. It uses either + * + * - the 48-bit RTC counter if an external 32.678 kHz crystal is connected or + * - the 64-bit microsecond system timer. + * + * For this purpose, a hardware abstraction layer is defined by a driver + * interface of the type rtt_hw_driver_t, which generally provides a + * 48-bit RTC counter with a frequency of 32.678 kHz. This way the RTT + * implementation always sees a 48-bit counter with a frequency of 32.768 kHz + * regardless of which hardware implementation is actually used. + * + * If pseudomodule `esp_rtc_timer_32` is enabled by the board definition and + * the 32.768 kHz crystal is actually connected, the 48-bit RTC counter + * is used. Otherwise, the 64 bit microsecond system timer is used. + * + * Since the 64-bit microsecond system timer does not work during light/deep + * or during a reboot, the status of the 64-bit microsecond system timer is + * saved in RTC memory before entering a sleep mode or reboot. When leaving + * the sleep mode or after a reboot, it will be updated from the RTC counter. + * + * @author Gunar Schorcht + */ + +#ifndef RTT_ARCH_H +#define RTT_ARCH_H + +#include "periph/rtt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Maximum value of the 48-bit RTT hardware counter + */ +#define RTT_HW_COUNTER_MAX ((1ULL << 48) - 1) + +/** + * @brief RTT hardware abstraction layer driver + */ +typedef struct { + /** + * @brief Init the current hardware counter + */ + void (*init)(void); + + /** + * @brief Get the current hardware counter value + * @return 48-bit counter value with a frequency of 32.768 kHz + */ + uint64_t (*get_counter)(void); + + /** + * @brief Set the hardware alarm + * @param[in] alarm alarm time as 32.768 kHz ticks + * @param[in] cb function called on alarm interrupt + * @param[in] arg argument used as parameter for the @p cb function + */ + void (*set_alarm)(uint32_t alarm, rtt_cb_t cb, void *arg); + + /** + * @brief Clear the hardware alarm + */ + void (*clear_alarm)(void); + + /** + * @brief Save the counter value before sleep or reboot if necessary + */ + void (*save_counter)(void); + + /** + * @brief Restore the counter value before sleep or reboot + * @param[in] in_init true if function is called after deep sleep or + * reboot, false otherwise + */ + void (*restore_counter)(bool in_init); + + /** + * @brief Enable the RTT hardware counter + */ + void (*poweron)(void); + + /** + * @brief Disable the RTT hardware counter + */ + void (*poweroff)(void); + +} rtt_hw_driver_t; + +/** + * @brief Called before the power management enters a light or deep sleep mode + * @param mode sleep mode that is entered + * @return time to sleep in us + */ +uint64_t rtt_pm_sleep_enter(unsigned mode); + +/** + * @brief Called after the power management left light sleep mode + * @param cause wake-up cause + */ +void rtt_pm_sleep_exit(uint32_t cause); + +#ifdef __cplusplus +} +#endif + +#endif /* RTT_ARCH_H */ +/** @} */ diff --git a/cpu/esp32/periph/rtt.c b/cpu/esp32/periph/rtt.c new file mode 100644 index 0000000000..9324b60782 --- /dev/null +++ b/cpu/esp32/periph/rtt.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2020 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32 + * @ingroup drivers_periph_rtt + * @{ + * + * @file + * @brief Low-level RTT driver implementation for ESP32 + * + * @author Gunar Schorcht + * + * @} + */ + +#include "cpu.h" +#include "esp_attr.h" +#include "esp_sleep.h" +#include "irq_arch.h" +#include "log.h" +#include "periph/rtt.h" +#include "rtt_arch.h" +#include "syscalls.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define RTC_CLK_CAL_FRACT 19 /* fractional bits of calibration value */ + +/* contains the values as given at the interface */ +typedef struct { + uint32_t alarm; /**< alarm value as set at the interface */ + rtt_cb_t alarm_cb; /**< alarm callback */ + rtt_cb_t overflow_cb; /**< overflow callback */ + void *alarm_arg; /**< argument for alarm callback */ + void *overflow_arg; /**< argument for overflow callback */ + uint32_t alarm_active; /**< alarm that is currently active */ + bool alarm_set; /**< indicates whether an alarm is active */ + bool wakeup; /**< indicates whether next alarm is a wake-up */ +} rtt_counter_t; + +static rtt_counter_t rtt_counter; + +static uint32_t RTC_BSS_ATTR _rtt_offset; + +/* we can't include soc/rtc.h because of rtc_init declaration conflicts */ +extern uint32_t rtc_clk_slow_freq_get_hz(void); + +/* forward declaration of functions */ +void rtt_restore_counter(bool sys_time); +static void _rtt_update_hw_alarm(void); +static void IRAM_ATTR _rtt_isr(void *arg); + +/* forward declarations of driver functions */ +uint64_t _rtc_get_counter(void); + +/* declaration of hardware counters */ +extern const rtt_hw_driver_t _rtt_hw_sys_driver; +extern const rtt_hw_driver_t _rtt_hw_rtc_driver; + +/* used hardware driver (default _rtt_hw_sys_driver) */ +static const rtt_hw_driver_t *_rtt_hw = &_rtt_hw_sys_driver; + +void rtt_init(void) +{ + if (IS_USED(MODULE_ESP_RTC_TIMER_32K)) { + /* check whether the 32.678 kHz crystal is working */ + if (rtc_clk_slow_freq_get_hz() == 32768) { + _rtt_hw = &_rtt_hw_rtc_driver; + } + else { + LOG_ERROR("[rtt] 32.768 kHz crystal not used!\n"); + } + } + + DEBUG("%s rtt_offset=%u @rtc=%llu rtc_active=%d @sys_time=%llu\n", __func__, + _rtt_offset, _rtc_get_counter(), + (_rtt_hw == &_rtt_hw_sys_driver) ? 1 : 0, system_get_time_64()); + + /* init the hardware counter if necessary */ + _rtt_hw->init(); + + /* restore counter from RTC after deep sleep or reboot */ + rtt_restore_counter(true); + + /* clear alarm settings */ + rtt_clear_alarm(); + rtt_clear_overflow_cb(); + + /* power on the module and enable interrupts */ + rtt_poweron(); +} + +void rtt_poweron(void) +{ + _rtt_hw->poweron(); +} + +void rtt_poweroff(void) +{ + _rtt_hw->poweroff(); +} + +void rtt_set_overflow_cb(rtt_cb_t cb, void *arg) +{ + /* there is no overflow interrupt, we emulate */ + rtt_counter.overflow_cb = cb; + rtt_counter.overflow_arg = arg; + + _rtt_update_hw_alarm(); +} + +void rtt_clear_overflow_cb(void) +{ + /* there is no overflow interrupt, we emulate */ + rtt_counter.overflow_cb = NULL; + rtt_counter.overflow_arg = NULL; + + _rtt_update_hw_alarm(); +} + +uint32_t rtt_get_counter(void) +{ + /* we use only the lower 32 bit of the 48-bit RTC counter */ + uint32_t counter = _rtt_hw->get_counter() + _rtt_offset; + DEBUG("%s counter=%u @sys_time=%u\n", __func__, counter, system_get_time()); + return counter; +} + +void rtt_set_counter(uint32_t counter) +{ + uint32_t _rtt_current = _rtt_hw->get_counter(); + _rtt_offset = counter - _rtt_current; + + DEBUG("%s set=%u rtt_offset=%u @rtt=%u\n", + __func__, counter, _rtt_offset, _rtt_current); + + _rtt_update_hw_alarm(); +} + +void rtt_set_alarm(uint32_t alarm, rtt_cb_t cb, void *arg) +{ + uint32_t counter = rtt_get_counter(); + rtt_counter.alarm = alarm; + rtt_counter.alarm_cb = cb; + rtt_counter.alarm_arg = arg; + + DEBUG("%s alarm=%u @rtt=%u\n", __func__, alarm, counter); + + _rtt_update_hw_alarm(); +} + +void rtt_clear_alarm(void) +{ + /* clear the alarm */ + rtt_counter.alarm = 0; + rtt_counter.alarm_cb = NULL; + rtt_counter.alarm_arg = NULL; + + DEBUG("%s @rtt=%u\n", __func__, (uint32_t)_rtt_hw->get_counter()); + + _rtt_update_hw_alarm(); +} + +uint32_t rtt_get_alarm(void) +{ + return rtt_counter.alarm; +} + +void rtt_save_counter(void) +{ + _rtt_hw->save_counter(); +} + +void rtt_restore_counter(bool in_init) +{ + _rtt_hw->restore_counter(in_init); +} + +uint64_t rtt_pm_sleep_enter(unsigned mode) +{ + rtt_save_counter(); + + if (!rtt_counter.alarm_set) { + return 0; + } + + uint32_t counter = rtt_get_counter(); + uint64_t t_diff = RTT_TICKS_TO_US(rtt_counter.alarm_active - counter); + + DEBUG("%s rtt_alarm=%u @rtt=%u t_diff=%llu\n", __func__, + rtt_counter.alarm_active, counter, t_diff); + + if (t_diff) { + rtt_counter.wakeup = true; + esp_sleep_enable_timer_wakeup(t_diff); + } + else { + rtt_counter.wakeup = false; + } + + return t_diff; +} + +void rtt_pm_sleep_exit(uint32_t cause) +{ + rtt_restore_counter(false); + + if (cause == ESP_SLEEP_WAKEUP_TIMER) { + _rtt_isr(NULL); + } +} + +static void _rtt_update_hw_alarm(void) +{ + if (rtt_counter.alarm_cb && ((rtt_counter.alarm > rtt_get_counter()) || + (rtt_counter.overflow_cb == NULL))) { + /* alarm is the next event if either the alarm is greater than the + current counter value or the overflow callback is not set. */ + rtt_counter.alarm_active = rtt_counter.alarm; + rtt_counter.alarm_set = true; + _rtt_hw->set_alarm(rtt_counter.alarm - _rtt_offset, _rtt_isr, NULL); + } + else if (rtt_counter.overflow_cb) { + /* otherwise the overflow is the next event if its callback is set */ + rtt_counter.alarm_active = 0; + rtt_counter.alarm_set = true; + _rtt_hw->set_alarm(0 - _rtt_offset, _rtt_isr, NULL); + } + else { + rtt_counter.alarm_set = false; + _rtt_hw->clear_alarm(); + } +} + +static void IRAM_ATTR _rtt_isr(void *arg) +{ + DEBUG("%s\n", __func__); + + uint32_t alarm = rtt_counter.alarm_active; + + if (rtt_counter.wakeup) { + rtt_counter.wakeup = false; + DEBUG("%s wakeup alarm alarm=%u rtt_alarm=%u @rtt=%u\n", + __func__, alarm, rtt_counter.alarm_active, rtt_get_counter()); + } + + if ((alarm == rtt_counter.alarm) && rtt_counter.alarm_cb) { + DEBUG("%s alarm\n", __func__); + rtt_cb_t alarm_cb = rtt_counter.alarm_cb; + void * alarm_arg = rtt_counter.alarm_arg; + /* clear the alarm first, includes setting next alarm to overflow */ + rtt_clear_alarm(); + /* call the alarm handler afterwards if a callback is set */ + if (alarm_cb) { + alarm_cb(alarm_arg); + } + } + + if (alarm == 0) { + DEBUG("%s overflow\n", __func__); + /* set next alarm which is either an alarm if configured or overflow */ + _rtt_update_hw_alarm(); + /* call the overflow handler if set */ + if (rtt_counter.overflow_cb) { + rtt_counter.overflow_cb(rtt_counter.overflow_arg); + } + } + + DEBUG("%s next rtt=%u\n", __func__, rtt_counter.alarm_active); +} + +uint32_t _rtt_hw_to_rtt_counter(uint32_t hw_counter) +{ + return hw_counter + _rtt_offset; +} diff --git a/cpu/esp32/periph/rtt_hw_rtc.c b/cpu/esp32/periph/rtt_hw_rtc.c new file mode 100644 index 0000000000..162bb883e9 --- /dev/null +++ b/cpu/esp32/periph/rtt_hw_rtc.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2020 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32 + * @ingroup drivers_periph_rtt + * @{ + * + * @file + * @brief Low-level RTT driver implementation for ESP32 + * + * @author Gunar Schorcht + * + * @} + */ + +#include "cpu.h" +#include "esp_attr.h" +#include "esp/common_macros.h" +#include "esp_common.h" +#include "esp_sleep.h" +#include "irq_arch.h" +#include "log.h" +#include "periph/rtt.h" +#include "rtt_arch.h" +#include "soc/dport_reg.h" +#include "soc/rtc_cntl_struct.h" +#include "syscalls.h" +#include "timex.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define RTC_CLK_CAL_FRACT 19 /* fractional bits of calibration value */ + +typedef struct { + uint32_t alarm_set; /**< alarm set at interface */ + rtt_cb_t alarm_cb; /**< alarm callback */ + void *alarm_arg; /**< argument for alarm callback */ +} _rtc_alarm_t; + +static _rtc_alarm_t _rtc_alarm; + +/* we can't include soc/rtc.h because of rtc_init declaration conflicts */ +extern uint32_t esp_clk_slowclk_cal_get(void); + +/* convert hardware counter to 32-bit RTT counter */ +uint32_t _rtt_hw_to_rtt_counter(uint32_t hw_counter); + +static void IRAM _rtc_isr(void *arg); + +/* converts a 48-bit RTC counter value to microseconds */ +uint64_t _rtc_counter_to_us(uint64_t raw) +{ + const uint32_t cal = esp_clk_slowclk_cal_get(); + return ((((raw >> 32) * cal) << (32 - RTC_CLK_CAL_FRACT)) + /* high part */ + (((raw & 0xffffffff) * cal) >> RTC_CLK_CAL_FRACT)); /* low part */ +} + +static void _rtc_init(void) +{ +} + +static void _rtc_poweron(void) +{ + /* route all interrupt sources to the same RTT level type interrupt */ + intr_matrix_set(PRO_CPU_NUM, ETS_RTC_CORE_INTR_SOURCE, CPU_INUM_RTC); + + /* set interrupt handler and enable the CPU interrupt */ + xt_set_interrupt_handler(CPU_INUM_RTC, _rtc_isr, NULL); + xt_ints_on(BIT(CPU_INUM_RTC)); +} + +static void _rtc_poweroff(void) +{ + /* reset interrupt handler and disable the CPU interrupt */ + xt_ints_off(BIT(CPU_INUM_RTC)); + xt_set_interrupt_handler(CPU_INUM_RTC, NULL, NULL); +} + +uint64_t _rtc_get_counter(void) +{ + /* trigger timer register update */ + RTCCNTL.time_update.update = 1; + /* wait until values in registers are valid */ + while (!RTCCNTL.time_update.valid) { + ets_delay_us(1); + } + /* read the time from 48-bit counter and return */ + return (((uint64_t)RTCCNTL.time1.val) << 32) + RTCCNTL.time0; +} + +static void _rtc_set_alarm(uint32_t alarm, rtt_cb_t cb, void *arg) +{ + /* compute the time difference for 32.768 kHz as 32-bit value */ + uint64_t rtc_counter = _rtc_get_counter(); + uint32_t rtt_diff = alarm - rtc_counter; + + /* use computed time difference directly to set the RTC counter alarm */ + uint64_t rtc_alarm = (rtc_counter + rtt_diff) & RTT_HW_COUNTER_MAX; + + DEBUG("%s alarm=%u rtt_diff=%u rtc_alarm=%llu @rtc=%llu\n", + __func__, alarm, rtt_diff, rtc_alarm, rtc_counter); + + /* save the alarm configuration for interrupt handling */ + _rtc_alarm.alarm_set = alarm; + _rtc_alarm.alarm_cb = cb; + _rtc_alarm.alarm_arg = arg; + + /* set the timer value */ + RTCCNTL.slp_timer0 = rtc_alarm & 0xffffffff; + RTCCNTL.slp_timer1.slp_val_hi = rtc_alarm >> 32; + + DEBUG("%s %08x%08x \n", __func__, + RTCCNTL.slp_timer1.slp_val_hi, RTCCNTL.slp_timer0); + + /* enable RTC timer alarm */ + RTCCNTL.slp_timer1.main_timer_alarm_en = 1; + + /* clear and enable RTC timer interrupt */ + RTCCNTL.int_clr.rtc_main_timer = 1; + RTCCNTL.int_ena.rtc_main_timer = 1; +} + +static void _rtc_clear_alarm(void) +{ + /* disable alarms first */ + RTCCNTL.slp_timer1.main_timer_alarm_en = 0; + + /* clear the bit in interrupt enable and status register */ + RTCCNTL.int_clr.rtc_main_timer = 0; + RTCCNTL.int_ena.rtc_main_timer = 0; + + /* reset the alarm configuration for interrupt handling */ + _rtc_alarm.alarm_set = 0; + _rtc_alarm.alarm_cb = NULL; + _rtc_alarm.alarm_arg = NULL; +} + +static void _rtc_save_counter(void) +{ +} + +static void _rtc_restore_counter(bool in_init) +{ + (void)in_init; +} + +static void IRAM _rtc_isr(void *arg) +{ + /* disable alarms first */ + RTCCNTL.slp_timer1.main_timer_alarm_en = 0; + + /* clear the bit in interrupt enable and status register */ + RTCCNTL.int_clr.rtc_main_timer = 0; + RTCCNTL.int_ena.rtc_main_timer = 0; + + /* save the lower 32 bit of the current counter value */ + uint32_t counter = _rtc_get_counter(); + + DEBUG("%s %u\n", __func__, counter); + + if (_rtc_alarm.alarm_cb) { + DEBUG("%s alarm %u\n", __func__, counter); + + rtt_cb_t alarm_cb = _rtc_alarm.alarm_cb; + void *alarm_arg = _rtc_alarm.alarm_arg; + + /* clear the alarm first */ + _rtc_alarm.alarm_cb = NULL; + _rtc_alarm.alarm_arg = NULL; + + /* call the alarm handler afterwards if callback was defined */ + alarm_cb(alarm_arg); + } +} + +const rtt_hw_driver_t _rtt_hw_rtc_driver = { + .init = _rtc_init, + .get_counter = _rtc_get_counter, + .set_alarm = _rtc_set_alarm, + .clear_alarm = _rtc_clear_alarm, + .poweron = _rtc_poweron, + .poweroff = _rtc_poweroff, + .save_counter = _rtc_save_counter, + .restore_counter = _rtc_restore_counter, +}; diff --git a/cpu/esp32/periph/rtt_hw_sys.c b/cpu/esp32/periph/rtt_hw_sys.c new file mode 100644 index 0000000000..4a003f8efb --- /dev/null +++ b/cpu/esp32/periph/rtt_hw_sys.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2020 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32 + * @ingroup drivers_periph_rtt + * @{ + * + * @file + * @brief Low-level RTT driver implementation for ESP32 + * + * @author Gunar Schorcht + * + * @} + */ + +#include "cpu.h" +#include "esp_attr.h" +#include "esp/common_macros.h" +#include "esp_sleep.h" +#include "irq_arch.h" +#include "log.h" +#include "periph/rtt.h" +#include "rtt_arch.h" +#include "soc/timer_group_struct.h" +#include "syscalls.h" +#include "timex.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define TIMER_SYSTEM_GROUP TIMERG0 +#define TIMER_SYSTEM_INT_MASK BIT(0) +#define TIMER_SYSTEM_INT_SRC ETS_TG0_T0_LEVEL_INTR_SOURCE + +#define SYS_US_TO_TICKS(us) ((((uint64_t)us) << 15) / US_PER_SEC) +#define SYS_TICKS_TO_US(cnt) (((uint64_t)cnt * US_PER_SEC) >> 15) + +typedef struct { + uint32_t alarm_set; /**< alarm set at interface */ + rtt_cb_t alarm_cb; /**< alarm callback */ + void *alarm_arg; /**< argument for alarm callback */ +} _sys_alarm_t; + +static _sys_alarm_t _sys_alarm; + +/* variables used to save counters during sleep or reboot */ +static uint64_t RTC_BSS_ATTR _rtc_counter_saved; +static uint64_t RTC_BSS_ATTR _sys_counter_saved; + +/* the offset of the system time to the RTC time in microseconds */ +static uint64_t _sys_counter_offset; + +/* forward declarations of functions required from RTC counter */ +extern uint64_t _rtc_get_counter(void); +extern uint64_t _rtc_counter_to_us(uint64_t raw); + +static void IRAM _sys_isr(void *arg); + +static void _sys_init(void) +{ +} + +static void _sys_poweron(void) +{ + /* route all interrupt sources to the same RTT level type interrupt */ + intr_matrix_set(PRO_CPU_NUM, TIMER_SYSTEM_INT_SRC, CPU_INUM_RTC); + + /* set interrupt handler and enable the CPU interrupt */ + xt_set_interrupt_handler(CPU_INUM_RTC, _sys_isr, NULL); + xt_ints_on(BIT(CPU_INUM_RTC)); +} + +static void _sys_poweroff(void) +{ + /* reset interrupt handler and disable the CPU interrupt */ + xt_ints_off(BIT(CPU_INUM_RTC)); + xt_set_interrupt_handler(CPU_INUM_RTC, NULL, NULL); +} + +static uint64_t _sys_get_counter(void) +{ + /* convert the 64-bit microsecond system time to 48-bit 32.768 kHz time */ + return SYS_US_TO_TICKS(system_get_time_64() + _sys_counter_offset) & RTT_HW_COUNTER_MAX; +} + +static void _sys_set_alarm(uint32_t alarm, rtt_cb_t cb, void *arg) +{ + /* compute the time difference for 32.768 kHz as 32-bit value */ + uint32_t rtt_diff = alarm - _sys_get_counter(); + + /* + * convert the computed time difference for 32.768 kHz to a 64-bit + * microsecond value and determine the alarm time for the 64-bit + * microsecond system timer + */ + uint64_t _sys_diff = SYS_TICKS_TO_US(rtt_diff); + uint64_t _sys_time = system_get_time_64(); + uint64_t _sys_alarm_time = _sys_time + _sys_diff; + + DEBUG("%s alarm=%u rtt_diff=%u " + "sys_diff=%llu sys_alarm=%llu @sys_time=%llu\n", __func__, + alarm, rtt_diff, _sys_diff, _sys_alarm_time, _sys_time); + + /* save the alarm configuration for interrupt handling */ + _sys_alarm.alarm_set = alarm; + _sys_alarm.alarm_cb = cb; + _sys_alarm.alarm_arg = arg; + + /* set the timer value */ + TIMER_SYSTEM.alarm_high = (uint32_t)(_sys_alarm_time >> 32); + TIMER_SYSTEM.alarm_low = (uint32_t)(_sys_alarm_time & 0xffffffff); + + /* clear the bit in status and set the bit in interrupt enable */ + TIMER_SYSTEM_GROUP.int_clr_timers.val |= TIMER_SYSTEM_INT_MASK; + TIMER_SYSTEM_GROUP.int_ena.val |= TIMER_SYSTEM_INT_MASK; + + /* enable the timer alarm */ + TIMER_SYSTEM.config.level_int_en = 1; + TIMER_SYSTEM.config.alarm_en = 1; +} + +static void _sys_clear_alarm(void) +{ + /* disable alarms first */ + TIMER_SYSTEM.config.level_int_en = 0; + TIMER_SYSTEM.config.alarm_en = 0; + + /* clear the bit in interrupt enable and status register */ + TIMER_SYSTEM_GROUP.int_ena.val &= ~TIMER_SYSTEM_INT_MASK; + TIMER_SYSTEM_GROUP.int_clr_timers.val |= TIMER_SYSTEM_INT_MASK; + + /* reset the alarm configuration for interrupt handling */ + _sys_alarm.alarm_set = 0; + _sys_alarm.alarm_cb = NULL; + _sys_alarm.alarm_arg = NULL; +} + +static void _sys_save_counter(void) +{ + critical_enter(); + + /* save counters for synchronization after wakeup or reboot */ + _rtc_counter_saved = _rtc_get_counter(); + _sys_counter_saved = system_get_time_64() + _sys_counter_offset; + + critical_exit(); + + DEBUG("%s rtc_time_saved=%llu sys_time_saved=%llu\n", __func__, + _rtc_counter_saved, _sys_counter_saved); +} + +static void _sys_restore_counter(bool in_init) +{ + critical_enter(); + + /* synchronize RTC counter and the 64-bit microsecond system timer */ + uint64_t _rtc_time_diff = _rtc_get_counter() - _rtc_counter_saved; + _sys_counter_offset += _rtc_counter_to_us(_rtc_time_diff & RTT_HW_COUNTER_MAX); + _sys_counter_offset += (in_init) ? _sys_counter_saved : 0; + + critical_exit(); + + DEBUG("%s rtc_time_saved=%llu rtc_time_diff=%llu " + "sys_time_saved=%llu sys_time_offset=%llu\n", __func__, + _rtc_counter_saved, _rtc_time_diff, + _sys_counter_saved, _sys_counter_offset); +} + +static void IRAM _sys_isr(void *arg) +{ + if (!(TIMER_SYSTEM_GROUP.int_st_timers.val & TIMER_SYSTEM_INT_MASK)) { + /* return in case of another timer interrupt */ + return; + } + + /* disable alarms first */ + TIMER_SYSTEM.config.level_int_en = 0; + TIMER_SYSTEM.config.alarm_en = 0; + + /* clear the bit in interrupt enable and status register */ + TIMER_SYSTEM_GROUP.int_ena.val &= ~TIMER_SYSTEM_INT_MASK; + TIMER_SYSTEM_GROUP.int_clr_timers.val |= TIMER_SYSTEM_INT_MASK; + + /* save the lower 32 bit of the current counter value */ + uint32_t counter = _sys_get_counter(); + + DEBUG("%s %u\n", __func__, counter); + + if (_sys_alarm.alarm_cb) { + DEBUG("%s alarm %u\n", __func__, counter); + + rtt_cb_t alarm_cb = _sys_alarm.alarm_cb; + void *alarm_arg = _sys_alarm.alarm_arg; + + /* clear the alarm first */ + _sys_alarm.alarm_cb = NULL; + _sys_alarm.alarm_arg = NULL; + + /* call the alarm handler afterwards if callback was defined */ + alarm_cb(alarm_arg); + } +} + +const rtt_hw_driver_t _rtt_hw_sys_driver = { + .init = _sys_init, + .get_counter = _sys_get_counter, + .set_alarm = _sys_set_alarm, + .clear_alarm = _sys_clear_alarm, + .poweron = _sys_poweron, + .poweroff = _sys_poweroff, + .save_counter = _sys_save_counter, + .restore_counter = _sys_restore_counter, +};