1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/esp32/periph/rtc.c
2020-03-23 12:38:36 +01:00

368 lines
11 KiB
C

/*
* Copyright (C) 2018 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_rtc
* @{
*
* @file
* @brief Low-level RTC driver implementation
*
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @}
*/
/*
* The RTC low-level driver uses either the RTC hardware timer directly or the
* PLL-controlled 64-bit microsecond system timer to emulate an RTC timer. The
* RTC hardware timer can be clocked with either an external 32.768 kHz crystal
* or an internal adjustable 150 kHz RC oscillator. Which RTC timer is used,
* is controlled by modules as following:
*
* default
* Since the RTC hardware timer with the 150 kHz RC oscillator is less
* accurate than the emulated RTC timer, the emulated RTC timer is used by
* default. In this case, the RTC hardware timer is only used with the
* internal 150 kHz oscillator in deep sleep mode and during a reset.
*
* module `esp_rtc_timer`
* To use the RTC hardware timer with the 150 kHz Oscilator, the module
* `esp_rtc_timer` has to be enabled.
*
* module `esp_rtc_timer_32k`
* To use the RTC hardware timer with the external 32.768 kHz crystal, the
* module 'esp_rtc_timer_32k` has to be enabled. If the module
* `esp_rtc_timer_32k` is used, but no external 32.768 kHz crystal is
* available, the RTC low-level driver uses the RTC hardware driver,
* but with the internal 150 kHz RC oscillator.
*/
#define ENABLE_DEBUG (0)
#include "debug.h"
#include "esp_common.h"
#include <string.h>
#include "cpu.h"
#include "esp_attr.h"
#include "esp_sleep.h"
#include "log.h"
#include "irq_arch.h"
#include "periph/rtc.h"
#include "rom/ets_sys.h"
#include "soc/dport_reg.h"
#include "soc/rtc_cntl_struct.h"
#include "soc/timer_group_struct.h"
#include "syscalls.h"
#include "timex.h"
#include "xtensa/xtensa_api.h"
/* TODO move to TIMER_SYSTEM definition in periph_cpu.h */
#define TIMER_SYSTEM_GROUP TIMERG0
#define TIMER_SYSTEM_INT_MASK BIT(0)
#define TIMER_SYSTEM_INT_SRC ETS_TG0_T0_LEVEL_INTR_SOURCE
/* RTC timer interrupt source */
#define TIMER_RTC_INT_SRC ETS_RTC_CORE_INTR_SOURCE
#define RTC_CLK_CAL_FRACT 19 /* fractional bits of calibration value */
/* we can't include soc/rtc.h because of rtc_init declaration conflicts */
extern uint32_t rtc_clk_slow_freq_get_hz(void);
extern uint32_t esp_clk_slowclk_cal_get(void);
/* static variables */
static rtc_alarm_cb_t _rtc_alarm_cb = NULL;
static void* _rtc_alarm_arg = NULL;
static time_t _sys_alarm_time = 0;
#define RTC_BSS_ATTR __attribute__((section(".rtc.bss")))
/* save several time stamps in RTC memory */
static uint64_t RTC_BSS_ATTR _rtc_time_init_us; /* RTC time on init in us */
static uint64_t RTC_BSS_ATTR _rtc_time_init; /* RTC time on init in cycles */
static uint64_t RTC_BSS_ATTR _rtc_time_set_us; /* RTC time on set in us */
static uint64_t RTC_BSS_ATTR _rtc_time_set; /* RTC time on set in cycles */
static uint64_t RTC_BSS_ATTR _sys_time_set_us; /* system time on set in us */
static time_t RTC_BSS_ATTR _sys_time_set; /* system time on set in sec */
static uint64_t RTC_BSS_ATTR _sys_time_off_us; /* system time offset in us */
/* forward declarations */
static time_t _sys_get_time (void); /* system time in seconds */
static uint64_t _rtc_get_time_raw(void); /* RTC time in cycles */
static uint64_t _rtc_time_to_us(uint64_t raw); /* convert RTC cycles to us */
static void IRAM_ATTR _rtc_timer_handler(void* arg);
/* alias for compatibility with espressif/esp-idf */
int64_t esp_set_time_from_rtc(void) __attribute__((alias("rtc_init")));
void rtc_init(void)
{
uint64_t _rtc_time = _rtc_get_time_raw();
uint64_t _rtc_time_us = _rtc_time_to_us(_rtc_time);
if (_rtc_time_init == 0 && _rtc_time_init_us == 0) {
/* only set it new, if it was not set before */
_rtc_time_init = _rtc_time;
_rtc_time_init_us = _rtc_time_us;
DEBUG("%s saved rtc_init=%lld rtc_init_us=%lld\n",
__func__, _rtc_time_init, _rtc_time_init_us);
}
_sys_time_off_us = _rtc_time_us - _rtc_time_set_us - system_get_time_64();
_sys_time_set_us = 0;
}
void rtc_poweron(void)
{
/* RTC is always on, also in deep sleep mode */
return;
}
void rtc_poweroff(void)
{
/* RTC is always on, also in deep sleep mode */
return;
}
int rtc_set_time(struct tm *ttime)
{
_rtc_time_set = _rtc_get_time_raw();
_rtc_time_set_us = _rtc_time_to_us(_rtc_time_set);
_sys_time_set = rtc_mktime(ttime);
_sys_time_set_us = system_get_time_64();
_sys_time_off_us = 0;
DEBUG("%s sys_time=%ld rtc_time=%lld rtc_time_us=%lld\n",
__func__, _sys_time_set, _rtc_time_set, _rtc_time_set_us);
return 0;
}
int rtc_get_time(struct tm *ttime)
{
rtc_localtime(_sys_get_time(), ttime);
DEBUG("%s sys_time=%ld rtc_time=%lld rtc_time_us=%lld\n", __func__,
_sys_get_time(), _rtc_get_time_raw(), _rtc_time_to_us(_rtc_get_time_raw()));
return 0;
}
int rtc_get_alarm(struct tm *time)
{
rtc_localtime(_sys_alarm_time, time);
return 0;
}
int rtc_set_alarm(struct tm *time, rtc_alarm_cb_t cb, void *arg)
{
_rtc_alarm_cb = cb;
_rtc_alarm_arg = arg;
/* determine the offset of alarm time to the set time in seconds */
_sys_alarm_time = rtc_mktime(time);
time_t _sys_time_offset = _sys_alarm_time - _sys_time_set;
#if MODULE_ESP_RTC_TIMER
/* determine the offset of alarm time to current time in RTC time */
uint64_t _rtc_time_alarm;
_rtc_time_alarm = _rtc_time_set + _sys_time_offset * rtc_clk_slow_freq_get_hz();
DEBUG("%s sys=%ld sys_alarm=%ld rtc=%lld rtc_alarm=%lld\n", __func__,
_sys_get_time(), _sys_time_offset, _rtc_get_time_raw(), _rtc_time_alarm);
/* set the timer value */
RTCCNTL.slp_timer0 = _rtc_time_alarm & 0xffffffff;
RTCCNTL.slp_timer1.slp_val_hi = _rtc_time_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;
/* route all RTC interrupt sources to the same 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_timer_handler, NULL);
xt_ints_on(BIT(CPU_INUM_RTC));
#else
/* determine the offset of alarm time to the RTC set time in us */
uint64_t _sys_alarm_us;
_sys_alarm_us = _sys_time_set_us - _sys_time_off_us + _sys_time_offset * US_PER_SEC;
DEBUG("%s sys_time=%ld sys_time_offset=%ld "
"sys_time_us=%lld sys_time_alarm_us=%lld\n", __func__,
_sys_get_time(), _sys_time_offset, system_get_time_64(), _sys_alarm_us);
/* set the timer value */
TIMER_SYSTEM.alarm_high = (uint32_t)(_sys_alarm_us >> 32);
TIMER_SYSTEM.alarm_low = (uint32_t)(_sys_alarm_us & 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;
/* route all timer interrupt sources to the same 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, _rtc_timer_handler, NULL);
xt_ints_on(BIT(CPU_INUM_RTC));
/* enable the timer alarm */
TIMER_SYSTEM.config.level_int_en = 1;
TIMER_SYSTEM.config.alarm_en = 1;
#endif
return 0;
}
void rtc_clear_alarm(void)
{
_rtc_alarm_cb = NULL;
_rtc_alarm_arg = NULL;
#if MODULE_ESP_RTC_TIMER
/* disable RTC timer alarm and disable the RTC timer interrupt */
RTCCNTL.slp_timer1.main_timer_alarm_en = 0;
RTCCNTL.int_ena.rtc_main_timer = 0;
/* route all RTC interrupt sources to the same level type interrupt */
intr_matrix_set(PRO_CPU_NUM, DPORT_PRO_RTC_CORE_INTR_MAP_REG, CPU_INUM_RTC);
/* disable the the CPU interrupt */
#else
/* reset the bit in interrupt enable */
TIMER_SYSTEM_GROUP.int_ena.val |= TIMER_SYSTEM_INT_MASK;
/* disable the CPU interrupt */
xt_ints_on(BIT(CPU_INUM_RTC));
/* disable the timer alarm */
TIMER_SYSTEM.config.level_int_en = 0;
TIMER_SYSTEM.config.alarm_en = 0;
xt_ints_on(BIT(CPU_INUM_RTC));
#endif
}
static time_t _sys_get_time (void)
{
#if MODULE_ESP_RTC_TIMER
return _sys_time_set +
(_rtc_time_to_us(_rtc_get_time_raw() - _rtc_time_set) / US_PER_SEC);
#else
return _sys_time_set +
((_sys_time_off_us + system_get_time_64() - _sys_time_set_us) / US_PER_SEC);
#endif
}
static uint64_t _rtc_get_time_raw(void)
{
/* trigger timer register update */
RTCCNTL.time_update.update = 1;
/* wait until values in registers are valid */
while (!RTCCNTL.time_update.valid) { }
/* read the time and return */
uint64_t rtc_time;
rtc_time = RTCCNTL.time0;
rtc_time += ((uint64_t)RTCCNTL.time1.val) << 32;
return rtc_time;
}
static uint64_t _rtc_time_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 IRAM_ATTR _rtc_timer_handler(void* arg)
{
irq_isr_enter();
#if MODULE_ESP_RTC_TIMER
/* check for RTC timer interrupt */
if (RTCCNTL.int_st.rtc_main_timer) {
/* disable RTC timer alarm */
RTCCNTL.slp_timer1.main_timer_alarm_en = 0;
/* clear the interrupt */
RTCCNTL.int_clr.rtc_main_timer = 1;
/* call back registered function */
if (_rtc_alarm_cb) {
_rtc_alarm_cb(_rtc_alarm_arg);
_rtc_alarm_cb = 0;
}
}
/* clear all interrupts */
RTCCNTL.int_clr.val = 0x1ff;
#else
/* check for RTC timer interrupt */
if (TIMER_SYSTEM_GROUP.int_st_timers.val & TIMER_SYSTEM_INT_MASK) {
DEBUG("%s\n", __func__);
/* disable alarms */
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;
/* call back registered function */
if (_rtc_alarm_cb) {
_rtc_alarm_cb(_rtc_alarm_arg);
}
}
#endif
irq_isr_exit();
}
uint64_t rtc_pm_sleep_enter(unsigned mode)
{
(void)mode;
if (_rtc_alarm_cb) {
uint64_t sleep = (_sys_alarm_time - _sys_get_time()) * US_PER_SEC;
esp_sleep_enable_timer_wakeup(sleep);
return sleep;
}
return 0;
}
void rtc_pm_sleep_exit(uint32_t cause)
{
/* call the RTC time was the wakeup source and an RTC alarm was set */
if (cause == ESP_SLEEP_WAKEUP_TIMER && _rtc_alarm_cb) {
_rtc_alarm_cb(_rtc_alarm_arg);
_rtc_alarm_cb = 0;
}
}