mod conversions;
mod filter;
use core::future::poll_fn;
use core::sync::atomic::{AtomicBool, Ordering, compiler_fence};
use core::task::Poll;
use embassy_hal_internal::{Peri, PeripheralType};
use embassy_sync::waitqueue::AtomicWaker;
pub use self::filter::DateTimeFilter;
use crate::clocks::clk_rtc_freq;
pub use crate::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
use crate::interrupt::typelevel::Binding;
use crate::interrupt::{self, InterruptExt};
static WAKER: AtomicWaker = AtomicWaker::new();
static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false);
pub struct Rtc<'d, T: Instance> {
inner: Peri<'d, T>,
}
impl<'d, T: Instance> Rtc<'d, T> {
pub fn new(inner: Peri<'d, T>, _irq: impl Binding<interrupt::typelevel::RTC_IRQ, InterruptHandler>) -> Self {
inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1));
interrupt::RTC_IRQ.unpend();
unsafe { interrupt::RTC_IRQ.enable() };
Self { inner }
}
pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) {
self.inner.regs().ctrl().modify(|w| {
w.set_force_notleapyear(!leap_year_check_enabled);
});
}
pub fn restore(&mut self, ymd: rp_pac::rtc::regs::Rtc1, hms: rp_pac::rtc::regs::Rtc0) {
self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
while self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
self.inner.regs().setup_0().write(|w| {
*w = rp_pac::rtc::regs::Setup0(ymd.0);
});
self.inner.regs().setup_1().write(|w| {
*w = rp_pac::rtc::regs::Setup1(hms.0);
});
self.inner.regs().ctrl().write(|w| w.set_load(true));
self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
while !self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
}
pub fn save(&mut self) -> (rp_pac::rtc::regs::Rtc1, rp_pac::rtc::regs::Rtc0) {
let rtc_0: rp_pac::rtc::regs::Rtc0 = self.inner.regs().rtc_0().read();
let rtc_1 = self.inner.regs().rtc_1().read();
(rtc_1, rtc_0)
}
pub fn is_running(&self) -> bool {
self.inner.regs().ctrl().read().rtc_active()
}
pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> {
conversions::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?;
self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
while self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
self.inner.regs().setup_0().write(|w| {
conversions::write_setup_0(&t, w);
});
self.inner.regs().setup_1().write(|w| {
conversions::write_setup_1(&t, w);
});
self.inner.regs().ctrl().write(|w| w.set_load(true));
self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
while !self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
Ok(())
}
pub fn now(&self) -> Result<DateTime, RtcError> {
if !self.is_running() {
return Err(RtcError::NotRunning);
}
let rtc_0 = self.inner.regs().rtc_0().read();
let rtc_1 = self.inner.regs().rtc_1().read();
conversions::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime)
}
pub fn disable_alarm(&mut self) {
self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false));
while self.inner.regs().irq_setup_0().read().match_active() {
core::hint::spin_loop();
}
}
pub fn schedule_alarm(&mut self, filter: DateTimeFilter) {
self.disable_alarm();
self.inner.regs().irq_setup_0().write(|w| {
filter.write_setup_0(w);
});
self.inner.regs().irq_setup_1().write(|w| {
filter.write_setup_1(w);
});
self.inner.regs().inte().modify(|w| w.set_rtc(true));
self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true));
while !self.inner.regs().irq_setup_0().read().match_active() {
core::hint::spin_loop();
}
}
pub fn clear_interrupt(&mut self) {
self.disable_alarm();
}
pub fn alarm_scheduled(&self) -> Option<DateTimeFilter> {
if !self.inner.regs().irq_setup_0().read().match_active() {
return None;
}
let irq_0 = self.inner.regs().irq_setup_0().read();
let irq_1 = self.inner.regs().irq_setup_1().read();
let mut filter = DateTimeFilter::default();
if irq_0.year_ena() {
filter.year = Some(irq_0.year());
}
if irq_0.month_ena() {
filter.month = Some(irq_0.month());
}
if irq_0.day_ena() {
filter.day = Some(irq_0.day());
}
if irq_1.dotw_ena() {
let Ok(day_of_week) = conversions::day_of_week_from_u8(irq_1.dotw()) else {
return None; };
filter.day_of_week = Some(day_of_week);
}
if irq_1.hour_ena() {
filter.hour = Some(irq_1.hour());
}
if irq_1.min_ena() {
filter.minute = Some(irq_1.min());
}
if irq_1.sec_ena() {
filter.second = Some(irq_1.sec());
}
Some(filter)
}
pub async fn wait_for_alarm(&mut self) {
poll_fn(|cx| {
WAKER.register(cx.waker());
if critical_section::with(|_| {
let occurred = ALARM_OCCURRED.load(Ordering::SeqCst);
if occurred {
ALARM_OCCURRED.store(false, Ordering::SeqCst);
}
occurred
}) {
self.clear_interrupt();
compiler_fence(Ordering::SeqCst);
return Poll::Ready(());
} else {
return Poll::Pending;
}
})
.await;
}
}
pub struct InterruptHandler {
_empty: (),
}
impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::RTC_IRQ> for InterruptHandler {
unsafe fn on_interrupt() {
let rtc = crate::pac::RTC;
rtc.irq_setup_0().modify(|w| w.set_match_ena(false));
ALARM_OCCURRED.store(true, Ordering::SeqCst);
WAKER.wake();
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RtcError {
InvalidDateTime(DateTimeError),
NotRunning,
}
trait SealedInstance {
fn regs(&self) -> crate::pac::rtc::Rtc;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {}
impl SealedInstance for crate::peripherals::RTC {
fn regs(&self) -> crate::pac::rtc::Rtc {
crate::pac::RTC
}
}
impl Instance for crate::peripherals::RTC {}