aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Glynn <[email protected]>2022-05-27 21:15:38 -0500
committerWill Glynn <[email protected]>2022-05-27 23:56:42 -0500
commit1c2b27dcadf6256dca6ddf7b3d8e5650fe8cb99d (patch)
treeff2e691da89288079da79f9a33b16b110711cb62
parent82e873d920c843d458c23dbd1b3e631006a29d0b (diff)
embassy-stm32: g0: add PLL clock source
STM32G0 SYSCLK can be sourced from PLLRCLK. Given that the HSI runs at 16 MHz and the HSE range is 4-48 MHz, the PLL is the only way to reach 64 MHz. This commit adds `ClockSrc::PLL`. The PLL sources from either HSI16 or HSE, divides it by `m`, and locks its VCO to multiple `n`. It then divides the VCO by `r`, `p`, and `q` to produce up to three associated clock signals: * PLLRCLK is one of the inputs on the SYSCLK mux. This is the main reason the user will configure the PLL, so `r` is mandatory and the output is enabled unconditionally. * PLLPCLK is available as a clock source for the ADC and I2S peripherals, so `p` is optional and the output is conditional. * PLLQCLK exists only on STM32G0B0xx, and exists only to feed the MCO and MCO2 peripherals, so `q` is optional and the output is conditional. When the user specifies `ClockSrc::PLL(PllConfig)`, `rcc::init()` calls `PllConfig::init()` which initializes the PLL per [RM0454]. It disables the PLL, waits for it to stop, enables the source oscillator, configures the PLL, waits for it to lock, and then enables the appropriate outputs. `rcc::init()` then switches the clock source to PLLRCLK. `rcc::init()` is now also resonsible for calculating and setting flash wait states. SYSCLCK < 24 MHz is fine in the reset state, but 24-48 MHz requires waiting 1 cycle and 48-64 MHz requires waiting 2 cycles. (This was likely a blocker for anyone using HSE >= 24 MHz, with or without the PLL.) Flash accesses are now automatically slowed down as needed before changing the clock source, and sped up as permitted after changing the clock source. The number of flash wait states also determines if flash prefetching will be profitable, so that is now handled automatically too. [RM0454]: https://www.st.com/resource/en/reference_manual/rm0454-stm32g0x0-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
-rw-r--r--embassy-stm32/src/rcc/g0.rs285
1 files changed, 280 insertions, 5 deletions
diff --git a/embassy-stm32/src/rcc/g0.rs b/embassy-stm32/src/rcc/g0.rs
index ad5a661d6..be0497290 100644
--- a/embassy-stm32/src/rcc/g0.rs
+++ b/embassy-stm32/src/rcc/g0.rs
@@ -1,5 +1,6 @@
1use crate::pac::rcc::vals::{Hpre, Hsidiv, Ppre, Sw}; 1use crate::pac::flash::vals::Latency;
2use crate::pac::{PWR, RCC}; 2use crate::pac::rcc::vals::{self, Hpre, Hsidiv, Ppre, Sw};
3use crate::pac::{FLASH, PWR, RCC};
3use crate::rcc::{set_freqs, Clocks}; 4use crate::rcc::{set_freqs, Clocks};
4use crate::time::Hertz; 5use crate::time::Hertz;
5use crate::time::U32Ext; 6use crate::time::U32Ext;
@@ -15,6 +16,7 @@ pub const LSI_FREQ: u32 = 32_000;
15pub enum ClockSrc { 16pub enum ClockSrc {
16 HSE(Hertz), 17 HSE(Hertz),
17 HSI16(HSI16Prescaler), 18 HSI16(HSI16Prescaler),
19 PLL(PllConfig),
18 LSI, 20 LSI,
19} 21}
20 22
@@ -45,6 +47,132 @@ impl Into<Hsidiv> for HSI16Prescaler {
45 } 47 }
46} 48}
47 49
50/// The PLL configuration.
51///
52/// * `VCOCLK = source / m * n`
53/// * `PLLRCLK = VCOCLK / r`
54/// * `PLLQCLK = VCOCLK / q`
55/// * `PLLPCLK = VCOCLK / p`
56#[derive(Clone, Copy)]
57pub struct PllConfig {
58 /// The source from which the PLL receives a clock signal
59 pub source: PllSrc,
60 /// The initial divisor of that clock signal
61 pub m: Pllm,
62 /// The PLL VCO multiplier, which must be in the range `8..=86`.
63 pub n: u8,
64 /// The final divisor for `PLLRCLK` output which drives the system clock
65 pub r: Pllr,
66
67 /// The divisor for the `PLLQCLK` output, if desired
68 pub q: Option<Pllr>,
69
70 /// The divisor for the `PLLPCLK` output, if desired
71 pub p: Option<Pllr>,
72}
73
74impl Default for PllConfig {
75 #[inline]
76 fn default() -> PllConfig {
77 // HSI16 / 1 * 8 / 2 = 64 MHz
78 PllConfig {
79 source: PllSrc::HSI16,
80 m: Pllm::Div1,
81 n: 8,
82 r: Pllr::Div2,
83 q: None,
84 p: None,
85 }
86 }
87}
88
89#[derive(Clone, Copy, Eq, PartialEq)]
90pub enum PllSrc {
91 HSI16,
92 HSE(Hertz),
93}
94
95#[derive(Clone, Copy)]
96pub enum Pllm {
97 Div1,
98 Div2,
99 Div3,
100 Div4,
101 Div5,
102 Div6,
103 Div7,
104 Div8,
105}
106
107impl From<Pllm> for u8 {
108 fn from(v: Pllm) -> Self {
109 match v {
110 Pllm::Div1 => 0b000,
111 Pllm::Div2 => 0b001,
112 Pllm::Div3 => 0b010,
113 Pllm::Div4 => 0b011,
114 Pllm::Div5 => 0b100,
115 Pllm::Div6 => 0b101,
116 Pllm::Div7 => 0b110,
117 Pllm::Div8 => 0b111,
118 }
119 }
120}
121
122impl From<Pllm> for u32 {
123 fn from(v: Pllm) -> Self {
124 match v {
125 Pllm::Div1 => 1,
126 Pllm::Div2 => 2,
127 Pllm::Div3 => 3,
128 Pllm::Div4 => 4,
129 Pllm::Div5 => 5,
130 Pllm::Div6 => 6,
131 Pllm::Div7 => 7,
132 Pllm::Div8 => 8,
133 }
134 }
135}
136
137#[derive(Clone, Copy)]
138pub enum Pllr {
139 Div2,
140 Div3,
141 Div4,
142 Div5,
143 Div6,
144 Div7,
145 Div8,
146}
147
148impl From<Pllr> for u8 {
149 fn from(v: Pllr) -> Self {
150 match v {
151 Pllr::Div2 => 0b000,
152 Pllr::Div3 => 0b001,
153 Pllr::Div4 => 0b010,
154 Pllr::Div5 => 0b011,
155 Pllr::Div6 => 0b101,
156 Pllr::Div7 => 0b110,
157 Pllr::Div8 => 0b111,
158 }
159 }
160}
161
162impl From<Pllr> for u32 {
163 fn from(v: Pllr) -> Self {
164 match v {
165 Pllr::Div2 => 2,
166 Pllr::Div3 => 3,
167 Pllr::Div4 => 4,
168 Pllr::Div5 => 5,
169 Pllr::Div6 => 6,
170 Pllr::Div7 => 7,
171 Pllr::Div8 => 8,
172 }
173 }
174}
175
48/// AHB prescaler 176/// AHB prescaler
49#[derive(Clone, Copy, PartialEq)] 177#[derive(Clone, Copy, PartialEq)]
50pub enum AHBPrescaler { 178pub enum AHBPrescaler {
@@ -117,6 +245,95 @@ impl Default for Config {
117 } 245 }
118} 246}
119 247
248impl PllConfig {
249 pub(crate) unsafe fn init(self) -> u32 {
250 assert!(self.n >= 8 && self.n <= 86);
251 let (src, input_freq) = match self.source {
252 PllSrc::HSI16 => (vals::Pllsrc::HSI16, HSI_FREQ),
253 PllSrc::HSE(freq) => (vals::Pllsrc::HSE, freq.0),
254 };
255
256 let m_freq = input_freq / u32::from(self.m);
257 // RM0454 § 5.4.4:
258 // > Caution: The software must set these bits so that the PLL input frequency after the
259 // > /M divider is between 2.66 and 16 MHz.
260 debug_assert!(m_freq >= 2_660_000 && m_freq <= 16_000_000);
261
262 let n_freq = m_freq * self.n as u32;
263 // RM0454 § 5.4.4:
264 // > Caution: The software must set these bits so that the VCO output frequency is between
265 // > 64 and 344 MHz.
266 debug_assert!(n_freq >= 64_000_000 && n_freq <= 344_000_000);
267
268 let r_freq = n_freq / u32::from(self.r);
269 // RM0454 § 5.4.4:
270 // > Caution: The software must set this bitfield so as not to exceed 64 MHz on this clock.
271 debug_assert!(r_freq <= 64_000_000);
272
273 // RM0454 § 5.2.3:
274 // > To modify the PLL configuration, proceed as follows:
275 // > 1. Disable the PLL by setting PLLON to 0 in Clock control register (RCC_CR).
276 RCC.cr().modify(|w| w.set_pllon(false));
277
278 // > 2. Wait until PLLRDY is cleared. The PLL is now fully stopped.
279 while RCC.cr().read().pllrdy() {}
280
281 // > 3. Change the desired parameter.
282 // Enable whichever clock source we're using, and wait for it to become ready
283 match self.source {
284 PllSrc::HSI16 => {
285 RCC.cr().write(|w| w.set_hsion(true));
286 while !RCC.cr().read().hsirdy() {}
287 }
288 PllSrc::HSE(_) => {
289 RCC.cr().write(|w| w.set_hseon(true));
290 while !RCC.cr().read().hserdy() {}
291 }
292 }
293
294 // Configure PLLSYSCFGR
295 RCC.pllsyscfgr().modify(|w| {
296 w.set_pllr(u8::from(self.r));
297 w.set_pllren(false);
298
299 if let Some(q) = self.q {
300 w.set_pllq(u8::from(q));
301 }
302 w.set_pllqen(false);
303
304 if let Some(p) = self.p {
305 w.set_pllp(u8::from(p));
306 }
307 w.set_pllpen(false);
308
309 w.set_plln(self.n);
310
311 w.set_pllm(self.m as u8);
312
313 w.set_pllsrc(src)
314 });
315
316 // > 4. Enable the PLL again by setting PLLON to 1.
317 RCC.cr().modify(|w| w.set_pllon(true));
318
319 // Wait for the PLL to become ready
320 while !RCC.cr().read().pllrdy() {}
321
322 // > 5. Enable the desired PLL outputs by configuring PLLPEN, PLLQEN, and PLLREN in PLL
323 // > configuration register (RCC_PLLCFGR).
324 RCC.pllsyscfgr().modify(|w| {
325 // We'll use R for system clock, so enable that unconditionally
326 w.set_pllren(true);
327
328 // We may also use Q or P
329 w.set_pllqen(self.q.is_some());
330 w.set_pllpen(self.p.is_some());
331 });
332
333 r_freq
334 }
335}
336
120pub(crate) unsafe fn init(config: Config) { 337pub(crate) unsafe fn init(config: Config) {
121 let (sys_clk, sw) = match config.mux { 338 let (sys_clk, sw) = match config.mux {
122 ClockSrc::HSI16(div) => { 339 ClockSrc::HSI16(div) => {
@@ -137,6 +354,10 @@ pub(crate) unsafe fn init(config: Config) {
137 354
138 (freq.0, Sw::HSE) 355 (freq.0, Sw::HSE)
139 } 356 }
357 ClockSrc::PLL(pll) => {
358 let freq = pll.init();
359 (freq, Sw::PLLRCLK)
360 }
140 ClockSrc::LSI => { 361 ClockSrc::LSI => {
141 // Enable LSI 362 // Enable LSI
142 RCC.csr().write(|w| w.set_lsion(true)); 363 RCC.csr().write(|w| w.set_lsion(true));
@@ -145,12 +366,66 @@ pub(crate) unsafe fn init(config: Config) {
145 } 366 }
146 }; 367 };
147 368
369 // Determine the flash latency implied by the target clock speed
370 // RM0454 § 3.3.4:
371 let target_flash_latency = if sys_clk <= 24_000_000 {
372 Latency::WS0
373 } else if sys_clk <= 48_000_000 {
374 Latency::WS1
375 } else {
376 Latency::WS2
377 };
378
379 // Increase the number of cycles we wait for flash if the new value is higher
380 // There's no harm in waiting a little too much before the clock change, but we'll
381 // crash immediately if we don't wait enough after the clock change
382 let mut set_flash_latency_after = false;
383 FLASH.acr().modify(|w| {
384 // Is the current flash latency less than what we need at the new SYSCLK?
385 if w.latency().0 <= target_flash_latency.0 {
386 // We must increase the number of wait states now
387 w.set_latency(target_flash_latency)
388 } else {
389 // We may decrease the number of wait states later
390 set_flash_latency_after = true;
391 }
392
393 // RM0454 § 3.3.5:
394 // > Prefetch is enabled by setting the PRFTEN bit of the FLASH access control register
395 // > (FLASH_ACR). This feature is useful if at least one wait state is needed to access the
396 // > Flash memory.
397 //
398 // Enable flash prefetching if we have at least one wait state, and disable it otherwise.
399 w.set_prften(target_flash_latency.0 > 0);
400 });
401
402 if !set_flash_latency_after {
403 // Spin until the effective flash latency is compatible with the clock change
404 while FLASH.acr().read().latency().0 < target_flash_latency.0 {}
405 }
406
407 // Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once
408 let (sw, hpre, ppre) = (sw.into(), config.ahb_pre.into(), config.apb_pre.into());
148 RCC.cfgr().modify(|w| { 409 RCC.cfgr().modify(|w| {
149 w.set_sw(sw.into()); 410 w.set_sw(sw);
150 w.set_hpre(config.ahb_pre.into()); 411 w.set_hpre(hpre);
151 w.set_ppre(config.apb_pre.into()); 412 w.set_ppre(ppre);
152 }); 413 });
153 414
415 if set_flash_latency_after {
416 // We can make the flash require fewer wait states
417 // Spin until the SYSCLK changes have taken effect
418 loop {
419 let cfgr = RCC.cfgr().read();
420 if cfgr.sw() == sw && cfgr.hpre() == hpre && cfgr.ppre() == ppre {
421 break;
422 }
423 }
424
425 // Set the flash latency to require fewer wait states
426 FLASH.acr().modify(|w| w.set_latency(target_flash_latency));
427 }
428
154 let ahb_div = match config.ahb_pre { 429 let ahb_div = match config.ahb_pre {
155 AHBPrescaler::NotDivided => 1, 430 AHBPrescaler::NotDivided => 1,
156 AHBPrescaler::Div2 => 2, 431 AHBPrescaler::Div2 => 2,