atsamd_hal/rtc/modes.rs
1//! Provides low-level access to the [Real Time Clock (RTC)](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-E17D8859-D42B-4B0E-9B81-76168A0C38AC.html) peripheral on ATSAMD chips.
2//!
3//! The main abstraction is the [`RtcMode`] trait, which exposes
4//! static/associated functions to use the RTC in in a particular mode. All functions are marked as [`inline`](https://matklad.github.io/2021/07/09/inline-in-rust.html)
5//! so that this should be a zero cost abstraction.
6//!
7//! This module is intended to serve as the basis for the safe
8//! [`Rtc`](crate::rtc::Rtc) abstraction as well as RTIC and embassy time
9//! drivers.
10//!
11//! Abstraction benefits:
12//! - Handles all RTC register accesses.
13//! - Handles RTC [register synchronization](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html).
14//! - Handles ATSAMD chip variations.
15//!
16//! The idea is that various higher-level users of these abstractions will not
17//! have to handle these low-level aspects of using the RTC. However, this
18//! module does not present a safe interface. For example, many of the methods
19//! in [`RtcMode`] assume that the RTC has already been put into the correct
20//! mode (using [`RtcMode::set_mode`]), but without enforcing this in any way.
21
22// As explained in the [datasheets](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html),
23// reading a read-synced register may result in
24// an old value, which we try to avoid by ensuring that SYNCBUSY is clear
25// before reading. A write to a write-synced register will be discarded if
26// syncing is happening during the write. As such, we also ensure that SYNCBUSY
27// is clear before writing to a synced register. Throughout the crate, every
28// register access should be prefaced by a `SYNC` comment indicating the
29// required synchronization. The presence of this comment signals that this
30// access was checked in the datasheet and accounted for.
31
32use crate::pac;
33use atsamd_hal_macros::{hal_cfg, hal_macro_helper};
34use pac::Rtc;
35
36// Import prescaler divider enum
37#[hal_cfg(any("rtc-d11", "rtc-d21"))]
38use crate::pac::rtc::mode0::ctrl::Prescalerselect;
39#[hal_cfg("rtc-d5x")]
40use crate::pac::rtc::mode0::ctrla::Prescalerselect;
41
42/// Type-level enum for RTC interrupts.
43pub trait RtcInterrupt {
44 /// Enable this interrupt.
45 fn enable(rtc: &Rtc);
46 /// Disable this interrupt.
47 fn disable(rtc: &Rtc);
48 /// Returns whether the interrupt has been triggered.
49 fn check_flag(rtc: &Rtc) -> bool;
50 /// Clears the interrupt flag so the ISR will not be called again
51 /// immediately.
52 fn clear_flag(rtc: &Rtc);
53}
54
55/// Macro to easily declare an RTC interrupt.
56macro_rules! create_rtc_interrupt {
57 ($mode:ident, $name:ident, $bit:ident) => {
58 #[doc = concat!("Type-level variant for the ", stringify!($name), " interrupt in ", stringify!($mode))]
59 pub enum $name {}
60 impl RtcInterrupt for $name {
61 #[inline]
62 fn enable(rtc: &Rtc) {
63 // SYNC: None
64 rtc.$mode().intenset().write(|w| w.$bit().set_bit());
65 }
66
67 #[inline]
68 fn disable(rtc: &Rtc) {
69 // SYNC: None
70 rtc.$mode().intenclr().write(|w| w.$bit().set_bit());
71 }
72
73 #[inline]
74 fn check_flag(rtc: &Rtc) -> bool {
75 // SYNC: None
76 rtc.$mode().intflag().read().$bit().bit_is_set()
77 }
78
79 #[inline]
80 fn clear_flag(rtc: &Rtc) {
81 // SYNC: None
82 rtc.$mode().intflag().write(|w| w.$bit().set_bit());
83 }
84 }
85 };
86}
87
88/// An abstraction of an RTC in a particular mode that provides low-level
89/// access and handles all register syncing issues using only associated
90/// functions.
91pub trait RtcMode {
92 /// The type of the COUNT register.
93 type Count: Copy + PartialEq + Eq;
94
95 /// Sets this mode in the CTRL register.
96 ///
97 /// # Safety
98 ///
99 /// This should only be called when the RTC is disabled, and is typically
100 /// only called once before calling most other methods.
101 fn set_mode(rtc: &Rtc);
102
103 /// Sets a compare value.
104 ///
105 /// # Safety
106 ///
107 /// Should be called only after setting the RTC mode using
108 /// [`set_mode`](RtcMode::set_mode).
109 fn set_compare(rtc: &Rtc, number: usize, value: Self::Count);
110
111 /// Retrieves a compare from the register.
112 ///
113 /// # Safety
114 ///
115 /// Should be called only after setting the RTC mode using
116 /// [`set_mode`](RtcMode::set_mode).
117 #[cfg(feature = "rtic")]
118 fn get_compare(rtc: &Rtc, number: usize) -> Self::Count;
119
120 /// Returns the current synced COUNT value.
121 ///
122 /// # Safety
123 ///
124 /// Should be called only after setting the RTC mode using
125 /// [`set_mode`](RtcMode::set_mode).
126 fn count(rtc: &Rtc) -> Self::Count;
127
128 /// Sets the current synced COUNT value.
129 ///
130 /// # Safety
131 ///
132 /// Should be called only after setting the RTC mode using
133 /// [`set_mode`](RtcMode::set_mode).
134 fn set_count(rtc: &Rtc, count: Self::Count);
135
136 /// Returns whether register syncing is currently happening.
137 ///
138 /// # Safety
139 ///
140 /// Can be called any time.
141 #[inline]
142 #[hal_macro_helper]
143 fn sync_busy(rtc: &Rtc) -> bool {
144 // NOTE: This register and field are the same in all modes.
145 // SYNC: None
146 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
147 return rtc.mode0().status().read().syncbusy().bit_is_set();
148 // SYNC: None
149 #[hal_cfg("rtc-d5x")]
150 return rtc.mode0().syncbusy().read().bits() != 0;
151 }
152
153 /// Resets the RTC, leaving it disabled in MODE0.
154 ///
155 /// # Safety
156 ///
157 /// Can be called any time.
158 #[inline]
159 #[hal_macro_helper]
160 fn reset(rtc: &Rtc) {
161 // Reset RTC back to initial settings, which disables it and enters mode 0.
162 // NOTE: This register and field are the same in all modes.
163 // SYNC: Write
164 Self::sync(rtc);
165 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
166 rtc.mode0().ctrl().modify(|_, w| w.swrst().set_bit());
167 #[hal_cfg("rtc-d5x")]
168 rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit());
169
170 // Wait for the reset to complete
171 // SYNC: Write (we just read though)
172 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
173 while rtc.mode0().ctrl().read().swrst().bit_is_set() {}
174 #[hal_cfg("rtc-d5x")]
175 // NOTE: There is also a SWRST bit in the SYNCBUSY register but the bit CTRLA register
176 // is the one that clears when the reset is complete.
177 while rtc.mode0().ctrla().read().swrst().bit_is_set() {}
178 }
179
180 /// Sets the clock prescaler divider to lower the tick rate.
181 ///
182 /// # Safety
183 ///
184 /// Should be called only when the RTC is disabled.
185 #[inline]
186 #[hal_macro_helper]
187 fn set_prescaler(rtc: &Rtc, divider: Prescalerselect) {
188 // NOTE: This register and field are the same in all modes.
189 // SYNC: None
190 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
191 rtc.mode0()
192 .ctrl()
193 .modify(|_, w| w.prescaler().variant(divider));
194 #[hal_cfg("rtc-d5x")]
195 rtc.mode0()
196 .ctrla()
197 .modify(|_, w| w.prescaler().variant(divider));
198 }
199
200 /// Starts the RTC and does any required initialization for this mode.
201 ///
202 /// # Safety
203 ///
204 /// Should be called only after setting the RTC mode using
205 /// [`set_mode`](RtcMode::set_mode).
206 #[inline]
207 #[hal_macro_helper]
208 fn start_and_initialize(rtc: &Rtc) {
209 Self::enable(rtc);
210
211 // Enable counter sync on SAMx5x, the counter cannot be read otherwise.
212 #[hal_cfg("rtc-d5x")]
213 {
214 // Enable counter synchronization
215 // NOTE: This register and field are the same in all modes.
216 // SYNC: Write
217 Self::sync(rtc);
218 rtc.mode0().ctrla().modify(|_, w| {
219 // Notifications may not work with prescaler disabled
220 w.prescaler().div1();
221 w.countsync().set_bit();
222 w
223 });
224
225 // Errata: The first read of the count is incorrect so we need to read it
226 // then wait for it to change.
227 Self::_wait_for_count_change(rtc);
228 }
229 }
230
231 /// Enables an RTC interrupt.
232 ///
233 /// # Safety
234 ///
235 /// Should be called only after setting the RTC mode using
236 /// [`set_mode`](RtcMode::set_mode).
237 #[inline]
238 fn enable_interrupt<I: RtcInterrupt>(rtc: &Rtc) {
239 I::enable(rtc);
240 }
241
242 /// Disables an RTC interrupt.
243 ///
244 /// # Safety
245 ///
246 /// Should be called only after setting the RTC mode using
247 /// [`set_mode`](RtcMode::set_mode).
248 #[inline]
249 fn disable_interrupt<I: RtcInterrupt>(rtc: &Rtc) {
250 I::disable(rtc);
251 }
252
253 /// Returns whether an RTC interrupt has been triggered.
254 ///
255 /// # Safety
256 ///
257 /// Should be called only after setting the RTC mode using
258 /// [`set_mode`](RtcMode::set_mode).
259 #[inline]
260 fn check_interrupt_flag<I: RtcInterrupt>(rtc: &Rtc) -> bool {
261 I::check_flag(rtc)
262 }
263
264 /// Clears an RTC interrupt flag so the ISR will not be called again
265 /// immediately.
266 ///
267 /// # Safety
268 ///
269 /// Should be called only after setting the RTC mode using
270 /// [`set_mode`](RtcMode::set_mode).
271 #[inline]
272 fn clear_interrupt_flag<I: RtcInterrupt>(rtc: &Rtc) {
273 I::clear_flag(rtc);
274 }
275
276 /// Waits for any register syncing to be completed, or returns immediately
277 /// if not currently syncing.
278 ///
279 /// # Safety
280 ///
281 /// Can be called any time.
282 #[inline]
283 fn sync(rtc: &Rtc) {
284 while Self::sync_busy(rtc) {}
285 }
286
287 /// Disables the RTC.
288 ///
289 /// # Safety
290 ///
291 /// Can be called any time.
292 #[inline]
293 #[hal_macro_helper]
294 fn disable(rtc: &Rtc) {
295 // NOTE: This register and field are the same in all modes.
296 // SYNC: Write
297 Self::sync(rtc);
298 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
299 rtc.mode0().ctrl().modify(|_, w| w.enable().clear_bit());
300 #[hal_cfg("rtc-d5x")]
301 rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit());
302 }
303
304 /// Enables the RTC.
305 ///
306 /// # Safety
307 ///
308 /// Can be called any time.
309 #[inline]
310 #[hal_macro_helper]
311 fn enable(rtc: &Rtc) {
312 // NOTE: This register and field are the same in all modes.
313 // SYNC: Write
314 Self::sync(rtc);
315
316 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
317 rtc.mode0().ctrl().modify(|_, w| w.enable().set_bit());
318 #[hal_cfg("rtc-d5x")]
319 rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit());
320 }
321
322 /// Waits until the COUNT register changes.
323 ///
324 /// Note that this may not necessarily be the next tick numerically due sync
325 /// delay.
326 ///
327 /// # Safety
328 ///
329 /// Should be called only after setting the RTC mode using
330 /// [`set_mode`](RtcMode::set_mode). This will halt forever if called when
331 /// the RTC is disabled.
332 #[inline]
333 fn _wait_for_count_change(rtc: &Rtc) -> Self::Count {
334 let mut last_count = Self::count(rtc);
335
336 loop {
337 let count = Self::count(rtc);
338
339 if count != last_count {
340 break count;
341 }
342
343 last_count = count;
344 }
345 }
346}
347
348/// Interface for using the RTC in MODE0 (32-bit COUNT)
349pub mod mode0 {
350 use super::*;
351
352 create_rtc_interrupt!(mode0, Compare0, cmp0);
353 #[cfg(feature = "rtic")]
354 #[hal_cfg("rtc-d5x")]
355 create_rtc_interrupt!(mode0, Compare1, cmp1);
356 #[cfg(feature = "rtic")]
357 #[hal_cfg("rtc-d5x")]
358 create_rtc_interrupt!(mode0, Overflow, ovf);
359
360 /// The RTC operating in MODE0 (32-bit COUNT)
361 pub struct RtcMode0;
362
363 impl RtcMode0 {
364 /// Sets or resets the match clear bit, which clears the counter when a
365 /// compare value matches.
366 ///
367 /// # Safety
368 ///
369 /// This should only be called when the RTC is disabled.
370 #[inline]
371 #[hal_macro_helper]
372 pub fn set_match_clear(rtc: &Rtc, enable: bool) {
373 // SYNC: None
374 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
375 rtc.mode0().ctrl().modify(|_, w| w.matchclr().bit(enable));
376 #[hal_cfg("rtc-d5x")]
377 rtc.mode0().ctrla().modify(|_, w| w.matchclr().bit(enable));
378 }
379 }
380
381 impl RtcMode for RtcMode0 {
382 type Count = u32;
383
384 #[inline]
385 #[hal_macro_helper]
386 fn set_mode(rtc: &Rtc) {
387 // NOTE: This register and field are the same in all modes.
388 // SYNC: None (for these bits)
389 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
390 rtc.mode0().ctrl().modify(|_, w| w.mode().count32());
391 #[hal_cfg("rtc-d5x")]
392 rtc.mode0().ctrla().modify(|_, w| w.mode().count32());
393 }
394
395 #[inline]
396 fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) {
397 // SYNC: Write
398 Self::sync(rtc);
399 unsafe {
400 rtc.mode0().comp(number).write(|w| w.comp().bits(value));
401 }
402 }
403
404 #[inline]
405 #[cfg(feature = "rtic")]
406 fn get_compare(rtc: &Rtc, number: usize) -> Self::Count {
407 // SYNC: Write (we just read though)
408 rtc.mode0().comp(number).read().bits()
409 }
410
411 #[inline]
412 #[hal_macro_helper]
413 fn count(rtc: &Rtc) -> Self::Count {
414 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
415 {
416 // Request syncing of the COUNT register.
417 // SYNC: None
418 rtc.mode0().readreq().modify(|_, w| w.rreq().set_bit());
419 }
420
421 // SYNC: Read/Write
422 Self::sync(rtc);
423 rtc.mode0().count().read().bits()
424 }
425
426 #[inline]
427 fn set_count(rtc: &Rtc, count: Self::Count) {
428 // SYNC: Read/Write
429 Self::sync(rtc);
430 unsafe { rtc.mode0().count().write(|w| w.count().bits(count)) };
431 }
432 }
433}
434
435/// Interface for using the RTC in MODE1 (16-bit COUNT)
436#[hal_cfg(any("rtc-d11", "rtc-d21"))]
437#[cfg(feature = "rtic")]
438pub mod mode1 {
439 use super::*;
440
441 create_rtc_interrupt!(mode1, Compare0, cmp0);
442 #[cfg(feature = "rtic")]
443 create_rtc_interrupt!(mode1, Compare1, cmp1);
444 #[cfg(feature = "rtic")]
445 create_rtc_interrupt!(mode1, Overflow, ovf);
446
447 /// The RTC operating in MODE1 (16-bit COUNT)
448 pub struct RtcMode1;
449
450 impl RtcMode for RtcMode1 {
451 type Count = u16;
452
453 #[inline]
454 #[hal_macro_helper]
455 fn set_mode(rtc: &Rtc) {
456 // SYNC: Write
457 Self::sync(rtc);
458 // NOTE: This register and field are the same in all modes.
459 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
460 rtc.mode0().ctrl().modify(|_, w| w.mode().count16());
461 #[hal_cfg("rtc-d5x")]
462 rtc.mode0().ctrla().modify(|_, w| w.mode().count16());
463
464 // Set the mode 1 period
465 // SYNC: Write
466 Self::sync(rtc);
467 unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) };
468 }
469
470 #[inline]
471 fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) {
472 // SYNC: Write
473 Self::sync(rtc);
474 unsafe { rtc.mode1().comp(number).write(|w| w.comp().bits(value)) };
475 }
476
477 #[inline]
478 #[cfg(feature = "rtic")]
479 fn get_compare(rtc: &Rtc, number: usize) -> Self::Count {
480 // SYNC: Write (we just read though)
481 rtc.mode1().comp(number).read().bits()
482 }
483
484 #[inline]
485 #[hal_macro_helper]
486 fn count(rtc: &Rtc) -> Self::Count {
487 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
488 {
489 // Request syncing of the COUNT register.
490 // SYNC: None
491 rtc.mode1().readreq().modify(|_, w| w.rreq().set_bit());
492 }
493
494 // SYNC: Read/Write
495 Self::sync(rtc);
496 rtc.mode1().count().read().bits()
497 }
498
499 #[inline]
500 fn set_count(rtc: &Rtc, count: Self::Count) {
501 // SYNC: Read/Write
502 Self::sync(rtc);
503 unsafe { rtc.mode1().count().write(|w| w.count().bits(count)) };
504 }
505 }
506}
507
508/// Interface for using the RTC in MODE2 (Clock/Calendar)
509pub mod mode2 {
510 use super::*;
511
512 // These actually aren't needed for anything right now
513 //create_rtc_interrupt!(mode2, Alarm0, alarm0);
514 //create_rtc_interrupt!(mode2, Alarm1, alarm1);
515
516 /// Datetime represents an RTC clock/calendar value.
517 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
518 pub struct Datetime {
519 pub seconds: u8,
520 pub minutes: u8,
521 pub hours: u8,
522 pub day: u8,
523 pub month: u8,
524 pub year: u8,
525 }
526
527 /// Macro to read from to the clock or alarm registers.
528 macro_rules! from_reg_datetime {
529 ($regr:ident) => {
530 impl From<pac::rtc::mode2::$regr::R> for Datetime {
531 fn from(clock: pac::rtc::mode2::$regr::R) -> Datetime {
532 Datetime {
533 seconds: clock.second().bits(),
534 minutes: clock.minute().bits(),
535 hours: clock.hour().bits(),
536 day: clock.day().bits(),
537 month: clock.month().bits(),
538 year: clock.year().bits(),
539 }
540 }
541 }
542 };
543 }
544
545 from_reg_datetime!(clock);
546 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
547 from_reg_datetime!(alarm);
548 #[hal_cfg("rtc-d5x")]
549 from_reg_datetime!(alarm0);
550 #[hal_cfg("rtc-d5x")]
551 from_reg_datetime!(alarm1);
552
553 /// Macro to write to the clock or alarm registers.
554 macro_rules! write_datetime {
555 ($regw:ident, $time:ident) => {
556 unsafe {
557 $regw
558 .second()
559 .bits($time.seconds)
560 .minute()
561 .bits($time.minutes)
562 .hour()
563 .bits($time.hours)
564 .day()
565 .bits($time.day)
566 .month()
567 .bits($time.month)
568 .year()
569 .bits($time.year)
570 }
571 };
572 }
573
574 /// The RTC operating in MODE2 (Clock/Calendar)
575 pub struct RtcMode2;
576
577 impl RtcMode for RtcMode2 {
578 type Count = Datetime;
579
580 #[inline]
581 #[hal_macro_helper]
582 fn set_mode(rtc: &Rtc) {
583 // SYNC: Write
584 Self::sync(rtc);
585 // NOTE: This register and field are the same in all modes.
586 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
587 rtc.mode0().ctrl().modify(|_, w| w.mode().clock());
588 #[hal_cfg("rtc-d5x")]
589 rtc.mode0().ctrla().modify(|_, w| w.mode().clock());
590 }
591
592 #[inline]
593 #[hal_macro_helper]
594 fn set_compare(rtc: &Rtc, _number: usize, value: Self::Count) {
595 // SYNC: Write
596 Self::sync(rtc);
597
598 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
599 rtc.mode2().alarm(0).write(|w| write_datetime!(w, value));
600 #[hal_cfg("rtc-d5x")]
601 if _number == 0 {
602 rtc.mode2().alarm0().write(|w| write_datetime!(w, value));
603 } else {
604 rtc.mode2().alarm1().write(|w| write_datetime!(w, value));
605 }
606 }
607
608 #[inline]
609 #[hal_macro_helper]
610 #[cfg(feature = "rtic")]
611 fn get_compare(rtc: &Rtc, _number: usize) -> Self::Count {
612 // SYNC: Write (we just read though)
613 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
614 return rtc.mode2().alarm(0).read().into();
615 #[hal_cfg("rtc-d5x")]
616 if _number == 0 {
617 rtc.mode2().alarm0().read().into()
618 } else {
619 rtc.mode2().alarm1().read().into()
620 }
621 }
622
623 #[inline]
624 #[hal_macro_helper]
625 fn count(rtc: &Rtc) -> Self::Count {
626 #[hal_cfg(any("rtc-d11", "rtc-d21"))]
627 {
628 // Request syncing of the COUNT register.
629 // SYNC: None
630 rtc.mode2().readreq().modify(|_, w| w.rreq().set_bit());
631 }
632
633 // SYNC: Read/Write
634 Self::sync(rtc);
635 rtc.mode2().clock().read().into()
636 }
637
638 #[inline]
639 fn set_count(rtc: &Rtc, count: Self::Count) {
640 // SYNC: Read/Write
641 Self::sync(rtc);
642 rtc.mode2().clock().write(|w| write_datetime!(w, count));
643 }
644 }
645}