| 1 | use core::sync::atomic::{compiler_fence, Ordering}; |
| 2 | |
| 3 | use crate::pac::common::{Reg, RW}; |
| 4 | pub use crate::pac::rcc::vals::Rtcsel as RtcClockSource; |
| 5 | use crate::time::Hertz; |
| 6 | |
| 7 | #[cfg (any(stm32f0, stm32f1, stm32f3))] |
| 8 | pub const LSI_FREQ: Hertz = Hertz(40_000); |
| 9 | #[cfg (not(any(stm32f0, stm32f1, stm32f3)))] |
| 10 | pub const LSI_FREQ: Hertz = Hertz(32_000); |
| 11 | |
| 12 | #[allow (dead_code)] |
| 13 | #[derive (Clone, Copy)] |
| 14 | pub enum LseMode { |
| 15 | Oscillator(LseDrive), |
| 16 | Bypass, |
| 17 | } |
| 18 | |
| 19 | #[derive (Clone, Copy)] |
| 20 | pub struct LseConfig { |
| 21 | pub frequency: Hertz, |
| 22 | pub mode: LseMode, |
| 23 | /// If peripherals other than RTC/TAMP or RCC functions need the lse this bit must be set |
| 24 | #[cfg (any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] |
| 25 | pub peripherals_clocked: bool, |
| 26 | } |
| 27 | |
| 28 | #[allow (dead_code)] |
| 29 | #[derive (Default, Clone, Copy)] |
| 30 | pub enum LseDrive { |
| 31 | #[cfg (not(stm32h5))] // ES0565: LSE Low drive mode is not functional |
| 32 | Low = 0, |
| 33 | MediumLow = 0x01, |
| 34 | #[default] |
| 35 | MediumHigh = 0x02, |
| 36 | High = 0x03, |
| 37 | } |
| 38 | |
| 39 | // All families but these have the LSEDRV register |
| 40 | #[cfg (not(any(rcc_f1, rcc_f1cl, rcc_f100, rcc_f2, rcc_f4, rcc_f410, rcc_l1)))] |
| 41 | impl From<LseDrive> for crate::pac::rcc::vals::Lsedrv { |
| 42 | fn from(value: LseDrive) -> Self { |
| 43 | use crate::pac::rcc::vals::Lsedrv; |
| 44 | |
| 45 | match value { |
| 46 | #[cfg (not(stm32h5))] // ES0565: LSE Low drive mode is not functional |
| 47 | LseDrive::Low => Lsedrv::LOW, |
| 48 | LseDrive::MediumLow => Lsedrv::MEDIUM_LOW, |
| 49 | LseDrive::MediumHigh => Lsedrv::MEDIUM_HIGH, |
| 50 | LseDrive::High => Lsedrv::HIGH, |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | #[cfg (not(any(rtc_v2l0, rtc_v2l1, stm32c0)))] |
| 56 | type Bdcr = crate::pac::rcc::regs::Bdcr; |
| 57 | #[cfg (any(rtc_v2l0, rtc_v2l1))] |
| 58 | type Bdcr = crate::pac::rcc::regs::Csr; |
| 59 | #[cfg (any(stm32c0))] |
| 60 | type Bdcr = crate::pac::rcc::regs::Csr1; |
| 61 | |
| 62 | #[cfg (any(stm32c0))] |
| 63 | fn unlock() {} |
| 64 | |
| 65 | #[cfg (not(any(stm32c0)))] |
| 66 | fn unlock() { |
| 67 | #[cfg (any(stm32f0, stm32f1, stm32f2, stm32f3, stm32l0, stm32l1))] |
| 68 | let cr = crate::pac::PWR.cr(); |
| 69 | #[cfg (not(any(stm32f0, stm32f1, stm32f2, stm32f3, stm32l0, stm32l1, stm32u5, stm32h5, stm32wba)))] |
| 70 | let cr = crate::pac::PWR.cr1(); |
| 71 | #[cfg (any(stm32u5, stm32h5, stm32wba))] |
| 72 | let cr: Reg = crate::pac::PWR.dbpcr(); |
| 73 | |
| 74 | cr.modify(|w: &mut Dbpcr| w.set_dbp(val:true)); |
| 75 | while !cr.read().dbp() {} |
| 76 | } |
| 77 | |
| 78 | fn bdcr() -> Reg<Bdcr, RW> { |
| 79 | #[cfg (any(rtc_v2l0, rtc_v2l1))] |
| 80 | return crate::pac::RCC.csr(); |
| 81 | #[cfg (not(any(rtc_v2l0, rtc_v2l1, stm32c0)))] |
| 82 | return crate::pac::RCC.bdcr(); |
| 83 | #[cfg (any(stm32c0))] |
| 84 | return crate::pac::RCC.csr1(); |
| 85 | } |
| 86 | |
| 87 | #[derive (Clone, Copy)] |
| 88 | pub struct LsConfig { |
| 89 | pub rtc: RtcClockSource, |
| 90 | pub lsi: bool, |
| 91 | pub lse: Option<LseConfig>, |
| 92 | } |
| 93 | |
| 94 | impl LsConfig { |
| 95 | pub const fn default_lse() -> Self { |
| 96 | Self { |
| 97 | rtc: RtcClockSource::LSE, |
| 98 | lse: Some(LseConfig { |
| 99 | frequency: Hertz(32_768), |
| 100 | mode: LseMode::Oscillator(LseDrive::MediumHigh), |
| 101 | #[cfg (any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] |
| 102 | peripherals_clocked: false, |
| 103 | }), |
| 104 | lsi: false, |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | pub const fn default_lsi() -> Self { |
| 109 | Self { |
| 110 | rtc: RtcClockSource::LSI, |
| 111 | lsi: true, |
| 112 | lse: None, |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | pub const fn off() -> Self { |
| 117 | Self { |
| 118 | rtc: RtcClockSource::DISABLE, |
| 119 | lsi: false, |
| 120 | lse: None, |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | impl Default for LsConfig { |
| 126 | fn default() -> Self { |
| 127 | // on L5, just the fact that LSI is enabled makes things crash. |
| 128 | // TODO: investigate. |
| 129 | |
| 130 | #[cfg (not(stm32l5))] |
| 131 | return Self::default_lsi(); |
| 132 | #[cfg (stm32l5)] |
| 133 | return Self::off(); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | impl LsConfig { |
| 138 | pub(crate) fn init(&self) -> Option<Hertz> { |
| 139 | let rtc_clk = match self.rtc { |
| 140 | RtcClockSource::LSI => { |
| 141 | assert!(self.lsi); |
| 142 | Some(LSI_FREQ) |
| 143 | } |
| 144 | RtcClockSource::LSE => Some(self.lse.as_ref().unwrap().frequency), |
| 145 | RtcClockSource::DISABLE => None, |
| 146 | _ => todo!(), |
| 147 | }; |
| 148 | |
| 149 | let (lse_en, lse_byp, lse_drv) = match &self.lse { |
| 150 | Some(c) => match c.mode { |
| 151 | LseMode::Oscillator(lse_drv) => (true, false, Some(lse_drv)), |
| 152 | LseMode::Bypass => (true, true, None), |
| 153 | }, |
| 154 | None => (false, false, None), |
| 155 | }; |
| 156 | #[cfg (any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] |
| 157 | let lse_sysen = if let Some(lse) = self.lse { |
| 158 | Some(lse.peripherals_clocked) |
| 159 | } else { |
| 160 | None |
| 161 | }; |
| 162 | #[cfg (rcc_u0)] |
| 163 | let lse_sysen = Some(lse_en); |
| 164 | |
| 165 | _ = lse_drv; // not all chips have it. |
| 166 | |
| 167 | // Disable backup domain write protection |
| 168 | unlock(); |
| 169 | |
| 170 | if self.lsi { |
| 171 | #[cfg (any(stm32u5, stm32h5, stm32wba))] |
| 172 | let csr = crate::pac::RCC.bdcr(); |
| 173 | #[cfg (not(any(stm32u5, stm32h5, stm32wba, stm32c0)))] |
| 174 | let csr = crate::pac::RCC.csr(); |
| 175 | #[cfg (any(stm32c0))] |
| 176 | let csr = crate::pac::RCC.csr2(); |
| 177 | |
| 178 | #[cfg (not(any(rcc_wb, rcc_wba)))] |
| 179 | csr.modify(|w| w.set_lsion(true)); |
| 180 | |
| 181 | #[cfg (any(rcc_wb, rcc_wba))] |
| 182 | csr.modify(|w| w.set_lsi1on(true)); |
| 183 | |
| 184 | #[cfg (not(any(rcc_wb, rcc_wba)))] |
| 185 | while !csr.read().lsirdy() {} |
| 186 | |
| 187 | #[cfg (any(rcc_wb, rcc_wba))] |
| 188 | while !csr.read().lsi1rdy() {} |
| 189 | } |
| 190 | |
| 191 | // backup domain configuration (LSEON, RTCEN, RTCSEL) is kept across resets. |
| 192 | // once set, changing it requires a backup domain reset. |
| 193 | // first check if the configuration matches what we want. |
| 194 | |
| 195 | // check if it's already enabled and in the source we want. |
| 196 | let reg = bdcr().read(); |
| 197 | let mut ok = true; |
| 198 | ok &= reg.rtcsel() == self.rtc; |
| 199 | #[cfg (not(rcc_wba))] |
| 200 | { |
| 201 | ok &= reg.rtcen() == (self.rtc != RtcClockSource::DISABLE); |
| 202 | } |
| 203 | ok &= reg.lseon() == lse_en; |
| 204 | ok &= reg.lsebyp() == lse_byp; |
| 205 | #[cfg (any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba, rcc_u0))] |
| 206 | if let Some(lse_sysen) = lse_sysen { |
| 207 | ok &= reg.lsesysen() == lse_sysen; |
| 208 | } |
| 209 | #[cfg (not(any(rcc_f1, rcc_f1cl, rcc_f100, rcc_f2, rcc_f4, rcc_f410, rcc_l1)))] |
| 210 | if let Some(lse_drv) = lse_drv { |
| 211 | ok &= reg.lsedrv() == lse_drv.into(); |
| 212 | } |
| 213 | |
| 214 | // if configuration is OK, we're done. |
| 215 | if ok { |
| 216 | trace!("BDCR ok: {:08x}" , bdcr().read().0); |
| 217 | return rtc_clk; |
| 218 | } |
| 219 | |
| 220 | // If not OK, reset backup domain and configure it. |
| 221 | #[cfg (not(any(rcc_l0, rcc_l0_v2, rcc_l1, stm32h5, stm32h7rs, stm32c0)))] |
| 222 | { |
| 223 | bdcr().modify(|w| w.set_bdrst(true)); |
| 224 | bdcr().modify(|w| w.set_bdrst(false)); |
| 225 | } |
| 226 | // H5 has a terrible, terrible errata: 'SRAM2 is erased when the backup domain is reset' |
| 227 | // pending a more sane sane way to handle this, just don't reset BD for now. |
| 228 | // This means the RTCSEL write below will have no effect, only if it has already been written |
| 229 | // after last power-on. Since it's uncommon to dynamically change RTCSEL, this is better than |
| 230 | // letting half our RAM go magically *poof*. |
| 231 | // STM32H503CB/EB/KB/RB device errata - 2.2.8 SRAM2 unduly erased upon a backup domain reset |
| 232 | // STM32H562xx/563xx/573xx device errata - 2.2.14 SRAM2 is erased when the backup domain is reset |
| 233 | //#[cfg(any(stm32h5, stm32h7rs))] |
| 234 | #[cfg (any(stm32h7rs))] |
| 235 | { |
| 236 | bdcr().modify(|w| w.set_vswrst(true)); |
| 237 | bdcr().modify(|w| w.set_vswrst(false)); |
| 238 | } |
| 239 | #[cfg (any(stm32c0, stm32l0))] |
| 240 | { |
| 241 | bdcr().modify(|w| w.set_rtcrst(true)); |
| 242 | bdcr().modify(|w| w.set_rtcrst(false)); |
| 243 | } |
| 244 | |
| 245 | if lse_en { |
| 246 | bdcr().modify(|w| { |
| 247 | #[cfg (not(any(rcc_f1, rcc_f1cl, rcc_f100, rcc_f2, rcc_f4, rcc_f410, rcc_l1)))] |
| 248 | if let Some(lse_drv) = lse_drv { |
| 249 | w.set_lsedrv(lse_drv.into()); |
| 250 | } |
| 251 | w.set_lsebyp(lse_byp); |
| 252 | w.set_lseon(true); |
| 253 | }); |
| 254 | |
| 255 | while !bdcr().read().lserdy() {} |
| 256 | |
| 257 | #[cfg (any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba, rcc_u0))] |
| 258 | if let Some(lse_sysen) = lse_sysen { |
| 259 | bdcr().modify(|w| { |
| 260 | w.set_lsesysen(lse_sysen); |
| 261 | }); |
| 262 | |
| 263 | if lse_sysen { |
| 264 | while !bdcr().read().lsesysrdy() {} |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | if self.rtc != RtcClockSource::DISABLE { |
| 270 | bdcr().modify(|w| { |
| 271 | #[cfg (any(rtc_v2h7, rtc_v2l4, rtc_v2wb, rtc_v3, rtc_v3u5))] |
| 272 | assert!(!w.lsecsson(), "RTC is not compatible with LSE CSS, yet." ); |
| 273 | |
| 274 | #[cfg (not(rcc_wba))] |
| 275 | w.set_rtcen(true); |
| 276 | w.set_rtcsel(self.rtc); |
| 277 | }); |
| 278 | } |
| 279 | |
| 280 | trace!("BDCR configured: {:08x}" , bdcr().read().0); |
| 281 | |
| 282 | compiler_fence(Ordering::SeqCst); |
| 283 | |
| 284 | rtc_clk |
| 285 | } |
| 286 | } |
| 287 | |