blob: 35173cf89231738ec40be88efbea9a97adc90778 [file] [log] [blame]
/*
* Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/aon_timer.h"
#include "hardware/irq.h"
#include "hardware/sync.h"
static aon_timer_alarm_handler_t aon_timer_alarm_handler;
#if HAS_RP2040_RTC
#include "hardware/rtc.h"
#include "pico/util/datetime.h"
#elif HAS_POWMAN_TIMER
#include "hardware/powman.h"
static void powman_timer_irq_handler(void) {
uint irq_num = aon_timer_get_irq_num();
// we are one-shot, so remove ourselves
irq_set_enabled(irq_num, false);
irq_remove_handler(irq_num, powman_timer_irq_handler);
if (aon_timer_alarm_handler) aon_timer_alarm_handler();
}
#endif
void aon_timer_set_time(const struct timespec *ts) {
#if HAS_RP2040_RTC
datetime_t dt;
bool ok = time_to_datetime(ts->tv_sec, &dt);
assert(ok);
if (ok) rtc_set_datetime(&dt);
#elif HAS_POWMAN_TIMER
powman_timer_set_ms(timespec_to_ms(ts));
#else
panic_unsupported();
#endif
}
void aon_timer_get_time(struct timespec *ts) {
#if HAS_RP2040_RTC
datetime_t dt;
rtc_get_datetime(&dt);
time_t t;
bool ok = datetime_to_time(&dt, &t);
assert(ok);
ts->tv_nsec = 0;
if (ok) {
ts->tv_sec = t;
} else {
ts->tv_sec = -1;
}
#elif HAS_POWMAN_TIMER
ms_to_timespec(powman_timer_get_ms(), ts);
#else
panic_unsupported();
#endif
}
aon_timer_alarm_handler_t aon_timer_enable_alarm(const struct timespec *ts, aon_timer_alarm_handler_t handler, bool wakeup_from_low_power) {
uint32_t save = save_and_disable_interrupts();
aon_timer_alarm_handler_t old_handler = aon_timer_alarm_handler;
struct timespec ts_adjusted = *ts;
#if HAS_RP2040_RTC
((void)wakeup_from_low_power); // don't have a choice
datetime_t dt;
// adjust to after the target time
if (ts_adjusted.tv_nsec) ts_adjusted.tv_sec++;
bool ok = time_to_datetime(ts_adjusted.tv_sec, &dt);
assert(ok);
if (ok) {
rtc_set_alarm(&dt, handler);
}
#elif HAS_POWMAN_TIMER
uint irq_num = aon_timer_get_irq_num();
powman_timer_disable_alarm();
// adjust to after the target time
ts_adjusted.tv_nsec += 999999;
if (ts_adjusted.tv_nsec > 1000000000) {
ts_adjusted.tv_nsec -= 1000000000;
ts_adjusted.tv_sec++;
}
if (ts_adjusted.tv_nsec) ts_adjusted.tv_sec++;
if (wakeup_from_low_power) {
powman_enable_alarm_wakeup_at_ms(timespec_to_ms(ts));
} else {
powman_disable_alarm_wakeup();
powman_timer_enable_alarm_at_ms(timespec_to_ms(ts));
}
if (handler) {
irq_set_exclusive_handler(irq_num, powman_timer_irq_handler);
irq_set_enabled(irq_num, true);
}
#else
panic_unsupported();
#endif
aon_timer_alarm_handler = handler;
restore_interrupts_from_disabled(save);
return old_handler;
}
void aon_timer_disable_alarm(void) {
irq_set_enabled(aon_timer_get_irq_num(), false);
#if HAS_RP2040_RTC
rtc_disable_alarm();
#elif HAS_POWMAN_TIMER
powman_timer_disable_alarm();
#else
panic_unsupported();
#endif
}
void aon_timer_start_with_timeofday(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
struct timespec ts;
ts.tv_sec = tv.tv_sec;
ts.tv_nsec = tv.tv_usec * 1000;
aon_timer_start(&ts);
}
void aon_timer_start(const struct timespec *ts) {
#if HAS_RP2040_RTC
rtc_init();
aon_timer_set_time(ts);
#elif HAS_POWMAN_TIMER
// todo how best to allow different configurations; this should just be the default
powman_timer_set_1khz_tick_source_xosc();
powman_timer_set_ms(timespec_to_ms(ts));
powman_timer_start();
#else
panic_unsupported();
#endif
}
void aon_timer_stop(void) {
#if HAS_RP2040_RTC
hw_clear_bits(&rtc_hw->ctrl, RTC_CTRL_RTC_ENABLE_BITS);
#elif HAS_POWMAN_TIMER
powman_timer_stop();
#else
panic_unsupported();
#endif
}
void aon_timer_get_resolution(struct timespec *ts) {
#if HAS_RP2040_RTC
ts->tv_sec = 1;
ts->tv_nsec = 0;
#elif HAS_POWMAN_TIMER
ts->tv_sec = 0;
ts->tv_nsec = 1000000000 / 1000;
#else
panic_unsupported();
#endif
}
bool aon_timer_is_running(void) {
#if HAS_RP2040_RTC
return rtc_running();
#elif HAS_POWMAN_TIMER
return powman_timer_is_running();
#else
panic_unsupported();
#endif
}