aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src/clocks.rs
diff options
context:
space:
mode:
authorpennae <[email protected]>2023-08-05 00:02:39 +0200
committerpennae <[email protected]>2023-08-05 00:57:29 +0200
commit3c5f011245948d44dc88286ecd4e1554c8e4b28c (patch)
treeafda418d70aff3726a30f6a2d8ba6bdb6ff4bfd9 /embassy-rp/src/clocks.rs
parent30358c435e29eae0be1a8d5a991794ed4ba264ab (diff)
rp: add generic dormant-sleep functionality
this is "generic" in that it doesn't require the user to set up anything specific to go to dormant sleep, unlike the C sdk which requires clock sources to be configured explicitly and doesn't much care about PLLs. we will instead take a snapshot of the current clock configuration, switch to a known clock source (very slow rosc, in this case), go to sleep, and on wakeup undo everything we've done (ensuring stability of PLLs and such). tested locally, but adding tests to HIL seems infeasible. we'd need at least another pico or extensive modifications to teleprobe since dormant-sleep breaks SWD (except to rescue-dp), neither of which is feasible at this point. if we *did* want to add tests we should check for both rtc wakeups (with an external rtc clock source) and gpio wakeups.
Diffstat (limited to 'embassy-rp/src/clocks.rs')
-rw-r--r--embassy-rp/src/clocks.rs130
1 files changed, 130 insertions, 0 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index a33980230..7b25ecffb 100644
--- a/embassy-rp/src/clocks.rs
+++ b/embassy-rp/src/clocks.rs
@@ -1,3 +1,4 @@
1use core::arch::asm;
1use core::marker::PhantomData; 2use core::marker::PhantomData;
2use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; 3use core::sync::atomic::{AtomicU16, AtomicU32, Ordering};
3 4
@@ -6,6 +7,7 @@ use pac::clocks::vals::*;
6 7
7use crate::gpio::sealed::Pin; 8use crate::gpio::sealed::Pin;
8use crate::gpio::AnyPin; 9use crate::gpio::AnyPin;
10use crate::pac::common::{Reg, RW};
9use crate::{pac, reset, Peripheral}; 11use crate::{pac, reset, Peripheral};
10 12
11// NOTE: all gpin handling is commented out for future reference. 13// NOTE: all gpin handling is commented out for future reference.
@@ -873,3 +875,131 @@ impl rand_core::RngCore for RoscRng {
873 dest.fill_with(Self::next_u8) 875 dest.fill_with(Self::next_u8)
874 } 876 }
875} 877}
878/// Enter the `DORMANT` sleep state. This will stop *all* internal clocks
879/// and can only be exited through resets, dormant-wake GPIO interrupts,
880/// and RTC interrupts. If RTC is clocked from an internal clock source
881/// it will be stopped and not function as a wakeup source.
882#[cfg(target_arch = "arm")]
883pub fn dormant_sleep() {
884 struct Set<T: Copy, F: Fn()>(Reg<T, RW>, T, F);
885
886 impl<T: Copy, F: Fn()> Drop for Set<T, F> {
887 fn drop(&mut self) {
888 self.0.write_value(self.1);
889 self.2();
890 }
891 }
892
893 fn set_with_post_restore<T: Copy, After: Fn(), F: FnOnce(&mut T) -> After>(
894 reg: Reg<T, RW>,
895 f: F,
896 ) -> Set<T, impl Fn()> {
897 reg.modify(|w| {
898 let old = *w;
899 let after = f(w);
900 Set(reg, old, after)
901 })
902 }
903
904 fn set<T: Copy, F: FnOnce(&mut T)>(reg: Reg<T, RW>, f: F) -> Set<T, impl Fn()> {
905 set_with_post_restore(reg, |r| {
906 f(r);
907 || ()
908 })
909 }
910
911 // disable all clocks that are not vital in preparation for disabling clock sources.
912 // we'll keep gpout and rtc clocks untouched, gpout because we don't care about them
913 // and rtc because it's a possible wakeup source. if clk_rtc is not configured for
914 // gpin we'll never wake from rtc, but that's what the user asked for then.
915 let _stop_adc = set(pac::CLOCKS.clk_adc_ctrl(), |w| w.set_enable(false));
916 let _stop_usb = set(pac::CLOCKS.clk_usb_ctrl(), |w| w.set_enable(false));
917 let _stop_peri = set(pac::CLOCKS.clk_peri_ctrl(), |w| w.set_enable(false));
918 // set up rosc. we could ask the use to tell us which clock source to wake from like
919 // the C SDK does, but that seems rather unfriendly. we *may* disturb rtc by changing
920 // rosc configuration if it's currently the rtc clock source, so we'll configure rosc
921 // to the slowest frequency to minimize that impact.
922 let _configure_rosc = (
923 set(pac::ROSC.ctrl(), |w| {
924 w.set_enable(pac::rosc::vals::Enable::ENABLE);
925 w.set_freq_range(pac::rosc::vals::FreqRange::LOW);
926 }),
927 // div=32
928 set(pac::ROSC.div(), |w| w.set_div(pac::rosc::vals::Div(0xaa0))),
929 );
930 while !pac::ROSC.status().read().stable() {}
931 // switch over to rosc as the system clock source. this will change clock sources for
932 // watchdog and timer clocks, but timers won't be a concern and the watchdog won't
933 // speed up by enough to worry about (unless it's clocked from gpin, which we don't
934 // support anyway).
935 let _switch_clk_ref = set(pac::CLOCKS.clk_ref_ctrl(), |w| {
936 w.set_src(pac::clocks::vals::ClkRefCtrlSrc::ROSC_CLKSRC_PH);
937 });
938 let _switch_clk_sys = set(pac::CLOCKS.clk_sys_ctrl(), |w| {
939 w.set_src(pac::clocks::vals::ClkSysCtrlSrc::CLK_REF);
940 });
941 // oscillator dormancy does not power down plls, we have to do that ourselves. we'll
942 // restore them to their prior glory when woken though since the system may be clocked
943 // from either (and usb/adc will probably need the USB PLL anyway)
944 let _stop_pll_sys = set_with_post_restore(pac::PLL_SYS.pwr(), |w| {
945 let wake = !w.pd() && !w.vcopd();
946 w.set_pd(true);
947 w.set_vcopd(true);
948 move || while wake && !pac::PLL_SYS.cs().read().lock() {}
949 });
950 let _stop_pll_usb = set_with_post_restore(pac::PLL_USB.pwr(), |w| {
951 let wake = !w.pd() && !w.vcopd();
952 w.set_pd(true);
953 w.set_vcopd(true);
954 move || while wake && !pac::PLL_USB.cs().read().lock() {}
955 });
956 // dormancy only stops the oscillator we're telling to go dormant, the other remains
957 // running. nothing can use xosc at this point any more. not doing this costs an 200µA.
958 let _stop_xosc = set_with_post_restore(pac::XOSC.ctrl(), |w| {
959 let wake = w.enable() == pac::xosc::vals::Enable::ENABLE;
960 if wake {
961 w.set_enable(pac::xosc::vals::Enable::DISABLE);
962 }
963 move || while wake && !pac::XOSC.status().read().stable() {}
964 });
965 let _power_down_xip_cache = set(pac::XIP_CTRL.ctrl(), |w| w.set_power_down(true));
966
967 // only power down memory if we're running from XIP (or ROM? how?).
968 // powering down memory otherwise would require a lot of exacting checks that
969 // are better done by the user in a local copy of this function.
970 // powering down memories saves ~100µA, so it's well worth doing.
971 unsafe {
972 let is_in_flash = {
973 // we can't rely on the address of this function as rust sees it since linker
974 // magic or even boot2 may place it into ram.
975 let pc: usize;
976 asm!(
977 "mov {pc}, pc",
978 pc = out (reg) pc
979 );
980 pc < 0x20000000
981 };
982 if is_in_flash {
983 // we will be powering down memories, so we must be *absolutely*
984 // certain that we're running entirely from XIP and registers until
985 // memories are powered back up again. accessing memory that's powered
986 // down may corrupt memory contents (see section 2.11.4 of the manual).
987 // additionally a 20ns wait time is needed after powering up memories
988 // again. rosc is likely to run at only a few MHz at most, so the
989 // inter-instruction delay alone will be enough to satisfy this bound.
990 asm!(
991 "ldr {old_mem}, [{mempowerdown}]",
992 "str {power_down_mems}, [{mempowerdown}]",
993 "str {coma}, [{dormant}]",
994 "str {old_mem}, [{mempowerdown}]",
995 old_mem = out (reg) _,
996 mempowerdown = in (reg) pac::SYSCFG.mempowerdown().as_ptr(),
997 power_down_mems = in (reg) 0b11111111,
998 dormant = in (reg) pac::ROSC.dormant().as_ptr(),
999 coma = in (reg) 0x636f6d61,
1000 );
1001 } else {
1002 pac::ROSC.dormant().write_value(0x636f6d61);
1003 }
1004 }
1005}