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 | |