aboutsummaryrefslogtreecommitdiff
path: root/embassy-nrf/src/rtc.rs
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2020-09-24 19:59:20 +0200
committerDario Nieuwenhuis <[email protected]>2020-09-24 19:59:20 +0200
commit3b39ab07e5b52acfe2ba7aa7f92cfa3c859d3dd7 (patch)
tree1aef3c96efca9bce274c0b1d373d0caf4e79846d /embassy-nrf/src/rtc.rs
parent4e4241bf909b786a5950ac6d72a33a75d8ec982a (diff)
Add 64-bit rtc driver with alarm support.
Diffstat (limited to 'embassy-nrf/src/rtc.rs')
-rw-r--r--embassy-nrf/src/rtc.rs204
1 files changed, 204 insertions, 0 deletions
diff --git a/embassy-nrf/src/rtc.rs b/embassy-nrf/src/rtc.rs
new file mode 100644
index 000000000..a5810eb5e
--- /dev/null
+++ b/embassy-nrf/src/rtc.rs
@@ -0,0 +1,204 @@
1use core::cell::Cell;
2use core::ops::Deref;
3use core::sync::atomic::{AtomicU32, Ordering};
4
5use defmt::trace;
6
7use crate::interrupt;
8use crate::interrupt::Mutex;
9use crate::pac::{rtc0, Interrupt, RTC0, RTC1};
10
11#[cfg(any(feature = "52832", feature = "52833", feature = "52840"))]
12use crate::pac::RTC2;
13
14fn calc_now(period: u32, counter: u32) -> u64 {
15 let shift = ((period & 1) << 23) + 0x400000;
16 let counter_shifted = (counter + shift) & 0xFFFFFF;
17 ((period as u64) << 23) + counter_shifted as u64 - 0x400000
18}
19
20mod test {
21 use super::*;
22
23 #[test]
24 fn test_calc_now() {
25 assert_eq!(calc_now(0, 0x000000), 0x0_000000);
26 assert_eq!(calc_now(0, 0x000001), 0x0_000001);
27 assert_eq!(calc_now(0, 0x7FFFFF), 0x0_7FFFFF);
28 assert_eq!(calc_now(1, 0x7FFFFF), 0x0_7FFFFF);
29 assert_eq!(calc_now(0, 0x800000), 0x0_800000);
30 assert_eq!(calc_now(1, 0x800000), 0x0_800000);
31 assert_eq!(calc_now(1, 0x800001), 0x0_800001);
32 assert_eq!(calc_now(1, 0xFFFFFF), 0x0_FFFFFF);
33 assert_eq!(calc_now(2, 0xFFFFFF), 0x0_FFFFFF);
34 assert_eq!(calc_now(1, 0x000000), 0x1_000000);
35 assert_eq!(calc_now(2, 0x000000), 0x1_000000);
36 }
37}
38
39pub struct RTC<T> {
40 rtc: T,
41
42 /// Number of 2^23 periods elapsed since boot.
43 ///
44 /// This is incremented by 1
45 /// - on overflow (counter value 0)
46 /// - on "midway" between overflows (at counter value 0x800000)
47 ///
48 /// Therefore: When even, counter is in 0..0x7fffff. When odd, counter is in 0x800000..0xFFFFFF
49 /// This allows for now() to return the correct value even if it races an overflow.
50 ///
51 /// It overflows on 2^32 * 2^23 / 32768 seconds of uptime, which is 34865 years.
52 period: AtomicU32,
53
54 /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
55 alarm: Mutex<Cell<u64>>,
56}
57
58impl<T: Instance> RTC<T> {
59 pub fn new(rtc: T) -> Self {
60 Self {
61 rtc,
62 period: AtomicU32::new(0),
63 alarm: Mutex::new(Cell::new(u64::MAX)),
64 }
65 }
66
67 pub fn start(&'static self) {
68 self.rtc.cc[0].write(|w| unsafe { w.bits(0x800000) });
69
70 self.rtc.intenset.write(|w| {
71 let w = w.ovrflw().set();
72 let w = w.compare0().set();
73 w
74 });
75
76 self.rtc.tasks_clear.write(|w| w.tasks_clear().set_bit());
77 self.rtc.tasks_start.write(|w| w.tasks_start().set_bit());
78
79 // Wait for clear
80 while self.rtc.counter.read().bits() != 0 {}
81
82 T::set_rtc_instance(self);
83 interrupt::enable(T::INTERRUPT);
84 }
85
86 fn on_interrupt(&self) {
87 if self.rtc.events_ovrflw.read().bits() == 1 {
88 self.rtc.events_ovrflw.write(|w| w);
89 trace!("rtc overflow");
90 self.next_period();
91 }
92
93 if self.rtc.events_compare[0].read().bits() == 1 {
94 self.rtc.events_compare[0].write(|w| w);
95 trace!("rtc compare0");
96 self.next_period();
97 }
98
99 if self.rtc.events_compare[1].read().bits() == 1 {
100 self.rtc.events_compare[1].write(|w| w);
101 trace!("rtc compare1");
102 self.trigger_alarm();
103 }
104 }
105
106 fn next_period(&self) {
107 interrupt::free(|cs| {
108 let period = self.period.fetch_add(1, Ordering::Relaxed) + 1;
109 let t = (period as u64) << 23;
110
111 let at = self.alarm.borrow(cs).get();
112
113 let diff = at - t;
114 if diff < 0xc00000 {
115 self.rtc.cc[1].write(|w| unsafe { w.bits(at as u32 & 0xFFFFFF) });
116 self.rtc.intenset.write(|w| w.compare1().set());
117 }
118 })
119 }
120
121 pub fn now(&self) -> u64 {
122 let counter = self.rtc.counter.read().bits();
123 let period = self.period.load(Ordering::Relaxed);
124 calc_now(period, counter)
125 }
126
127 fn trigger_alarm(&self) {
128 self.rtc.intenclr.write(|w| w.compare1().clear());
129 interrupt::free(|cs| self.alarm.borrow(cs).set(u64::MAX));
130
131 // TODO
132 trace!("ALARM! {:u32}", self.now() as u32);
133 }
134
135 pub fn set_alarm(&self, at: u64) {
136 interrupt::free(|cs| {
137 let t = self.now();
138 if at <= t {
139 self.trigger_alarm();
140 return;
141 }
142
143 self.alarm.borrow(cs).set(at);
144
145 let diff = at - t;
146 if diff < 0xc00000 {
147 self.rtc.cc[1].write(|w| unsafe { w.bits(at as u32 & 0xFFFFFF) });
148 self.rtc.intenset.write(|w| w.compare1().set());
149
150 // We may have been preempted for arbitrary time between checking if `at` is in the past
151 // and setting the cc. In that case, we don't know if the cc has triggered.
152 // So, we check again just in case.
153
154 let t = self.now();
155 if at <= t {
156 self.trigger_alarm();
157 return;
158 }
159 } else {
160 self.rtc.intenclr.write(|w| w.compare1().clear());
161 }
162 })
163 }
164
165 pub fn clear_alarm(&self) {
166 self.set_alarm(u64::MAX);
167 }
168}
169
170/// Implemented by all RTC instances.
171pub trait Instance: Deref<Target = rtc0::RegisterBlock> + Sized {
172 /// The interrupt associated with this RTC instance.
173 const INTERRUPT: Interrupt;
174
175 fn set_rtc_instance(rtc: &'static RTC<Self>);
176 fn get_rtc_instance() -> &'static RTC<Self>;
177}
178
179macro_rules! impl_instance {
180 ($name:ident, $static_name:ident) => {
181 static mut $static_name: Option<&'static RTC<$name>> = None;
182
183 impl Instance for $name {
184 const INTERRUPT: Interrupt = Interrupt::$name;
185 fn set_rtc_instance(rtc: &'static RTC<Self>) {
186 unsafe { $static_name = Some(rtc) }
187 }
188 fn get_rtc_instance() -> &'static RTC<Self> {
189 unsafe { $static_name.unwrap() }
190 }
191 }
192
193 #[interrupt]
194 fn $name() {
195 $name::get_rtc_instance().on_interrupt();
196 }
197 };
198}
199
200impl_instance!(RTC0, RTC0_INSTANCE);
201impl_instance!(RTC1, RTC1_INSTANCE);
202
203#[cfg(any(feature = "52832", feature = "52833", feature = "52840"))]
204impl_instance!(RTC2, RTC2_INSTANCE);