diff options
| -rw-r--r-- | embassy-rp/src/clocks.rs | 130 |
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 @@ | |||
| 1 | use core::arch::asm; | ||
| 1 | use core::marker::PhantomData; | 2 | use core::marker::PhantomData; |
| 2 | use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; | 3 | use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; |
| 3 | 4 | ||
| @@ -6,6 +7,7 @@ use pac::clocks::vals::*; | |||
| 6 | 7 | ||
| 7 | use crate::gpio::sealed::Pin; | 8 | use crate::gpio::sealed::Pin; |
| 8 | use crate::gpio::AnyPin; | 9 | use crate::gpio::AnyPin; |
| 10 | use crate::pac::common::{Reg, RW}; | ||
| 9 | use crate::{pac, reset, Peripheral}; | 11 | use 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")] | ||
| 883 | pub 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 | } | ||
