stm32l4x6_hal/rcc/clocking.rs
1//! The `clocking` module contains representations of the various objects in the STM32L4x6
2//! clock tree (see Reference Manual Figs 15 and 16) useful for wiring them up.
3//!
4//! There are two main concepts: sources (enum variants) and clocks (structs). For example, the
5//! SYSCLK clock can be driven by any one of four sources: HSI, MSI, HSI, and PLLCLK. This
6//! knowledge is encoded in the SysClkSources enum.
7//!
8//! Each enum variant contains information about the clock it represents. Some clocks are
9//! configurable, and thus have fields, and some are not. For example, the LSI
10//! (LowSpeedInternalRC) clock is always 32 kHz, but the MSI (MediumSpeedInternalRC) clock can
11//! be configured and thus has a frequency component.
12//!
13//! To use them, compose them and feed them to, e.g., sysclk.
14//!
15//! ```rust
16//! let mut rcc = RCC.constrain();
17//! let msi_clk = clocking::MediumSpeedInternalRC::new(8_000_000, false);
18//! let sys_clk_src = clocking::SysClkSource::MSI(msi_clk);
19//! let cfgr = rcc.cfgr.sysclk(sys_clk_src);
20//! ```
21//!
22//! The PLL is a bit more complex because it _is_ a source (`PLLClkOutput`) and also _requires_
23//! a source (`PLLClkSource`), but you compose the types similarly.
24
25use super::rcc;
26
27/// Clocks (OSCs or RCs) that can be used as inputs to peripherals
28///
29/// This trait isn't actually specified anywhere, and is used only by convention.
30pub trait InputClock {
31 /// Return the frequency of the clock (either calculated, configured, or intrinsic)
32 fn freq(&self) -> u32;
33}
34
35/// High-speed internal 16 MHz RC
36#[derive(Clone, Copy)]
37pub struct HighSpeedInternal16RC {
38 /// Force HSI16 ON even in Stop modes
39 pub always_on: bool,
40 /// When the system wakeup clock is MSI, wake up the HSI16 in parallel to system wakeup.
41 pub auto_start: bool,
42}
43
44impl HighSpeedInternal16RC {
45 /// Applies the selection options to the configuration registers and turns the clock on
46 pub fn configure(&self, rcc: &rcc::RegisterBlock) -> (u32, u8) {
47 rcc.cr.modify(|_, w| w.hsion().set_bit().hsikeron().bit(self.always_on).hsiasfs().bit(self.auto_start));
48 while rcc.cr.read().hsirdy().bit_is_clear() {}
49 (16_000_000, 0b01)
50 }
51}
52
53/// Medium-speed internal 100 kHz - 48 MHz RC
54#[derive(Clone, Copy)]
55pub struct MediumSpeedInternalRC {
56 freq: u32,
57 auto_cal: bool,
58}
59
60impl MediumSpeedInternalRC {
61 /// Create a new MSI RC
62 ///
63 /// `freq` must be a valid MSI RC frequency range (see 6.2.3)
64 /// TODO make freq a repr(C) enum
65 pub fn new(freq: u32, auto_cal: bool) -> Self {
66 MediumSpeedInternalRC { freq, auto_cal }
67 }
68
69 /// Convert the freq range to MSIRANGE bits (6.4.1). Panics if `freq` is invalid.
70 pub fn bits(&self) -> u8 {
71 match self.freq {
72 100_000 => 0b0000,
73 200_000 => 0b0001,
74 400_000 => 0b0010,
75 800_000 => 0b0011,
76 1_000_000 => 0b0100,
77 2_000_000 => 0b0101,
78 4_000_000 => 0b0110,
79 8_000_000 => 0b0111,
80 16_000_000 => 0b1000,
81 24_000_000 => 0b1001,
82 32_000_000 => 0b1010,
83 48_000_000 => 0b1011,
84 _ => panic!("bad MSI speed value!"),
85 }
86 }
87
88 /// Configures the MSI to the specified frequency, and enables hardware
89 /// auto-calibration if requested by enabling (and waiting for) the LSE.
90 pub fn configure(&self, rcc: &rcc::RegisterBlock) -> (u32, u8) {
91 rcc.cr.modify(|_, w| unsafe { w.msirange().bits(self.bits()).msirgsel().set_bit() });
92 while rcc.cr.read().msirdy().bit_is_clear() {}
93
94 if self.auto_cal {
95 // FIXME This... may not work? I'm not sure if I've got a board problem or using
96 // the LSE requires some precondition I'm missing. In either case, LSERDY is never
97 // set by the hardware, so auto_cal doesn't succeed.
98 rcc.apb1enr1.modify(|_, w| w.pwren().set_bit());
99
100 rcc.bdcr.modify(|_, w| w.lseon().clear_bit());
101 while rcc.bdcr.read().lserdy().bit_is_set() {}
102 rcc.bdcr.modify(|_, w| unsafe { w.lsedrv().bits(0b11).lseon().set_bit() });
103 while rcc.bdcr.read().lserdy().bit_is_clear() {}
104 rcc.cr.modify(|_, w| w.msipllen().set_bit());
105 }
106 (self.freq(), 0b00)
107 }
108}
109
110impl InputClock for MediumSpeedInternalRC {
111 fn freq(&self) -> u32 {
112 self.freq
113 }
114}
115
116/// High-speed external 4-48 MHz oscillator
117#[derive(Clone, Copy)]
118pub struct HighSpeedExternalOSC(pub u32);
119
120impl InputClock for HighSpeedExternalOSC {
121 fn freq(&self) -> u32 {
122 self.0
123 }
124}
125
126impl HighSpeedExternalOSC {
127 /// Turns on the HSE oscillator.
128 ///
129 /// (Should this also configure the pin?)
130 pub fn configure(&self, rcc: &rcc::RegisterBlock) -> (u32, u8) {
131 rcc.cr.modify(|_, w| w.hseon().set_bit());
132 while rcc.cr.read().hserdy().bit_is_clear() {}
133 (self.freq(), 0b10)
134 }
135}
136
137/// Selectable input clocks to the RTC
138#[repr(C)]
139#[derive(Copy, Clone)]
140pub enum RtcClkSource {
141 /// RTC off
142 None,
143 /// Internal 32 kHz RC
144 LSI,
145 /// External 32.768 kHz oscillator
146 LSE,
147 /// High-speed external oscillator, prescaled by (a fixed value of) 32
148 HSEDiv32,
149}
150
151impl RtcClkSource {
152 /// Returns the output frequency of the RtcClkSource based on its input.
153 pub fn freq(&self, hse: Option<HighSpeedExternalOSC>) -> Option<u32> {
154 match *self {
155 RtcClkSource::None => None,
156 RtcClkSource::LSI => Some(32_000),
157 RtcClkSource::LSE => Some(32_768),
158 RtcClkSource::HSEDiv32 => {
159 if let Some(clk) = hse {
160 Some(clk.freq() / 32)
161 } else {
162 None
163 }
164 },
165 }
166 }
167
168 /// Return bits for setting RTCSEL (see 6.2.14)
169 pub fn bits(&self) -> u8 {
170 *self as u8
171 }
172}
173
174/// Selectable clocks for the SYSCLK signal (HCLK bus)
175#[derive(Clone, Copy)]
176pub enum SysClkSource {
177 /// High speed internal 16 MHz RC
178 HSI16(HighSpeedInternal16RC),
179 /// Medium speed internal 100kHz-48MHz RC
180 MSI(MediumSpeedInternalRC),
181 /// High-speed external oscillator
182 HSE(HighSpeedExternalOSC),
183 /// PLLCLK signal (output of PLL)
184 PLL(PLLClkOutput),
185}
186
187impl InputClock for SysClkSource {
188 fn freq(&self) -> u32 {
189 match *self {
190 SysClkSource::HSI16(_) => 16_000_000,
191 SysClkSource::MSI(s) => s.freq(),
192 SysClkSource::HSE(s) => s.freq(),
193 SysClkSource::PLL(s) => s.freq(),
194 }
195 }
196}
197
198/// PLLCLK output of PLL module
199#[derive(Clone, Copy)]
200pub struct PLLClkOutput {
201 /// The input source of the PLL module
202 pub src: PLLClkSource,
203 /// The initial prescaler value into all PLLs
204 pub m: u8,
205 n: u8,
206 r: u8,
207 f: u32,
208}
209
210impl PLLClkOutput {
211 /// Create a new PLL clock source to use as an input.
212 ///
213 /// The arguments refer to the scale factors described in Figs. 15 and 16 of the reference
214 /// manual, and end up in the PLLM, PLLN, and PLLR fields of the PLLCFGR register.
215 ///
216 /// Panics if the configuration is invalid, especially if the output frequency is >80 MHz
217 pub fn new(src: PLLClkSource, m: u8, n: u8, r: u8) -> Self {
218 assert!(m > 0 && m < 9);
219 assert!(n > 7 && n < 87);
220 assert!(r == 2 || r == 4 || r == 6 || r == 8);
221 let f = src.freq() / m as u32 * n as u32 / r as u32;
222 assert!(f < super::SYS_CLOCK_MAX);
223
224 PLLClkOutput { src, m, n, r, f }
225 }
226
227 /// Configure the PLL to enable the PLLCLK output. This explicitly does not (yet?)
228 /// support any PLL other than `PLL`, and no other outputs than `PLLCLK`, so this is
229 /// not suitable for driving e.g. USB.
230 pub fn configure(&self, rcc: &rcc::RegisterBlock) -> (u32, u8) {
231 let pllsrc_bits = self.src.configure(rcc);
232 rcc.cr.modify(|_, w| w.pllon().clear_bit());
233 while rcc.cr.read().pllrdy().bit_is_set() {}
234 rcc.pllcfgr
235 .modify(|_, w| unsafe { w.pllsrc().bits(pllsrc_bits).pllm().bits(self.m - 1).plln().bits(self.n).pllr().bits(self.r) });
236 rcc.cr.modify(|_, w| w.pllon().set_bit());
237 while rcc.cr.read().pllrdy().bit_is_clear() {}
238 rcc.pllcfgr.modify(|_, w| w.pllren().set_bit());
239 (self.freq(), 0b11)
240 }
241}
242
243impl InputClock for PLLClkOutput {
244 fn freq(&self) -> u32 {
245 self.f
246 }
247}
248
249//
250/// PLLADC2CLK output of PLLSAI2
251// #[derive(Clone, Copy)]
252// pub struct PLLADC2Clk {
253// src: PLLClkSource,
254// ...,
255// }
256//
257
258/// Selectable PLL module input sources
259#[derive(Clone, Copy)]
260pub enum PLLClkSource {
261 /// PLL off
262 None,
263 /// MSI16
264 MSI(MediumSpeedInternalRC),
265 /// HSI16
266 HSI16(HighSpeedInternal16RC),
267 /// HSE
268 HSE(HighSpeedExternalOSC),
269}
270
271impl PLLClkSource {
272 /// This configures the input to the PLL. It's usually only called by
273 /// PLLClkOutput::configure.
274 pub fn configure(&self, rcc: &rcc::RegisterBlock) -> u8 {
275 match self {
276 PLLClkSource::None => 0b00,
277 PLLClkSource::MSI(s) => {
278 let _c = s.configure(rcc);
279 0b01
280 },
281 PLLClkSource::HSI16(s) => {
282 let _c = s.configure(rcc);
283 0b10
284 },
285 PLLClkSource::HSE(s) => {
286 let _c = s.configure(rcc);
287 0b11
288 },
289 }
290 }
291}
292
293impl InputClock for PLLClkSource {
294 fn freq(&self) -> u32 {
295 match *self {
296 PLLClkSource::None => 0,
297 PLLClkSource::MSI(s) => s.freq(),
298 PLLClkSource::HSI16(_) => 16_000_000,
299 PLLClkSource::HSE(s) => s.freq(),
300 }
301 }
302}